Velodrome v2
Stage 1
Website

Protocol Decentralization

Stage 0
Stage 1
Stage 2

  • Upgrades with potential of “loss of funds or unclaimed yield” not protected with onchain governance AND Exit Window >= 30 days
  • There are no external dependencies
  • Alternative third-party frontends exist

Risk Areas

Chain
Upgrades
Autonomy
Exit Window
Access

Summary

Velodrome Finance is a next-generation AMM that combines the best of Curve, Convex and Uniswap, designed to serve as the liquidity hub for the Superchain. Velodrome NFTs vote on token emissions and receive incentives and fees generated by the protocol.

Ratings

## Chain

Velodrome is deployed on the Optimism chain, an Ethereum L2 in Stage 1 according to L2BEAT.

Chain score: Medium

Upgradeability

To a large part the protocol is immutable. In particular, no permissions with a potential impact on user funds have been identified.

The remaining permissions are related to the protocol's rewards system and periphery contracts. These permissions are controlled by a number of multisigs including an undeclared multisig.

Among these, the undeclared multisig has control over a whitelist of special voters with the ability to change the rewards distributed to LPs. Specifically, "regular" veNFT holders compete in weekly voting rounds to distribute a fixed amount of rewards, paid in the protocol's native token, VELO. Regular voters are able to signal their support for a specific gauge which, after the conclusion of a voting round, receives and distributes rewards among the LPs of the linked pool. This public voting period is followed by a "private" voting window of 1 hour during which only the whitelisted veNFT holders are able to participate. As a result, these whitelisted veNFT holders, and by extension the undeclared multisig, are able to control the final vote outcome. The impact of this is classified as a potential material change of the expected performance including the loss of accrued yield (if public votes are overwritten by the whitelisted veNFTs).

The undeclared multisig further is the owner of various contracts, and the associated permissions, including the FactoryRegistry. This contract allows the undeclared multisig to (un-) approve factory contracts within the protocol. User's swaps are routed through the pools created from the approved factories. Users are protected from malicious factories (and pools) through a user defined slippage tolerance which is enforced on all swaps on the router contract. On the other hand, only pools created from the default factory are enabled for LP provisioning functions effectively protecting LPs in the protocol.

Another noteworthy permission in the protocol is the ability to kill and revive gauges and thereby stop, or enable, a gauge to receive and distribute rewards to LPs and voters. However, killing a gauge does not affect the rewards already distributed to LPs or voters and also does not affect the rewards, or any other aspect, of other gauges. This permission hence can not materially affect the protocol's expected performance.

Upgradeability score: Medium

Autonomy

Velodrome does not have external dependencies 🎉

Autonomy score: Low

Exit Window

Existing permissions have a Medium risk score and are not protected by an Exit Window. Users are not able to withdraw funds in case of an unwanted update.

Exit Window score: Medium

Accessibility

Velodrome provides several access points to the protocol, both decentralized and centralized. Decentralized links include velo.drome.eth, velo.drome.eth.limo, and velo.drome.eth.link, while centralized options include velodrome.finance and alt.velodrome.finance. This diversity of user interfaces offers redundancy and reduces risks associated with access issues.

Accessibility score: Low

Conclusion

The Velodrome protocol achieves Medium centralization risk scores for its Upgradeability and Exit Window dimensions. It thus ranks Stage 1.

The protocol could reach Stage 2 by transferring the permissions to an onchain governance with 30 days Exit Window.

Overall score: Stage 1

Reviewer notes

There were no particular discoveries made during the analysis of this protocol.

Protocol Analysis

Killing of a gauge

After each period, VELO tokens from the Minter contract flow into the Voter contract via calling updatePeriod(). Once the tokens are in the Voter contract, they flow according to the voting weight to each Gauge through calling distribute() on the Voter contract. Once the VELO tokens ended up in the different Gauge contracts, the users can claim their reward no matter of the living status of the Gauge. It's the function call distribute() that sends the tokens to the Gauge which reverts if the Gauge is killed. For rewards to be claimable for users the function calls updatePeriod() and distribute() need to occur in succession and the killing of the Gauge before either one of them results in yield not being available to the user. But, since both functions are public everyone, can call them. And thus during the first block of each period where updatePeriod() is callable, the permission owner that kills the Gauge needs to frontrun the transactions which are calling updatePeriod() and distribute(). This centralisation vector is thus not definitive and only probabilistic.

On the other hand, Voters earn a claim on yield proportional to their allocated votes when calling the vote() for a gauge on the Voter contract. Function vote() will revert if the respective gauge is killed, thus not crediting a claim on yield for the killed gauge. However, previous votes and the credited yield claims, before the gauge is killed, are not affected by this. Voters will thus still be able to claim their accumulated yield.

Dependencies

No external dependency has been found.

Governance

Security Council

NameAccountType≥ 7 signers≥ 51% threshold≥ 50% non-insiderSigners public
Emergency Council0x838352F4E3992187a33a04826273dB3992Ee2b3fMultisig 4/7
Undeclared Multisig0xBA4BB89f4d1E66AA86B60696534892aE0cCf91F5Multisig 3/7

Info sourced from here: https://velodrome.finance/security#contracts.

Exit Window

No timelocks have been found protecting the various permissioned functions in the protocol. All updates take effect immediately.

Contracts & Permissions

Contracts

Contract NameAddress
VeArtProxy0x4A9eA0dd5649eC4B6745c60d1769e2184C1782DD
RewardsDistributor0x9D4736EC60715e71aFe72973f7885DCBC21EA99b
FactoryRegistry0xF4c67CdEAaB8360370F41514d06e32CcD8aA1d7B
Forwarder0x06824df38D1D77eADEB6baFCB03904E27429Ab74
GaugeFactory0x8391fE399640E7228A059f8Fa104b8a7B4835071
Gauge0x987E7922367B23C4A5fa34C8C5B1385174A095d6
ManagedRewardsFactory0xcDd9585005095ac7447d1fDbC990C5CFB805cff0
Minter0x6dc9E1C04eE59ed3531d73a72256C0da46D10982
PoolFactory0xF1046053aa5682b4F9a81b5481394DA16BE5FF5a
Pool (example)0xa0A215dE234276CAc1b844fD58901351a50fec8A
Router0xa062aE8A9c5e11aaA026fc2670B0D65cCc8B2858
SinkConverter0x585Af0b397AC42dbeF7f18395426BF878634f18D
SinkDrain0xda03Dc399aF3F1545748243aAFbC5050A63B17eC
SinkManager0x5aeE5F0E6C2055EbD776DB25F48f6c9A68ABcdaE
SinkManagerFacilitator0x45fF00822E8235b86Cb605ac8295c14628cE78a4
VELO0x9560e827aF36c94D2Ac33a39bCE1Fe78631088Db
Voter0x41C914ee0c7E1A5edCD0295623e6dC557B5aBf3C
VotingEscrow0xFAf8FD17D9840595845582fCB047DF13f006787d
VotingRewardsFactory0x756E7C245C69d351FfFBfb88bA234aa395AdA8ec
RestrictedTeam0x0a16cB36b553ba2bB2339f3B206A965E9841d305
Splitter0x6666B2dF7A328Cf775778Ebad368F5f13e39eC4C
PatchedManagedRewardsFactory0x3F468e35f5c262a6E796bfe3be831Bf8b9142e9c
RelayFactoryRegistry (called Registry)0x6b1253B116B5919932399295C75116d33F8EfF96
KeeperRegistry (called Registry)0x7bC95b327DF9d6dE05C1A02F6D252986Fcf45AF7
OptimizerRegistry (called Registry)0x7feF5407eD6C83f78eF82B3389FF89019095266B
CLGaugeFactory0x327147eE440252b893A771345025B41A267Ad985
CLGauge0x7155b84A704F0657975827c65Ff6fe42e3A962bb
MixedRouteQuoterV10xFF79ec912bA114FD7989b9A2b90C65f0c1b44722
NonfungiblePositionManager0x416b433906b1B72FA758e166e239c43d68dC6F29
NonfungibleTokenPositionDescriptor0xccDf417f49a14bC2b23c71684de0304C56DEA165
CLFactory0xCc0bDDB707055e04e497aB22a59c2aF4391cd12F
CLPool0xc28aD28853A547556780BEBF7847628501A3bCbb
QuoterV20x89D8218ed5fF1e46d8dcd33fb0bbeE3be1621466
CustomSwapFeeModule0x7361E9079920fb75496E9764A2665d8ee5049D5f
CustomUnstakedFeeModule0xC565F7ba9c56b157Da983c4Db30e13F5f06C59D9
UniversalRouter0x4bF3E32de155359D1D75e8B474b66848221142fc
SwapRouter0x0792a633F0c19c351081CF4B211F68F79bCc9676
VeloOracle0x07F544813E9Fb63D57a92f28FbD3FF0f7136F5cE
SugarHelper0x495193DaebdE03E12857f4d3BB8984da2d447a69
LpMigrator0x3Fdb481B25b24824A2339a4A1AbD0B0BC7534e71

All Permission Owners

NameAccountType
Undeclared Multisig0xBA4BB89f4d1E66AA86B60696534892aE0cCf91F5Multisig 3/7
Emergency Council0x838352F4E3992187a33a04826273dB3992Ee2b3fMultisig 4/7

Permissions

ContractFunctionImpactOwner
FactoryRegistryrenounceOwnershipThe function allows the permission owner to renounce ownership over all permissioned functions in this contract.Undeclared Multisig
FactoryRegistrytransferOwnershipThe function allows the permission owner to transfer ownership over all permissioned functions in this contract.Undeclared Multisig
FactoryRegistryapproveThis function allows the permission owner to approve a set of factories used in Velodrome Protocol. The Router contract (0xa062aE8A9c5e11aaA026fc2670B0D65cCc8B2858) checks whether the submitted factory is approved with the function isPoolFactoryApproved exposed by the FactoryRegistry. If a factory is approved, the router allows swaps on the pools from this factory. LiquidityManagement (addLiquidity() or removeLiquidity()) is not affected, as it only allows pools from the original PoolFactory 0xF1046053aa5682b4F9a81b5481394DA16BE5FF5aUndeclared Multisig
FactoryRegistryunapproveThis function allows the permission owner to unapprove a set of factories used in Velodrome Protocol. Calling removes approval for routing swaps.Undeclared Multisig
FactoryRegistrysetManagedRewardsFactoryThe permission owner can set the managed rewards factory as default for managed positions. This factory is used by the VotingEscrow contract to generate rewards for users that have a managed ve position (veNFT contract looks up the registered ManagedRewardsFactory inside createManagedLockFor). The reward contracts deployed from the ManagedRewardsFactory expose getReward function which could be a malicious implementation (not paying out rewards for managed positions after calling setManagedRewardsFactory with a malicious factory).Undeclared Multisig
MinternudgeAllows epoch governor to modify the tail emission rate within specific bounds. Tail emissions continue to reward LPs after the initial reward emissions decayed below a certain threshold. The tail emissions rate can be nudged by at most 1 basis point per epoch to a maximum of 100 basis points or to a minimum of 1 basis point.Undeclared Multisig
PoolFactorysetVoterSets a new voter for the pool factory and will set it for all future pools.Voter contract: 0x41C914ee0c7E1A5edCD0295623e6dC557B5aBf3C
PoolFactorysetSinkConverterSets a new sinkConverter for the pool factory for all future deployed pools.Contract: 0x585Af0b397AC42dbeF7f18395426BF878634f18D
PoolFactorysetPauserSets a pauser for all pools deployed with this factory. The pauser can halt the swaps for these pools.Undeclared Multisig
PoolFactorysetPauseStateSets or removes the paused state of the factory. This affects swaps.Undeclared Multisig
PoolFactorysetFeeManagerDesignates a new address to be the FeeManager. The FeeManager can call setFee and setCustomFee.Undeclared Multisig
PoolFactorysetFeeSets a new fee for all future deployed fees, within a maximum of 100 basis points.Undeclared Multisig
PoolFactorysetCustomFeeSets a custom fee for a specific pool that was deployed through the factory.Undeclared Multisig
PoolsetNameSet a name for the pool.Emergency Council referenced from Voter contract
PoolsetSymbolSet a symbol for the pool.Emergency Council referenced from Voter contract
SinkDrainrenounceOwnershipThe function allows the permission owner to renounce ownership over all permissioned functions in this contract.0x0 (renounced)
SinkDraintransferOwnershipThe function allows the permission owner to transfer ownership over all permissioned functions in this contract.0x0 (renounced)
SinkDrainmintThe function was used to mint to sinkManager so that it can provide all the liquidity.0x0 (renounced)
SinkManagerrenounceOwnershipThe function allows the permission owner to renounce ownership over all permissioned functions in this contract.Undeclared Multisig
SinkManagertransferOwnershipThe function allows the permission owner to transfer ownership over all permissioned functions in this contract.Undeclared Multisig
SinkManagersetOwnedTokenIdDeposit all SinkDrain tokens to gauge to earn 100% rewards.0x0 (renounced)
SinkManagersetupSinkDrainInitial setup of the ownedTokenId, the v1 veNFT which votes for the SinkDrain.0x0 (renounced)
VELOsetMinterThis function allows to set the minter for the V2 VELO token.Minter contract: 0x6dc9E1C04eE59ed3531d73a72256C0da46D10982
VELOsetSinkManagerThis function allows to set the SinkManager for the VELO V2 token.0x0 (renounced)
VELOmintThe mint function allows the permission owner to mint new VELO V2 tokens into existence.Minter contract: 0x6dc9E1C04eE59ed3531d73a72256C0da46D10982
VotersetGovernorSets a new governor. Governor alone owns the permission to call setEpochGovernor, setMaxVotingNum, whitelistToken and whitelistNFT on the Voter contract.Undeclared Multisig
VotersetEpochGovernorSets a new epoch-based governor. Epoch governor can call nudge on Minter contract to modify the tail emission rate by at most 1 basis point per epoch to a maximum of 100 basis points or to a minimum of 1 basis point.Undeclared Multisig
VotersetEmergencyCouncilSets a new emergency council which can call killGauge and reviveGauge. See Upgradability section for more details.Emergency Council
VotersetMaxVotingNumSets a maximum number of pools a user can vote for. Code enforces that this number is not below 10.Undeclared Multisig
VoterwhitelistTokenWhitelists (or unwhitelists) a token for use in bribes. If not whitelisted, no gauge can be created for a pool and thus the pool cannot participate in the reward distribution as it cannot receive votes.Undeclared Multisig
VoterwhitelistNFTWhitelists or unwhitelists a Ve Token for voting in the last hour prior to epoch flip, all regular veNFTs cannot vote during this period. The voting power for whitelisted veNFT is the regular voting power: IVotingEscrow(ve).balanceOfNFT(_tokenId).Undeclared Multisig
VoterkillGaugeKills a gauge, preventing new emissions and deposits, but allowing withdrawals (see "Notes regarding the killing of a gauge").Emergency Council
VoterreviveGaugeRevives a killed gauge, allowing it to receive emissions and deposits again (see "Notes regarding the killing of a gauge").Emergency Council
VotingEscrowcreateManagedLockForManager can create a managed ve position for a user (a permanent lock).Undeclared Multisig
VotingEscrowdepositManagedDelegates balance to managed NFT, re-locking NFTs to maximum lock time upon withdrawal.Voter contract: 0x41C914ee0c7E1A5edCD0295623e6dC557B5aBf3C
VotingEscrowwithdrawManagedWithdraws balance from managed NFT, re-locking NFT to the maximum lock time.Voter contract: 0x41C914ee0c7E1A5edCD0295623e6dC557B5aBf3C
VotingEscrowsetAllowedManagerAllows one address to call createManagedLockFor() that is not the governor.Undeclared Multisig
VotingEscrowsetManagedStateSets Managed NFT state. Inactive NFTs cannot be deposited into.Emergency Council
VotingEscrowsetManagedStateSets Managed NFT state. Inactive NFTs cannot be deposited into.Emergency Council or governor (Undeclared Multisig)
VotingEscrowsetVoterAndDistributorSets Voter and distributor reference in this contract.Voter contract: 0x41C914ee0c7E1A5edCD0295623e6dC557B5aBf3C
VotingEscrowvotingSets voted status for _tokenId to true or false.Voter contract: 0x41C914ee0c7E1A5edCD0295623e6dC557B5aBf3C
RestrictedTeamsetArtProxyThe function was used to mint to sinkManager so that it can provide all the liquidity.Factory registry owner (Undeclared Multisig)
SplittertoggleSplitToggles split for a specific address or address(0) to enable/disable for all.Factory registry owner (Undeclared Multisig)
Registry (RelayFactoryRegistry)approveApproves the given address.Undeclared Multisig
Registry (RelayFactoryRegistry)unapproveRevokes approval from the given address.Undeclared Multisig
Registry (KeeperRegistry)approveApproves the given address.Undeclared Multisig
Registry (KeeperRegistry)unapproveRevokes approval from the given address.Undeclared Multisig
Registry (OptimizerRegistry)approveApproves the given address.Undeclared Multisig
Registry (OptimizerRegistry)unapproveRevokes approval from the given address.Undeclared Multisig
CLGaugeFactorysetNotifyAdminSets the notifyAdmin value on gauge factory.Undeclared Multisig
CLGaugeFactorysetNonfungiblePositionManagerSets Nonfungible Position Manager, callable only once.0x0 (renounced)
CLGaugeFactorycreateGaugeAllows the creation of a gauge, callable only by the voter contract.Voter contract: 0x41C914ee0c7E1A5edCD0295623e6dC557B5aBf3C
CL GaugegetRewardRetrieves rewards for all tokens owned by an account.Voter contract: 0x41C914ee0c7E1A5edCD0295623e6dC557B5aBf3C
CL GaugenotifyRewardWithoutClaimNotifies gauge of gauge rewards without distributing fees.Notify admin: Undeclared Multisig
CL GaugenotifyRewardAmountNotifies gauge of gauge rewards.Voter contract: 0x41C914ee0c7E1A5edCD0295623e6dC557B5aBf3C
NonfungiblePositionManagersetTokenDescriptorSets a new token descriptor.Undeclared Multisig
NonfungiblePositionManagersetOwnerSets a new owner for the contract. The only permission the owner has is to change the token descriptor.Undeclared Multisig
CLFactorysetOwnerSets a new owner for the pool factory. The only permission the owner role has on this contract is to call enableTickSpacing which enables a certain tick spacing with a default associated fee.Undeclared Multisig
CLFactorysetSwapFeeManagerSwapFeeManager can set a new SwapFeeManager. The SwapFeeManager can call setSwapFeeModule.Undeclared Multisig
CLFactorysetUnstakedFeeManagerThe UnstakedFeeManager can set a new UnstakedFeeManager. The UnstakedFeeManager can call setUnstakedFeeModule.Undeclared Multisig
CLFactorysetSwapFeeModuleSets a new swap fee module for all the pools deployed by the factory. This fee module indicates the fee for a certain swap based on the pool. The fee is capped to 0.1% max irrespective of the fee module.Undeclared Multisig
CLFactorysetUnstakedFeeModuleUpdates the unstaked fee module for all the pools deployed by the factory. This fee module indicates the fee for unstaking based on the pool. The fee is capped to 1% max irrespective of the fee module.Undeclared Multisig
CLFactorysetDefaultUnstakedFeeUpdates the defaultUnstakedFee of the factory.Undeclared Multisig
CLFactoryenableTickSpacingEnables a specific tickSpacing for pools deployed from the factory.Undeclared Multisig
CustomSwapFeeModulesetCustomFeeSets a custom fee between 0 and a max fee for a specific pool.SwapFeeManager (Undeclared Multisig)
CustomUnstakedFeeModulesetCustomFeeSets a custom fee between 0 and a max fee for a specified pool.UnstakedFeeManager (Undeclared Multisig)