diff --git a/contracts/src/v0.8/ccip/pools/GHO/UpgradeableLockReleaseTokenPool.sol b/contracts/src/v0.8/ccip/pools/GHO/UpgradeableLockReleaseTokenPool.sol index bd46601d55..21bd2df50d 100644 --- a/contracts/src/v0.8/ccip/pools/GHO/UpgradeableLockReleaseTokenPool.sol +++ b/contracts/src/v0.8/ccip/pools/GHO/UpgradeableLockReleaseTokenPool.sol @@ -21,6 +21,9 @@ import {UpgradeableTokenPool} from "./UpgradeableTokenPool.sol"; /// - Implementation of Initializable to allow upgrades /// - Move of allowlist and router definition to initialization stage /// - Addition of a bridge limit to regulate the maximum amount of tokens that can be transferred out (burned/locked) +/// - Increment bridged amount on transferLiquidity, reverts if amount + current bridged > bridge limit +/// - Increment bridged amount on provideLiquidity, reverts if amount + current bridged > bridge limit +/// - Decrement bridged amount on withdrawLiquidity, reverts if amount > gho.balanceOf(this) /// @notice Token pool used for tokens on their native chain. This uses a lock and release mechanism. /// Because of lock/unlock requiring liquidity, this pool contract also has function to add and remove @@ -202,10 +205,13 @@ contract UpgradeableLockReleaseTokenPool is Initializable, UpgradeableTokenPool, /// @notice Adds liquidity to the pool. The tokens should be approved first. /// @param amount The amount of liquidity to provide. + /// @dev amount being added + currentBridged needs to be within the bridge limit, otherwise increase bridge limit first function provideLiquidity(uint256 amount) external { if (!i_acceptLiquidity) revert LiquidityNotAccepted(); if (s_rebalancer != msg.sender) revert Unauthorized(msg.sender); + if ((s_currentBridged += amount) > s_bridgeLimit) revert BridgeLimitExceeded(s_bridgeLimit); + i_token.safeTransferFrom(msg.sender, address(this), amount); emit LiquidityAdded(msg.sender, amount); } @@ -215,6 +221,8 @@ contract UpgradeableLockReleaseTokenPool is Initializable, UpgradeableTokenPool, function withdrawLiquidity(uint256 amount) external { if (s_rebalancer != msg.sender) revert Unauthorized(msg.sender); + s_currentBridged -= amount; + if (i_token.balanceOf(address(this)) < amount) revert InsufficientLiquidity(); i_token.safeTransfer(msg.sender, amount); emit LiquidityRemoved(msg.sender, amount); @@ -229,11 +237,14 @@ contract UpgradeableLockReleaseTokenPool is Initializable, UpgradeableTokenPool, /// changing which pool CCIP uses, to ensure both pools can operate. Then the pool should be changed in the /// TokenAdminRegistry, which will activate the new pool. All new transactions will use the new pool and its /// liquidity. Finally, the remaining liquidity can be transferred to the new pool using this function one more time. + /// @dev amount being added + currentBridged needs to be within the bridge limit, otherwise increase bridge limit first /// @param from The address of the old pool. /// @param amount The amount of liquidity to transfer. function transferLiquidity(address from, uint256 amount) external onlyOwner { UpgradeableLockReleaseTokenPool(from).withdrawLiquidity(amount); + if ((s_currentBridged += amount) > s_bridgeLimit) revert BridgeLimitExceeded(s_bridgeLimit); + emit LiquidityTransferred(from, amount); } } diff --git a/contracts/src/v0.8/ccip/pools/GHO/diffs/UpgradeableLockReleaseTokenPool_diff.md b/contracts/src/v0.8/ccip/pools/GHO/diffs/UpgradeableLockReleaseTokenPool_diff.md index 01d1b94fb1..80fb9c67d3 100644 --- a/contracts/src/v0.8/ccip/pools/GHO/diffs/UpgradeableLockReleaseTokenPool_diff.md +++ b/contracts/src/v0.8/ccip/pools/GHO/diffs/UpgradeableLockReleaseTokenPool_diff.md @@ -1,9 +1,9 @@ ```diff diff --git a/src/v0.8/ccip/pools/LockReleaseTokenPool.sol b/src/v0.8/ccip/pools/GHO/UpgradeableLockReleaseTokenPool.sol -index ecc28a14dd..bd46601d55 100644 +index ecc28a14dd..21bd2df50d 100644 --- a/src/v0.8/ccip/pools/LockReleaseTokenPool.sol +++ b/src/v0.8/ccip/pools/GHO/UpgradeableLockReleaseTokenPool.sol -@@ -1,25 +1,41 @@ +@@ -1,25 +1,44 @@ // SPDX-License-Identifier: BUSL-1.1 -pragma solidity 0.8.24; +pragma solidity ^0.8.0; @@ -35,6 +35,9 @@ index ecc28a14dd..bd46601d55 100644 +/// - Implementation of Initializable to allow upgrades +/// - Move of allowlist and router definition to initialization stage +/// - Addition of a bridge limit to regulate the maximum amount of tokens that can be transferred out (burned/locked) ++/// - Increment bridged amount on transferLiquidity, reverts if amount + current bridged > bridge limit ++/// - Increment bridged amount on provideLiquidity, reverts if amount + current bridged > bridge limit ++/// - Decrement bridged amount on withdrawLiquidity, reverts if amount > gho.balanceOf(this) /// @notice Token pool used for tokens on their native chain. This uses a lock and release mechanism. /// Because of lock/unlock requiring liquidity, this pool contract also has function to add and remove @@ -54,7 +57,7 @@ index ecc28a14dd..bd46601d55 100644 event LiquidityTransferred(address indexed from, uint256 amount); -@@ -33,30 +49,69 @@ contract LockReleaseTokenPool is TokenPool, ILiquidityContainer, ITypeAndVersion +@@ -33,30 +52,69 @@ contract LockReleaseTokenPool is TokenPool, ILiquidityContainer, ITypeAndVersion /// @notice The address of the rebalancer. address internal s_rebalancer; @@ -133,7 +136,7 @@ index ecc28a14dd..bd46601d55 100644 } /// @notice Release tokens from the pool to the recipient -@@ -64,11 +119,18 @@ contract LockReleaseTokenPool is TokenPool, ILiquidityContainer, ITypeAndVersion +@@ -64,11 +122,18 @@ contract LockReleaseTokenPool is TokenPool, ILiquidityContainer, ITypeAndVersion function releaseOrMint( Pool.ReleaseOrMintInV1 calldata releaseOrMintIn ) external virtual override returns (Pool.ReleaseOrMintOutV1 memory) { @@ -154,7 +157,7 @@ index ecc28a14dd..bd46601d55 100644 // Release to the recipient getToken().safeTransfer(releaseOrMintIn.receiver, localAmount); -@@ -79,9 +141,7 @@ contract LockReleaseTokenPool is TokenPool, ILiquidityContainer, ITypeAndVersion +@@ -79,9 +144,7 @@ contract LockReleaseTokenPool is TokenPool, ILiquidityContainer, ITypeAndVersion } /// @inheritdoc IERC165 @@ -165,7 +168,7 @@ index ecc28a14dd..bd46601d55 100644 return interfaceId == type(ILiquidityContainer).interfaceId || super.supportsInterface(interfaceId); } -@@ -93,12 +153,47 @@ contract LockReleaseTokenPool is TokenPool, ILiquidityContainer, ITypeAndVersion +@@ -93,12 +156,47 @@ contract LockReleaseTokenPool is TokenPool, ILiquidityContainer, ITypeAndVersion /// @notice Sets the LiquidityManager address. /// @dev Only callable by the owner. @@ -216,18 +219,23 @@ index ecc28a14dd..bd46601d55 100644 /// @notice Checks if the pool can accept liquidity. /// @return true if the pool can accept liquidity, false otherwise. function canAcceptLiquidity() external view returns (bool) { -@@ -107,9 +202,7 @@ contract LockReleaseTokenPool is TokenPool, ILiquidityContainer, ITypeAndVersion +@@ -107,23 +205,24 @@ contract LockReleaseTokenPool is TokenPool, ILiquidityContainer, ITypeAndVersion /// @notice Adds liquidity to the pool. The tokens should be approved first. /// @param amount The amount of liquidity to provide. - function provideLiquidity( - uint256 amount - ) external { ++ /// @dev amount being added + currentBridged needs to be within the bridge limit, otherwise increase bridge limit first + function provideLiquidity(uint256 amount) external { if (!i_acceptLiquidity) revert LiquidityNotAccepted(); if (s_rebalancer != msg.sender) revert Unauthorized(msg.sender); -@@ -119,9 +212,7 @@ contract LockReleaseTokenPool is TokenPool, ILiquidityContainer, ITypeAndVersion ++ if ((s_currentBridged += amount) > s_bridgeLimit) revert BridgeLimitExceeded(s_bridgeLimit); ++ + i_token.safeTransferFrom(msg.sender, address(this), amount); + emit LiquidityAdded(msg.sender, amount); + } /// @notice Removed liquidity to the pool. The tokens will be sent to msg.sender. /// @param amount The amount of liquidity to remove. @@ -237,13 +245,23 @@ index ecc28a14dd..bd46601d55 100644 + function withdrawLiquidity(uint256 amount) external { if (s_rebalancer != msg.sender) revert Unauthorized(msg.sender); ++ s_currentBridged -= amount; ++ if (i_token.balanceOf(address(this)) < amount) revert InsufficientLiquidity(); -@@ -141,7 +232,7 @@ contract LockReleaseTokenPool is TokenPool, ILiquidityContainer, ITypeAndVersion + i_token.safeTransfer(msg.sender, amount); + emit LiquidityRemoved(msg.sender, amount); +@@ -138,10 +237,13 @@ contract LockReleaseTokenPool is TokenPool, ILiquidityContainer, ITypeAndVersion + /// changing which pool CCIP uses, to ensure both pools can operate. Then the pool should be changed in the + /// TokenAdminRegistry, which will activate the new pool. All new transactions will use the new pool and its + /// liquidity. Finally, the remaining liquidity can be transferred to the new pool using this function one more time. ++ /// @dev amount being added + currentBridged needs to be within the bridge limit, otherwise increase bridge limit first /// @param from The address of the old pool. /// @param amount The amount of liquidity to transfer. function transferLiquidity(address from, uint256 amount) external onlyOwner { - LockReleaseTokenPool(from).withdrawLiquidity(amount); + UpgradeableLockReleaseTokenPool(from).withdrawLiquidity(amount); ++ ++ if ((s_currentBridged += amount) > s_bridgeLimit) revert BridgeLimitExceeded(s_bridgeLimit); emit LiquidityTransferred(from, amount); } diff --git a/contracts/src/v0.8/ccip/test/pools/GHO/GhoTokenPoolEthereum.t.sol b/contracts/src/v0.8/ccip/test/pools/GHO/GhoTokenPoolEthereum.t.sol index 6fa121b14d..bdc84be07a 100644 --- a/contracts/src/v0.8/ccip/test/pools/GHO/GhoTokenPoolEthereum.t.sol +++ b/contracts/src/v0.8/ccip/test/pools/GHO/GhoTokenPoolEthereum.t.sol @@ -422,16 +422,26 @@ contract GhoTokenPoolEthereum_canAcceptLiquidity is GhoTokenPoolEthereumSetup { } contract GhoTokenPoolEthereum_provideLiquidity is GhoTokenPoolEthereumSetup { + error BridgeLimitExceeded(uint256 limit); + function testFuzz_ProvideLiquiditySuccess(uint256 amount) public { vm.assume(amount < type(uint128).max); + uint256 bridgedAmount = s_ghoTokenPool.getCurrentBridgedAmount(); + uint256 balancePre = s_token.balanceOf(OWNER); s_token.approve(address(s_ghoTokenPool), amount); + if (amount > s_ghoTokenPool.getBridgeLimit()) { + vm.expectRevert(abi.encodeWithSelector(BridgeLimitExceeded.selector, s_ghoTokenPool.getBridgeLimit())); + } s_ghoTokenPool.provideLiquidity(amount); - assertEq(s_token.balanceOf(OWNER), balancePre - amount); - assertEq(s_token.balanceOf(address(s_ghoTokenPool)), amount); + if (amount < s_ghoTokenPool.getBridgeLimit()) { + assertEq(s_token.balanceOf(OWNER), balancePre - amount); + assertEq(s_token.balanceOf(address(s_ghoTokenPool)), amount); + assertEq(s_ghoTokenPool.getCurrentBridgedAmount(), bridgedAmount + amount); + } } // Reverts @@ -445,6 +455,11 @@ contract GhoTokenPoolEthereum_provideLiquidity is GhoTokenPoolEthereumSetup { function testFuzz_ExceedsAllowance(uint256 amount) public { vm.assume(amount > 0); + + changePrank(AAVE_DAO); + s_ghoTokenPool.setBridgeLimit(amount); + changePrank(OWNER); + vm.expectRevert(stdError.arithmeticError); s_ghoTokenPool.provideLiquidity(amount); } @@ -459,7 +474,7 @@ contract GhoTokenPoolEthereum_provideLiquidity is GhoTokenPoolEthereumSetup { contract GhoTokenPoolEthereum_withdrawalLiquidity is GhoTokenPoolEthereumSetup { function testFuzz_WithdrawalLiquiditySuccess(uint256 amount) public { - vm.assume(amount < type(uint128).max); + amount = bound(amount, 1, s_ghoTokenPool.getBridgeLimit()); uint256 balancePre = s_token.balanceOf(OWNER); s_token.approve(address(s_ghoTokenPool), amount); @@ -481,6 +496,11 @@ contract GhoTokenPoolEthereum_withdrawalLiquidity is GhoTokenPoolEthereumSetup { function testInsufficientLiquidityReverts() public { uint256 maxUint128 = 2 ** 128 - 1; + + changePrank(AAVE_DAO); + s_ghoTokenPool.setBridgeLimit(maxUint128); + changePrank(OWNER); + s_token.approve(address(s_ghoTokenPool), maxUint128); s_ghoTokenPool.provideLiquidity(maxUint128); @@ -493,6 +513,61 @@ contract GhoTokenPoolEthereum_withdrawalLiquidity is GhoTokenPoolEthereumSetup { } } +contract GhoTokenPoolEthereum_transferLiquidity is GhoTokenPoolEthereumSetup { + UpgradeableLockReleaseTokenPool internal s_oldLockReleaseTokenPool; + + uint256 internal s_amount = 100_000_000e18; + + error OnlyCallableByOwner(); + error BridgeLimitExceeded(uint256 limit); + + function setUp() public virtual override { + super.setUp(); + + s_oldLockReleaseTokenPool = UpgradeableLockReleaseTokenPool( + _deployUpgradeableLockReleaseTokenPool( + address(s_token), + address(s_mockRMN), + address(s_sourceRouter), + AAVE_DAO, + INITIAL_BRIDGE_LIMIT, + PROXY_ADMIN + ) + ); + deal(address(s_token), address(s_oldLockReleaseTokenPool), s_amount); + // write to currentBridged + vm.store(address(s_oldLockReleaseTokenPool), bytes32(uint256(12)), bytes32(s_amount)); + changePrank(AAVE_DAO); + } + + function testFuzz_TransferLiquidity(uint256 amount) public { + amount = bound(amount, 1, s_amount); + + s_oldLockReleaseTokenPool.setRebalancer(address(s_ghoTokenPool)); + uint256 bridgedAmount = s_ghoTokenPool.getCurrentBridgedAmount(); + + if (amount > s_ghoTokenPool.getBridgeLimit()) { + vm.expectRevert(abi.encodeWithSelector(BridgeLimitExceeded.selector, s_ghoTokenPool.getBridgeLimit())); + } + s_ghoTokenPool.transferLiquidity(address(s_oldLockReleaseTokenPool), amount); + + if (amount < s_ghoTokenPool.getBridgeLimit()) { + assertEq(s_token.balanceOf(address(s_ghoTokenPool)), amount); + assertEq(s_token.balanceOf(address(s_oldLockReleaseTokenPool)), s_amount - amount); + assertEq(s_ghoTokenPool.getCurrentBridgedAmount(), bridgedAmount + amount); + } + } + + // Reverts + + function test_UnauthorizedReverts() public { + changePrank(STRANGER); + vm.expectRevert(OnlyCallableByOwner.selector); + + s_ghoTokenPool.transferLiquidity(address(1), 1); + } +} + contract GhoTokenPoolEthereum_supportsInterface is GhoTokenPoolEthereumSetup { function testSupportsInterfaceSuccess() public view { assertTrue(s_ghoTokenPool.supportsInterface(type(ILiquidityContainer).interfaceId));