Skip to content

Commit

Permalink
Self serve migration (#796)
Browse files Browse the repository at this point in the history
This PR will add two new pools, one burnMint and one lockRelease. These
will be used as proxies for the existing <1.5 token pools when the lanes
upgrade to 1.5. Please see
`contracts/src/v0.8/ccip/test/legacy/BurnMintTokenPoolAndProxy.t.sol`
for a full step-by-step guide on how this upgrade works for both the
1.0/1.2 and the 1.4 pools.


Pools deployed after self serve is live will NOT use these proxies, but
rather the clean versions that do not implement the proxy behaviour.
Because of this, we only need proxies for the pools that are currently
live: burnMint and LockRelease. USDC will cleanly upgrade by deploying
new pools and doesn't require the proxy. All other pool flavours are not
live at the moment

---------

Co-authored-by: Justin Kaseman <[email protected]>
  • Loading branch information
RensR and justinkaseman authored May 6, 2024
1 parent 31dba11 commit 6b98596
Show file tree
Hide file tree
Showing 16 changed files with 7,694 additions and 1 deletion.
5 changes: 5 additions & 0 deletions .changeset/large-bottles-dress.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"ccip": patch
---

make pools backwards compatible proxies
5 changes: 5 additions & 0 deletions contracts/.changeset/slimy-moons-turn.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@chainlink/contracts-ccip": patch
---

make pools backwards compatible proxies
17 changes: 17 additions & 0 deletions contracts/gas-snapshots/ccip.gas-snapshot
Original file line number Diff line number Diff line change
Expand Up @@ -283,6 +283,19 @@ EtherSenderReceiverTest_validatedMessage:test_validatedMessage_emptyDataOverwrit
EtherSenderReceiverTest_validatedMessage:test_validatedMessage_invalidTokenAmounts() (gas: 17895)
EtherSenderReceiverTest_validatedMessage:test_validatedMessage_tokenOverwrittenToWeth() (gas: 25287)
EtherSenderReceiverTest_validatedMessage:test_validatedMessage_validMessage_extraArgs() (gas: 26292)
LockReleaseTokenPoolAndProxy_setRateLimitAdmin:test_SetRateLimitAdmin_Revert() (gas: 11057)
LockReleaseTokenPoolAndProxy_setRateLimitAdmin:test_SetRateLimitAdmin_Success() (gas: 35096)
LockReleaseTokenPoolAndProxy_setRebalancer:test_SetRebalancer_Revert() (gas: 10970)
LockReleaseTokenPoolAndProxy_setRebalancer:test_SetRebalancer_Success() (gas: 17992)
LockReleaseTokenPoolPoolAndProxy_canAcceptLiquidity:test_CanAcceptLiquidity_Success() (gas: 3316183)
LockReleaseTokenPoolPoolAndProxy_provideLiquidity:test_LiquidityNotAccepted_Revert() (gas: 3312692)
LockReleaseTokenPoolPoolAndProxy_provideLiquidity:test_Unauthorized_Revert() (gas: 11358)
LockReleaseTokenPoolPoolAndProxy_setChainRateLimiterConfig:test_NonExistentChain_Revert() (gas: 17004)
LockReleaseTokenPoolPoolAndProxy_setChainRateLimiterConfig:test_OnlyOwnerOrRateLimitAdmin_Revert() (gas: 68543)
LockReleaseTokenPoolPoolAndProxy_setChainRateLimiterConfig:test_OnlyOwner_Revert() (gas: 17188)
LockReleaseTokenPoolPoolAndProxy_supportsInterface:test_SupportsInterface_Success() (gas: 11984)
LockReleaseTokenPoolPoolAndProxy_withdrawalLiquidity:test_InsufficientLiquidity_Revert() (gas: 60025)
LockReleaseTokenPoolPoolAndProxy_withdrawalLiquidity:test_Unauthorized_Revert() (gas: 11355)
LockReleaseTokenPool_canAcceptLiquidity:test_CanAcceptLiquidity_Success() (gas: 2964601)
LockReleaseTokenPool_lockOrBurn:test_LockOrBurnWithAllowList_Revert() (gas: 24710)
LockReleaseTokenPool_lockOrBurn:test_LockOrBurnWithAllowList_Success() (gas: 73874)
Expand Down Expand Up @@ -469,6 +482,10 @@ TokenAdminRegistry_setPool:test_setPool_OnlyAdministrator_Revert() (gas: 18017)
TokenAdminRegistry_setPool:test_setPool_Success() (gas: 33947)
TokenAdminRegistry_transferAdminRole:test_transferAdminRole_OnlyAdministrator_Revert() (gas: 18073)
TokenAdminRegistry_transferAdminRole:test_transferAdminRole_Success() (gas: 50342)
TokenPoolAndProxy:test_lockOrBurn_burnMint_Success() (gas: 5926354)
TokenPoolAndProxy:test_lockOrBurn_lockRelease_Success() (gas: 6310837)
TokenPoolAndProxyMigration:test_tokenPoolMigration_Success_1_2() (gas: 6727032)
TokenPoolAndProxyMigration:test_tokenPoolMigration_Success_1_4() (gas: 6900022)
TokenPoolWithAllowList_applyAllowListUpdates:test_AllowListNotEnabled_Revert() (gas: 2184772)
TokenPoolWithAllowList_applyAllowListUpdates:test_OnlyOwner_Revert() (gas: 12111)
TokenPoolWithAllowList_applyAllowListUpdates:test_SetAllowListSkipsZero_Success() (gas: 23412)
Expand Down
2 changes: 2 additions & 0 deletions contracts/scripts/native_solc_compile_all_ccip
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,8 @@ compileContract ccip/pools/LockReleaseTokenPool.sol
compileContract ccip/pools/BurnMintTokenPool.sol
compileContract ccip/pools/BurnFromMintTokenPool.sol
compileContract ccip/pools/BurnWithFromMintTokenPool.sol
compileContract ccip/pools/LockReleaseTokenPoolAndProxy.sol
compileContract ccip/pools/BurnMintTokenPoolAndProxy.sol
compileContract ccip/pools/TokenPool.sol
compileContract shared/token/ERC677/BurnMintERC677.sol
compileContract ccip/ARM.sol
Expand Down
46 changes: 46 additions & 0 deletions contracts/src/v0.8/ccip/interfaces/IPoolPriorTo1_5.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import {IERC20} from "../../vendor/openzeppelin-solidity/v4.8.3/contracts/token/ERC20/IERC20.sol";

// Shared public interface for multiple pool types.
// Each pool type handles a different child token model (lock/unlock, mint/burn.)
interface IPoolPriorTo1_5 {
/// @notice Lock tokens into the pool or burn the tokens.
/// @param originalSender Original sender of the tokens.
/// @param receiver Receiver of the tokens on destination chain.
/// @param amount Amount to lock or burn.
/// @param remoteChainSelector Destination chain Id.
/// @param extraArgs Additional data passed in by sender for lockOrBurn processing
/// in custom pools on source chain.
/// @return retData Optional field that contains bytes. Unused for now but already
/// implemented to allow future upgrades while preserving the interface.
function lockOrBurn(
address originalSender,
bytes calldata receiver,
uint256 amount,
uint64 remoteChainSelector,
bytes calldata extraArgs
) external returns (bytes memory);

/// @notice Releases or mints tokens to the receiver address.
/// @param originalSender Original sender of the tokens.
/// @param receiver Receiver of the tokens.
/// @param amount Amount to release or mint.
/// @param remoteChainSelector Source chain Id.
/// @param extraData Additional data supplied offchain for releaseOrMint processing in
/// custom pools on dest chain. This could be an attestation that was retrieved through a
/// third party API.
/// @dev offchainData can come from any untrusted source.
function releaseOrMint(
bytes memory originalSender,
address receiver,
uint256 amount,
uint64 remoteChainSelector,
bytes memory extraData
) external;

/// @notice Gets the IERC20 token that this pool can lock or burn.
/// @return token The IERC20 token representation.
function getToken() external view returns (IERC20 token);
}
69 changes: 69 additions & 0 deletions contracts/src/v0.8/ccip/pools/BurnMintTokenPoolAndProxy.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.19;

import {ITypeAndVersion} from "../../shared/interfaces/ITypeAndVersion.sol";
import {IBurnMintERC20} from "../../shared/token/ERC20/IBurnMintERC20.sol";

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

contract BurnMintTokenPoolAndProxy is ITypeAndVersion, LegacyPoolWrapper {
string public constant override typeAndVersion = "BurnMintTokenPoolAndProxy 1.5.0-dev";

constructor(
IBurnMintERC20 token,
address[] memory allowlist,
address armProxy,
address router
) LegacyPoolWrapper(token, allowlist, armProxy, router) {}

/// @notice Burn the token in the pool
/// @dev The whenHealthy check is important to ensure that even if a ramp is compromised
/// we're able to stop token movement via ARM.
function lockOrBurn(Pool.LockOrBurnInV1 calldata lockOrBurnIn)
external
virtual
override
whenHealthy
returns (Pool.LockOrBurnOutV1 memory)
{
_checkAllowList(lockOrBurnIn.originalSender);
_onlyOnRamp(lockOrBurnIn.remoteChainSelector);
_consumeOutboundRateLimit(lockOrBurnIn.remoteChainSelector, lockOrBurnIn.amount);

if (!_hasLegacyPool()) {
IBurnMintERC20(address(i_token)).burn(lockOrBurnIn.amount);
} else {
_lockOrBurnLegacy(lockOrBurnIn);
}

emit Burned(msg.sender, lockOrBurnIn.amount);

return Pool.LockOrBurnOutV1({destPoolAddress: getRemotePool(lockOrBurnIn.remoteChainSelector), destPoolData: ""});
}

/// @notice Mint tokens from the pool to the recipient
/// @dev The whenHealthy check is important to ensure that even if a ramp is compromised
/// we're able to stop token movement via ARM.
function releaseOrMint(Pool.ReleaseOrMintInV1 calldata releaseOrMintIn)
external
virtual
override
whenHealthy
returns (Pool.ReleaseOrMintOutV1 memory)
{
_onlyOffRamp(releaseOrMintIn.remoteChainSelector);
_validateSourceCaller(releaseOrMintIn.remoteChainSelector, releaseOrMintIn.sourcePoolAddress);
_consumeInboundRateLimit(releaseOrMintIn.remoteChainSelector, releaseOrMintIn.amount);

if (!_hasLegacyPool()) {
IBurnMintERC20(address(i_token)).mint(releaseOrMintIn.receiver, releaseOrMintIn.amount);
} else {
_releaseOrMintLegacy(releaseOrMintIn);
}

emit Minted(msg.sender, releaseOrMintIn.receiver, releaseOrMintIn.amount);

return Pool.ReleaseOrMintOutV1({localToken: address(i_token), destinationAmount: releaseOrMintIn.amount});
}
}
79 changes: 79 additions & 0 deletions contracts/src/v0.8/ccip/pools/LegacyPoolWrapper.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
// SPDX-License-Identifier: BUSL-1.1
pragma solidity ^0.8.0;

import {IPoolPriorTo1_5} from "../interfaces/IPoolPriorTo1_5.sol";

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

import {IERC20} from "../../vendor/openzeppelin-solidity/v4.8.3/contracts/token/ERC20/IERC20.sol";

abstract contract LegacyPoolWrapper is TokenPool {
event LegacyPoolChanged(IPoolPriorTo1_5 oldPool, IPoolPriorTo1_5 newPool);

/// @dev The previous pool, if there is any. This is a property to make the older 1.0-1.4 pools
/// compatible with the current 1.5 pool. To achieve this, we set the previous pool address to the
/// currently deployed legacy pool. Then we configure this new pool as onRamp and offRamp on the legacy pools.
/// In the case of a 1.4 pool, this new pool contract has to be set to the Router as well, as it validates
/// who can call it through the router calls. This contract will always return itself as the only allowed ramp.
/// @dev Can be address(0), this would indicate that this pool is operating as a normal pool as opposed to
/// a proxy pool.
IPoolPriorTo1_5 internal s_previousPool;

constructor(
IERC20 token,
address[] memory allowlist,
address armProxy,
address router
) TokenPool(token, allowlist, armProxy, router) {}

// ================================================================
// │ Legacy Fallbacks │
// ================================================================
// Legacy fallbacks for older token pools that do not implement the new interface.

/// @notice Legacy fallback for the 1.4 token pools.
function getOnRamp(uint64) external view returns (address onRampAddress) {
return address(this);
}

/// @notice Return true if the given offRamp is a configured offRamp for the given source chain.
function isOffRamp(uint64 sourceChainSelector, address offRamp) external view returns (bool) {
return offRamp == address(this) || s_router.isOffRamp(sourceChainSelector, offRamp);
}

/// @notice Configures the legacy fallback option. If the previous pool is set, this pool will act as a proxy for
/// the legacy pool.
/// @param prevPool The address of the previous pool.
function setPreviousPool(IPoolPriorTo1_5 prevPool) external onlyOwner {
IPoolPriorTo1_5 oldPrevPool = s_previousPool;
s_previousPool = prevPool;

emit LegacyPoolChanged(oldPrevPool, prevPool);
}

function _hasLegacyPool() internal view returns (bool) {
return address(s_previousPool) != address(0);
}

function _lockOrBurnLegacy(Pool.LockOrBurnInV1 memory lockOrBurnIn) internal {
i_token.transfer(address(s_previousPool), lockOrBurnIn.amount);
s_previousPool.lockOrBurn(
lockOrBurnIn.originalSender, lockOrBurnIn.receiver, lockOrBurnIn.amount, lockOrBurnIn.remoteChainSelector, ""
);
}

/// @notice This call converts the arguments from a >=1.5 pool call to those of a <1.5 pool call, and uses these
/// to call the previous pool.
/// @param releaseOrMintIn The 1.5 style release or mint arguments.
/// @dev Since extraData has never been used in LockRelease or MintBurn token pools, we can safely ignore it.
function _releaseOrMintLegacy(Pool.ReleaseOrMintInV1 memory releaseOrMintIn) internal {
s_previousPool.releaseOrMint(
releaseOrMintIn.originalSender,
releaseOrMintIn.receiver,
releaseOrMintIn.amount,
releaseOrMintIn.remoteChainSelector,
""
);
}
}
Loading

0 comments on commit 6b98596

Please sign in to comment.