From 74c266c7a12a8c4c55bb1d0159ad22a73a88bab0 Mon Sep 17 00:00:00 2001 From: Roman Agureev Date: Fri, 6 Dec 2024 10:43:46 +0300 Subject: [PATCH 01/10] fix: decimals return (5.4) --- contracts/implementations/ChildGauge.vy | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/contracts/implementations/ChildGauge.vy b/contracts/implementations/ChildGauge.vy index 73521a8..6f585a8 100644 --- a/contracts/implementations/ChildGauge.vy +++ b/contracts/implementations/ChildGauge.vy @@ -796,11 +796,11 @@ def integrate_checkpoint() -> uint256: @view @external -def decimals() -> uint256: +def decimals() -> uint8: """ @notice Get the number of decimals for this token @dev Implemented as a view method to reduce gas costs - @return uint256 decimal places + @return uint8 decimal places """ return 18 From a4258365702f4094b4b590fe9e943d2c33209bf6 Mon Sep 17 00:00:00 2001 From: Roman Agureev Date: Fri, 6 Dec 2024 11:02:39 +0300 Subject: [PATCH 02/10] fix: recover_remaining reentrancy (5.10) --- contracts/implementations/ChildGauge.vy | 1 + 1 file changed, 1 insertion(+) diff --git a/contracts/implementations/ChildGauge.vy b/contracts/implementations/ChildGauge.vy index 6f585a8..34612fc 100644 --- a/contracts/implementations/ChildGauge.vy +++ b/contracts/implementations/ChildGauge.vy @@ -652,6 +652,7 @@ def deposit_reward_token(_reward_token: address, _amount: uint256, _epoch: uint2 @external +@nonreentrant("lock") def recover_remaining(_reward_token: address): """ @notice Recover reward token remaining after calculation errors. Helpful for small decimal tokens. From 34d771806351cae04a5a150d573e871f038c7bea Mon Sep 17 00:00:00 2001 From: Roman Agureev Date: Fri, 6 Dec 2024 11:15:06 +0300 Subject: [PATCH 03/10] fix: ERC20Detailed (7.1) --- contracts/implementations/ChildGauge.vy | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/contracts/implementations/ChildGauge.vy b/contracts/implementations/ChildGauge.vy index 34612fc..87a9e36 100644 --- a/contracts/implementations/ChildGauge.vy +++ b/contracts/implementations/ChildGauge.vy @@ -10,13 +10,11 @@ from vyper.interfaces import ERC20 +from vyper.interfaces import ERC20Detailed implements: ERC20 -interface ERC20Extended: - def symbol() -> String[32]: view - interface ERC1271: def isValidSignature(_hash: bytes32, _signature: Bytes[65]) -> bytes32: view @@ -157,7 +155,7 @@ def initialize(_lp_token: address, _root: address, _manager: address): self.voting_escrow = Factory(msg.sender).voting_escrow() - symbol: String[32] = ERC20Extended(_lp_token).symbol() + symbol: String[32] = ERC20Detailed(_lp_token).symbol() name: String[64] = concat("Curve.fi ", symbol, " Gauge Deposit") self.name = name From fc5cb1024e2c309fb1da08aed0a575dd95a0f2f3 Mon Sep 17 00:00:00 2001 From: Roman Agureev Date: Fri, 6 Dec 2024 11:40:22 +0300 Subject: [PATCH 04/10] fix: docs (7.4) --- contracts/implementations/ChildGauge.vy | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/contracts/implementations/ChildGauge.vy b/contracts/implementations/ChildGauge.vy index 87a9e36..e91e233 100644 --- a/contracts/implementations/ChildGauge.vy +++ b/contracts/implementations/ChildGauge.vy @@ -109,7 +109,7 @@ rewards_receiver: public(HashMap[address, address]) # reward token -> claiming address -> integral reward_integral_for: public(HashMap[address, HashMap[address, uint256]]) -# user -> [uint128 claimable amount][uint128 claimed amount] +# user -> token -> [uint128 claimable amount][uint128 claimed amount] claim_data: HashMap[address, HashMap[address, uint256]] working_balances: public(HashMap[address, uint256]) @@ -289,7 +289,7 @@ def _checkpoint_rewards(_user: address, _total_supply: uint256, _claim: bool, _r def _update_liquidity_limit(_user: address, _user_balance: uint256, _total_supply: uint256): """ @notice Calculate working balances to apply amplification of CRV production. - @dev https://resources.curve.fi/guides/boosting-your-crv-rewards#formula + @dev https://resources.curve.fi/reward-gauges/boosting-your-crv-rewards/#boost-info @param _user The user address @param _user_balance User's amount of liquidity (LP tokens) @param _total_supply Total amount of liquidity (LP tokens) From ef0d954c725c9c0114cb5462efff84f841563500 Mon Sep 17 00:00:00 2001 From: Roman Agureev Date: Fri, 6 Dec 2024 11:44:48 +0300 Subject: [PATCH 05/10] fix: pure (7.6) --- contracts/implementations/ChildGauge.vy | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/implementations/ChildGauge.vy b/contracts/implementations/ChildGauge.vy index e91e233..a4895fc 100644 --- a/contracts/implementations/ChildGauge.vy +++ b/contracts/implementations/ChildGauge.vy @@ -804,7 +804,7 @@ def decimals() -> uint8: return 18 -@view +@pure @external def version() -> String[8]: """ From c65b6455afd9c3a4b09ec329b34b1a70a5b6f638 Mon Sep 17 00:00:00 2001 From: Roman Agureev Date: Fri, 6 Dec 2024 11:59:58 +0300 Subject: [PATCH 06/10] fix: add events (7.10) --- contracts/implementations/ChildGauge.vy | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/contracts/implementations/ChildGauge.vy b/contracts/implementations/ChildGauge.vy index a4895fc..ad1e1b9 100644 --- a/contracts/implementations/ChildGauge.vy +++ b/contracts/implementations/ChildGauge.vy @@ -34,6 +34,17 @@ event Withdraw: provider: indexed(address) value: uint256 +event AddReward: + reward: address + index: uint256 + +event SetDistributor: + reward: address + distributor: address + +event SetKilled: + is_killed: bool + event UpdateLiquidityLimit: user: indexed(address) original_balance: uint256 @@ -152,6 +163,7 @@ def initialize(_lp_token: address, _root: address, _manager: address): self.lp_token = _lp_token self.root_gauge = _root self.manager = _manager + log SetGaugeManager(_manager) self.voting_escrow = Factory(msg.sender).voting_escrow() @@ -686,6 +698,8 @@ def add_reward(_reward_token: address, _distributor: address): self.reward_data[_reward_token].distributor = _distributor self.reward_tokens[reward_count] = _reward_token self.reward_count = reward_count + 1 + log AddReward(_reward_token, reward_count) + log SetDistributor(_reward_token, _distributor) @external @@ -702,6 +716,7 @@ def set_reward_distributor(_reward_token: address, _distributor: address): assert _distributor != empty(address) self.reward_data[_reward_token].distributor = _distributor + log SetDistributor(_reward_token, _distributor) @external @@ -714,6 +729,7 @@ def set_killed(_is_killed: bool): assert msg.sender == FACTORY.owner() # dev: only owner self.is_killed = _is_killed + log SetKilled(_is_killed) @external From cf19a45a19074a7cfcdfd38b5f2a642d84121a58 Mon Sep 17 00:00:00 2001 From: Roman Agureev Date: Fri, 6 Dec 2024 12:08:17 +0300 Subject: [PATCH 07/10] fix: add natspec (7.11) --- contracts/implementations/ChildGauge.vy | 27 ++++++++++++++++++++----- 1 file changed, 22 insertions(+), 5 deletions(-) diff --git a/contracts/implementations/ChildGauge.vy b/contracts/implementations/ChildGauge.vy index ad1e1b9..08b6e74 100644 --- a/contracts/implementations/ChildGauge.vy +++ b/contracts/implementations/ChildGauge.vy @@ -151,6 +151,10 @@ root_gauge: public(address) @external def __init__(_factory: Factory): + """ + @notice Deployer + @param _factory Factory for deploying this gauge + """ self.lp_token = 0x000000000000000000000000000000000000dEaD FACTORY = _factory @@ -158,6 +162,12 @@ def __init__(_factory: Factory): @external def initialize(_lp_token: address, _root: address, _manager: address): + """ + @notice Initiailize contract created as proxy + @param _lp_token Token to lock in + @param _root Address of mirror Root gauge + @param _manager Manager of rewards for the gauge + """ assert self.lp_token == empty(address) # dev: already initialized self.lp_token = _lp_token @@ -361,6 +371,7 @@ def deposit(_value: uint256, _addr: address = msg.sender, _claim_rewards: bool = @dev Depositting also claims pending reward tokens @param _value Number of tokens to deposit @param _addr Address to deposit for + @param _claim_rewards Whether to claim already accrued rewards """ assert _addr != empty(address) # dev: cannot deposit for zero address self._checkpoint(_addr) @@ -434,11 +445,12 @@ def claim_rewards(_addr: address = msg.sender, _receiver: address = empty(addres @nonreentrant('lock') def transferFrom(_from: address, _to :address, _value: uint256) -> bool: """ - @notice Transfer tokens from one address to another. - @dev Transferring claims pending reward tokens for the sender and receiver - @param _from address The address which you want to send tokens from - @param _to address The address which you want to transfer to - @param _value uint256 the amount of tokens to be transferred + @notice Transfer tokens from one address to another. + @dev Transferring claims pending reward tokens for the sender and receiver + @param _from address The address which you want to send tokens from + @param _to address The address which you want to transfer to + @param _value uint256 the amount of tokens to be transferred + @return True if transfer passed """ _allowance: uint256 = self.allowance[_from][msg.sender] if _allowance != max_value(uint256): @@ -457,6 +469,7 @@ def transfer(_to: address, _value: uint256) -> bool: @dev Transferring claims pending reward tokens for the sender and receiver @param _to The address to transfer to. @param _value The amount to be transferred. + @return True if transfer passed """ self._transfer(msg.sender, _to, _value) @@ -794,6 +807,7 @@ def claimable_tokens(addr: address) -> uint256: """ @notice Get the number of claimable tokens per user @dev This function should be manually changed to "view" in the ABI + @param addr User to check for @return uint256 number of claimable tokens per user """ self._checkpoint(addr) @@ -805,6 +819,7 @@ def claimable_tokens(addr: address) -> uint256: def integrate_checkpoint() -> uint256: """ @notice Get the timestamp of the last checkpoint + @return Integral value """ return self.period_timestamp[self.period] @@ -825,6 +840,7 @@ def decimals() -> uint8: def version() -> String[8]: """ @notice Get the version of this gauge contract + @return Version in x.x.x format """ return VERSION @@ -834,5 +850,6 @@ def version() -> String[8]: def factory() -> Factory: """ @notice Get factory of this gauge + @return address of factory """ return FACTORY From c79570de2d7b567e58441896878fd1f056a3b71a Mon Sep 17 00:00:00 2001 From: Roman Agureev Date: Fri, 6 Dec 2024 12:41:47 +0300 Subject: [PATCH 08/10] fix: ERC20Detailed has 1 string length, rollback (7.1) --- contracts/implementations/ChildGauge.vy | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/contracts/implementations/ChildGauge.vy b/contracts/implementations/ChildGauge.vy index 08b6e74..4ddbb94 100644 --- a/contracts/implementations/ChildGauge.vy +++ b/contracts/implementations/ChildGauge.vy @@ -10,10 +10,11 @@ from vyper.interfaces import ERC20 -from vyper.interfaces import ERC20Detailed implements: ERC20 +interface ERC20Extended: + def symbol() -> String[32]: view interface ERC1271: def isValidSignature(_hash: bytes32, _signature: Bytes[65]) -> bytes32: view @@ -177,7 +178,7 @@ def initialize(_lp_token: address, _root: address, _manager: address): self.voting_escrow = Factory(msg.sender).voting_escrow() - symbol: String[32] = ERC20Detailed(_lp_token).symbol() + symbol: String[32] = ERC20Extended(_lp_token).symbol() name: String[64] = concat("Curve.fi ", symbol, " Gauge Deposit") self.name = name From a5996681b7238d9c041a242460a7a7d7f2877007 Mon Sep 17 00:00:00 2001 From: bout3fiddy <11488427+bout3fiddy@users.noreply.github.com> Date: Thu, 19 Dec 2024 12:05:55 +0100 Subject: [PATCH 09/10] additional audit fixes --- contracts/implementations/ChildGauge.vy | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/contracts/implementations/ChildGauge.vy b/contracts/implementations/ChildGauge.vy index 4ddbb94..af1d015 100644 --- a/contracts/implementations/ChildGauge.vy +++ b/contracts/implementations/ChildGauge.vy @@ -5,7 +5,7 @@ @license Copyright (c) Curve.Fi, 2020-2024 - all rights reserved @author Curve.Fi @notice Layer2/Cross-Chain Gauge -@custom:version 1.0.0 +@custom:version 1.1.0 """ @@ -17,7 +17,7 @@ interface ERC20Extended: def symbol() -> String[32]: view interface ERC1271: - def isValidSignature(_hash: bytes32, _signature: Bytes[65]) -> bytes32: view + def isValidSignature(_hash: bytes32, _signature: Bytes[65]) -> bytes4: view interface Factory: def owner() -> address: view @@ -80,9 +80,9 @@ MAX_REWARDS: constant(uint256) = 8 TOKENLESS_PRODUCTION: constant(uint256) = 40 WEEK: constant(uint256) = 604800 -VERSION: constant(String[8]) = "1.0.0" +VERSION: constant(String[8]) = "1.1.0" -EIP712_TYPEHASH: constant(bytes32) = keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)") +EIP712_TYPEHASH: constant(bytes32) = keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract,bytes32 salt)") EIP2612_TYPEHASH: constant(bytes32) = keccak256("Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)") ERC1271_MAGIC_VAL: constant(bytes32) = 0x1626ba7e00000000000000000000000000000000000000000000000000000000 @@ -96,6 +96,7 @@ allowance: public(HashMap[address, HashMap[address, uint256]]) name: public(String[64]) symbol: public(String[40]) +salt: public(bytes32) # ERC2612 DOMAIN_SEPARATOR: public(bytes32) @@ -183,6 +184,7 @@ def initialize(_lp_token: address, _root: address, _manager: address): self.name = name self.symbol = concat(symbol, "-gauge") + self.salt = block.prevhash self.period_timestamp[0] = block.timestamp self.DOMAIN_SEPARATOR = keccak256( @@ -191,7 +193,8 @@ def initialize(_lp_token: address, _root: address, _manager: address): keccak256(name), keccak256(VERSION), chain.id, - self + self, + self.salt, ) ) @@ -423,8 +426,8 @@ def withdraw(_value: uint256, _claim_rewards: bool = False, _receiver: address = ERC20(self.lp_token).transfer(_receiver, _value) - log Withdraw(msg.sender, _value) - log Transfer(msg.sender, empty(address), _value) + log Withdraw(msg.sender, _value) + log Transfer(msg.sender, empty(address), _value) @external @@ -539,7 +542,7 @@ def permit( ) if _owner.is_contract: sig: Bytes[65] = concat(_abi_encode(_r, _s), slice(convert(_v, bytes32), 31, 1)) - assert ERC1271(_owner).isValidSignature(digest, sig) == ERC1271_MAGIC_VAL # dev: invalid signature + assert convert(ERC1271(_owner).isValidSignature(digest, sig), bytes32) == ERC1271_MAGIC_VAL # dev: invalid signature else: assert ecrecover(digest, _v, _r, _s) == _owner # dev: invalid signature @@ -702,7 +705,7 @@ def add_reward(_reward_token: address, _distributor: address): @param _distributor Address permitted to fund this contract with the reward token """ assert msg.sender in [self.manager, FACTORY.owner()] # dev: only manager or factory admin - assert _reward_token != FACTORY.crv().address # dev: can not distinguish CRV reward from CRV emission + assert _reward_token not in [FACTORY.crv().address, self] # dev: can not distinguish CRV reward from CRV emission; do not use gauge token as reward token assert _distributor != empty(address) # dev: distributor cannot be zero address reward_count: uint256 = self.reward_count From 8abdaec48ee7b3a64bfc9c7ae9c622bb758dd521 Mon Sep 17 00:00:00 2001 From: bout3fiddy <11488427+bout3fiddy@users.noreply.github.com> Date: Thu, 9 Jan 2025 14:03:44 +0100 Subject: [PATCH 10/10] index reward token address in events --- contracts/implementations/ChildGauge.vy | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/contracts/implementations/ChildGauge.vy b/contracts/implementations/ChildGauge.vy index af1d015..01e9cd9 100644 --- a/contracts/implementations/ChildGauge.vy +++ b/contracts/implementations/ChildGauge.vy @@ -36,11 +36,11 @@ event Withdraw: value: uint256 event AddReward: - reward: address + reward: indexed(address) index: uint256 event SetDistributor: - reward: address + reward: indexed(address) distributor: address event SetKilled: