Skip to content

Commit

Permalink
Add transferLiquidity function back in
Browse files Browse the repository at this point in the history
  • Loading branch information
jhweintraub committed Aug 12, 2024
1 parent f4568e0 commit a6c6a4c
Show file tree
Hide file tree
Showing 5 changed files with 88 additions and 20 deletions.
13 changes: 7 additions & 6 deletions contracts/gas-snapshots/ccip.gas-snapshot
Original file line number Diff line number Diff line change
Expand Up @@ -942,17 +942,18 @@ TokenProxy_getFee:test_GetFeeGasShouldBeZero_Revert() (gas: 16827)
TokenProxy_getFee:test_GetFeeInvalidToken_Revert() (gas: 12658)
TokenProxy_getFee:test_GetFeeNoDataAllowed_Revert() (gas: 15849)
TokenProxy_getFee:test_GetFee_Success() (gas: 86702)
USDCTokenPoolAltMechanismTests:test_LockOrBurn_PrimaryMechanism_Success() (gas: 136896)
USDCTokenPoolAltMechanismTests:test_LockOrBurn_WhileMigrationPause_Revert() (gas: 98179)
USDCTokenPoolAltMechanismTests:test_LockOrBurn_altMechanism_then_switchToPrimary_Success() (gas: 209580)
USDCTokenPoolAltMechanismTests:test_LockOrBurn_onAltMechanism_Success() (gas: 143718)
USDCTokenPoolAltMechanismTests:test_LockOrBurn_PrimaryMechanism_Success() (gas: 135152)
USDCTokenPoolAltMechanismTests:test_LockOrBurn_WhileMigrationPause_Revert() (gas: 97433)
USDCTokenPoolAltMechanismTests:test_LockOrBurn_altMechanism_then_switchToPrimary_Success() (gas: 207691)
USDCTokenPoolAltMechanismTests:test_LockOrBurn_onAltMechanism_Success() (gas: 143689)
USDCTokenPoolAltMechanismTests:test_MintOrRelease_OnAltMechanism_Success() (gas: 212557)
USDCTokenPoolAltMechanismTests:test_MintOrRelease_altMechanism_then_switchToPrimary_Success() (gas: 423280)
USDCTokenPoolAltMechanismTests:test_MintOrRelease_incomingMessageWithPrimaryMechanism() (gas: 270078)
USDCTokenPoolAltMechanismTests:test_withdrawLiquidity_Success() (gas: 118475)
USDCTokenPoolMigrationTests:test_burnLockedUSDC_invalidPermissions_Revert() (gas: 39951)
USDCTokenPoolMigrationTests:test_lockOrBurn_then_BurnInCCTPMigration_Success() (gas: 246269)
USDCTokenPoolMigrationTests:test_proposeCCTPMigration_withInvalidSelector_Revert() (gas: 35556)
USDCTokenPoolMigrationTests:test_lockOrBurn_then_BurnInCCTPMigration_Success() (gas: 246296)
USDCTokenPoolMigrationTests:test_proposeCCTPMigration_withInvalidSelector_Revert() (gas: 35644)
USDCTokenPoolMigrationTests:test_transferLiquidity_Success() (gas: 152643)
USDCTokenPool__validateMessage:test_ValidateInvalidMessage_Revert() (gas: 25290)
USDCTokenPool_lockOrBurn:test_CallerIsNotARampOnRouter_Revert() (gas: 35322)
USDCTokenPool_lockOrBurn:test_LockOrBurnWithAllowList_Revert() (gas: 30073)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
pragma solidity ^0.8.0;

interface IMigratableMultiMechanismTokenPool {
function withdrawLiquidity(uint64 remoteChainSelector, uint256 amount) external;
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@ pragma solidity 0.8.24;

import {ILiquidityContainer} from "../../../liquiditymanager/interfaces/ILiquidityContainer.sol";
import {ITokenMessenger} from "../USDC/ITokenMessenger.sol";
import {IMigratableMultiMechanismTokenPool} from "./IMigratableMultiMechanismTokenPool.sol";

import {Pool} from "../../libraries/Pool.sol";

import {TokenPool} from "../TokenPool.sol";
import {USDCTokenPool} from "../USDC/USDCTokenPool.sol";
import {MultiMechanismPoolManager} from "./MultiMechanismPoolManager.sol";
Expand All @@ -28,6 +28,8 @@ contract MultiMechanismUSDCTokenPool is USDCTokenPool, MultiMechanismPoolManager
/// balanceOf(pool) on home chain >= sum(totalSupply(mint/burn "wrapped" token) on all remote chains) should always hold
address internal s_rebalancer;

event LiquidityTransferred(address indexed from, uint64 indexed remoteChainSelector, uint256 amount);

error LanePausedForCCTPMigration(uint64 remoteChainSelector);

constructor(
Expand All @@ -48,7 +50,11 @@ contract MultiMechanismUSDCTokenPool is USDCTokenPool, MultiMechanismPoolManager
{
// If the alternative mechanism (L/R) for chains which have it enabled
if (shouldUseAltMechForOutgoingMessage(lockOrBurnIn.remoteChainSelector)) {
return _altMechOutgoingMessage(lockOrBurnIn);
if (s_proposedUSDCMigrationChain != 0 && s_proposedUSDCMigrationChain == lockOrBurnIn.remoteChainSelector) {
revert LanePausedForCCTPMigration(s_proposedUSDCMigrationChain);
} else {
return _altMechOutgoingMessage(lockOrBurnIn);
}
} else {
// Otherwise, use native CCTP functionality
return super.lockOrBurn(lockOrBurnIn);
Expand Down Expand Up @@ -107,17 +113,6 @@ contract MultiMechanismUSDCTokenPool is USDCTokenPool, MultiMechanismPoolManager
return Pool.LockOrBurnOutV1({destTokenAddress: getRemoteToken(lockOrBurnIn.remoteChainSelector), destPoolData: ""});
}

/// @notice Circle's migration policies requires a pausable supply-lock during the migration process.
/// This override adds an additional condition to valid lockOrBurns that ensures the destination chain
/// is not currently undergoing a migration by reverting, as well as all other validations
function _validateLockOrBurn(Pool.LockOrBurnInV1 memory lockOrBurnIn) internal override {
if (s_proposedUSDCMigrationChain != 0 && s_proposedUSDCMigrationChain == lockOrBurnIn.remoteChainSelector) {
revert LanePausedForCCTPMigration(s_proposedUSDCMigrationChain);
}

super._validateLockOrBurn(lockOrBurnIn);
}

/// @notice Gets LiquidityManager, can be address(0) if none is configured.
/// @return The current liquidity manager.
function getRebalancer() external view returns (address) {
Expand Down Expand Up @@ -158,4 +153,23 @@ contract MultiMechanismUSDCTokenPool is USDCTokenPool, MultiMechanismPoolManager
i_token.safeTransfer(msg.sender, amount);
emit ILiquidityContainer.LiquidityRemoved(msg.sender, amount);
}

/// @notice This function can be used to transfer liquidity from an older version of the pool to this pool. To do so
/// this pool will have to be set as the rebalancer in the older version of the pool. This allows it to transfer the
/// funds in the old pool to the new pool.
/// @dev When upgrading a LockRelease pool, this function can be called at the same time as the pool is changed in the
/// TokenAdminRegistry. This allows for a smooth transition of both liquidity and transactions to the new pool.
/// Alternatively, when no multicall is available, a portion of the funds can be transferred to the new pool before
/// 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.
/// @param from The address of the old pool.
/// @param amount The amount of liquidity to transfer.
function transferLiquidity(address from, uint64 remoteChainSelector, uint256 amount) external onlyOwner {
IMigratableMultiMechanismTokenPool(from).withdrawLiquidity(remoteChainSelector, amount);

s_lockedTokensByChainSelector[remoteChainSelector] += amount;

emit LiquidityTransferred(from, remoteChainSelector, amount);
}
}
2 changes: 1 addition & 1 deletion contracts/src/v0.8/ccip/pools/TokenPool.sol
Original file line number Diff line number Diff line change
Expand Up @@ -157,7 +157,7 @@ abstract contract TokenPool is IPoolV1, OwnerIsCreator {
/// @param lockOrBurnIn The input to validate.
/// @dev This function should always be called before executing a lock or burn. Not doing so would allow
/// for various exploits.
function _validateLockOrBurn(Pool.LockOrBurnInV1 memory lockOrBurnIn) internal virtual {
function _validateLockOrBurn(Pool.LockOrBurnInV1 memory lockOrBurnIn) internal {
if (!isSupportedToken(lockOrBurnIn.localToken)) revert InvalidToken(lockOrBurnIn.localToken);
if (IRMN(i_rmnProxy).isCursed(bytes16(uint128(lockOrBurnIn.remoteChainSelector)))) revert CursedByRMN();
_checkAllowList(lockOrBurnIn.originalSender);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ contract USDCTokenPoolSetup is BaseTest {
Router internal s_router;

MultiMechanismUSDCTokenPool internal s_usdcTokenPool;
MultiMechanismUSDCTokenPool internal s_usdcTokenPoolTransferLiquidity;
address[] internal s_allowedList;

function setUp() public virtual override {
Expand All @@ -71,6 +72,9 @@ contract USDCTokenPoolSetup is BaseTest {
s_usdcTokenPool =
new MultiMechanismUSDCTokenPool(s_mockUSDC, s_token, new address[](0), address(s_mockRMN), address(s_router));

s_usdcTokenPoolTransferLiquidity =
new MultiMechanismUSDCTokenPool(s_mockUSDC, s_token, new address[](0), address(s_mockRMN), address(s_router));

usdcToken.grantMintAndBurnRoles(address(s_mockUSDC));
usdcToken.grantMintAndBurnRoles(address(s_usdcTokenPool));

Expand Down Expand Up @@ -592,4 +596,48 @@ contract USDCTokenPoolMigrationTests is USDCTokenPoolSetup {
vm.expectRevert(abi.encodeWithSelector(USDCBridgeMigrator.ExistingMigrationProposal.selector));
s_usdcTokenPool.burnLockedUSDC();
}

function test_transferLiquidity_Success() public {
// Set as the OWNER so we can provide liquidity
vm.startPrank(OWNER);
s_usdcTokenPoolTransferLiquidity.setRebalancer(OWNER);

s_token.approve(address(s_usdcTokenPoolTransferLiquidity), type(uint256).max);

uint256 liquidityAmount = 1e9;

// Provide 1000 USDC as liquidity
s_usdcTokenPoolTransferLiquidity.provideLiquidity(DEST_CHAIN_SELECTOR, liquidityAmount);

// Set the new token pool as the rebalancer
s_usdcTokenPoolTransferLiquidity.setRebalancer(address(s_usdcTokenPool));

vm.expectEmit();
emit ILiquidityContainer.LiquidityRemoved(address(s_usdcTokenPool), liquidityAmount);

vm.expectEmit();
emit MultiMechanismUSDCTokenPool.LiquidityTransferred(
address(s_usdcTokenPoolTransferLiquidity), DEST_CHAIN_SELECTOR, liquidityAmount
);

s_usdcTokenPool.transferLiquidity(address(s_usdcTokenPoolTransferLiquidity), DEST_CHAIN_SELECTOR, liquidityAmount);

assertEq(
s_usdcTokenPool.getLockedTokensForChain(DEST_CHAIN_SELECTOR),
liquidityAmount,
"Tokens locked for dest chain doesn't match expected amount in storage"
);

assertEq(
s_usdcTokenPoolTransferLiquidity.getLockedTokensForChain(DEST_CHAIN_SELECTOR),
0,
"Tokens locked for dest chain in old token pool doesn't match expected amount in storage"
);

assertEq(
s_token.balanceOf(address(s_usdcTokenPool)),
liquidityAmount,
"Liquidity amount of tokens should be new in new pool, but aren't"
);
}
}

0 comments on commit a6c6a4c

Please sign in to comment.