Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin/develop' into deploy-transferabl…
Browse files Browse the repository at this point in the history
…e-solana
  • Loading branch information
tt-cll committed Feb 1, 2025
2 parents 1c225c6 + f791bf9 commit 7b747f8
Show file tree
Hide file tree
Showing 91 changed files with 9,406 additions and 957 deletions.
10 changes: 10 additions & 0 deletions contracts/.changeset/quiet-masks-act.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
---
'@chainlink/contracts': patch
---

Comment and parameter validation fixes and remove outstandingTokens from BurnToAddressMintTokenPool #bugfix


PR issue: CCIP-5061

Solidity Review issue: CCIP-3966
32 changes: 15 additions & 17 deletions contracts/gas-snapshots/ccip.gas-snapshot
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,7 @@ BurnMintTokenPool_lockOrBurn:test_PoolBurn() (gas: 236872)
BurnMintTokenPool_lockOrBurn:test_Setup() (gas: 17819)
BurnMintTokenPool_releaseOrMint:test_PoolMint() (gas: 102527)
BurnMintWithLockReleaseFlagTokenPool_lockOrBurn:test_LockOrBurn_CorrectReturnData() (gas: 237292)
BurnToAddressMintTokenPool_lockOrBurn:test_LockOrBurn() (gas: 257956)
BurnToAddressMintTokenPool_releaseOrMint:test_releaseOrMint() (gas: 126048)
BurnToAddressMintTokenPool_setOutstandingokens:test_setOutstandingTokens() (gas: 37793)
BurnToAddressMintTokenPool_lockOrBurn:test_LockOrBurn() (gas: 235440)
BurnWithFromMintTokenPool_lockOrBurn:test_PoolBurn() (gas: 239012)
BurnWithFromMintTokenPool_lockOrBurn:test_Setup() (gas: 24169)
CCIPClientExample_sanity:test_ImmutableExamples() (gas: 2079619)
Expand Down Expand Up @@ -364,20 +362,20 @@ Router_routeMessage:test_routeMessage_ExecutionEvent() (gas: 157232)
Router_routeMessage:test_routeMessage_ManualExec() (gas: 34881)
SiloedLockReleaseTokenPool_lockOrBurn:test_lockOrBurn_SiloedFunds() (gas: 76874)
SiloedLockReleaseTokenPool_lockOrBurn:test_lockOrBurn_UnsiloedFunds() (gas: 76104)
SiloedLockReleaseTokenPool_provideLiqudity:test_ProvideLiquidity_LegacyProvideLiquiditySelector() (gas: 91873)
SiloedLockReleaseTokenPool_provideLiqudity:test_ProvideLiquidity_SiloedChain() (gas: 82416)
SiloedLockReleaseTokenPool_provideLiqudity:test_ProvideLiquidity_UnsiloedChain() (gas: 84036)
SiloedLockReleaseTokenPool_releaseOrMint:test_ReleaseOrMint_RevertsWhen_InsufficientLiquidity_SiloedChain() (gas: 110002)
SiloedLockReleaseTokenPool_releaseOrMint:test_ReleaseOrMint_RevertsWhen_InsufficientLiquidity_UnsiloedChain() (gas: 115718)
SiloedLockReleaseTokenPool_releaseOrMint:test_ReleaseOrMint_SiloedChain() (gas: 262340)
SiloedLockReleaseTokenPool_releaseOrMint:test_ReleaseOrMint_UnsiloedChain() (gas: 263392)
SiloedLockReleaseTokenPool_setRebalancer:test_setRebalancer_UnsiloedChains() (gas: 24429)
SiloedLockReleaseTokenPool_setRebalancer:test_setSiloRebalancer() (gas: 32165)
SiloedLockReleaseTokenPool_updateSiloDesignations:test_updateSiloDesignations() (gas: 105825)
SiloedLockReleaseTokenPool_withdrawLiqudity:test_withdrawLiquidity_RevertsWhen_LegacyFunctionSelectorUnauthorized() (gas: 18244)
SiloedLockReleaseTokenPool_withdrawLiqudity:test_withdrawLiquidity_SiloedFunds() (gas: 70948)
SiloedLockReleaseTokenPool_withdrawLiqudity:test_withdrawLiquidity_UnsiloedFunds_LegacyFunctionSelector() (gas: 76391)
SiloedLockReleaseTokenPool_withdrawLiqudity:test_withdrawSiloedLiquidity_UnsiloedFunds() (gas: 71945)
SiloedLockReleaseTokenPool_provideLiquidity:test_provideLiquidity() (gas: 89627)
SiloedLockReleaseTokenPool_provideSiloedLiquidity:test_SiloedChain() (gas: 82328)
SiloedLockReleaseTokenPool_provideSiloedLiquidity:test_UnsiloedChain() (gas: 81889)
SiloedLockReleaseTokenPool_releaseOrMint:test_ReleaseOrMint_RevertsWhen_InsufficientLiquidity_SiloedChain() (gas: 109975)
SiloedLockReleaseTokenPool_releaseOrMint:test_ReleaseOrMint_RevertsWhen_InsufficientLiquidity_UnsiloedChain() (gas: 113535)
SiloedLockReleaseTokenPool_releaseOrMint:test_ReleaseOrMint_SiloedChain() (gas: 262243)
SiloedLockReleaseTokenPool_releaseOrMint:test_ReleaseOrMint_UnsiloedChain() (gas: 263296)
SiloedLockReleaseTokenPool_setRebalancer:test_setRebalancer_UnsiloedChains() (gas: 23661)
SiloedLockReleaseTokenPool_setRebalancer:test_setSiloRebalancer() (gas: 27540)
SiloedLockReleaseTokenPool_updateSiloDesignations:test_updateSiloDesignations() (gas: 135167)
SiloedLockReleaseTokenPool_withdrawLiqudity:test_withdrawLiquidity_RevertsWhen_LegacyFunctionSelectorUnauthorized() (gas: 16067)
SiloedLockReleaseTokenPool_withdrawLiqudity:test_withdrawLiquidity_SiloedFunds() (gas: 70845)
SiloedLockReleaseTokenPool_withdrawLiqudity:test_withdrawLiquidity_UnsiloedFunds_LegacyFunctionSelector() (gas: 72904)
SiloedLockReleaseTokenPool_withdrawLiqudity:test_withdrawSiloedLiquidity_UnsiloedFunds() (gas: 70058)
TokenAdminRegistry_acceptAdminRole:test_acceptAdminRole() (gas: 44236)
TokenAdminRegistry_addRegistryModule:test_addRegistryModule() (gas: 67093)
TokenAdminRegistry_getAllConfiguredTokens:test_getAllConfiguredTokens_outOfBounds() (gas: 11363)
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 @@ -73,8 +73,10 @@ compileContract pools/LockReleaseTokenPool
compileContract pools/BurnMintTokenPool
compileContract pools/BurnFromMintTokenPool
compileContract pools/BurnWithFromMintTokenPool
compileContract pools/BurnToAddressMintTokenPool
compileContract pools/TokenPool
compileContract pools/USDC/USDCTokenPool
compileContract pools/SiloedLockReleaseTokenPool

# Test helpers
compileContract test/helpers/BurnMintERC677Helper
Expand Down
49 changes: 0 additions & 49 deletions contracts/src/v0.8/ccip/pools/BurnToAddressMintTokenPool.sol
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ pragma solidity ^0.8.24;
import {ITypeAndVersion} from "../../shared/interfaces/ITypeAndVersion.sol";
import {IBurnMintERC20} from "../../shared/token/ERC20/IBurnMintERC20.sol";

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

Expand All @@ -18,23 +17,13 @@ import {SafeERC20} from "../../vendor/openzeppelin-solidity/v4.8.3/contracts/tok
contract BurnToAddressMintTokenPool is BurnMintTokenPoolAbstract, ITypeAndVersion {
using SafeERC20 for IERC20;

event OutstandingTokensSet(uint256 newMintedTokenAmount, uint256 oldMintedTokenAmount);

error InsufficientOutstandingTokens();

string public constant override typeAndVersion = "BurnToAddressTokenPool 1.5.1";

/// @notice The address where tokens are sent during a call to lockOrBurn, functionally burning but without decreasing
/// total supply. This address is expected to have no ability to recover the tokens sent to it, and will thus be locked forever.
/// This can be either an EOA without a corresponding private key, or a contract which does not have the ability to transfer the tokens.
address public immutable i_burnAddress;

/// @notice Minted Tokens is a safety mechanism to ensure that more tokens cannot be sent out of the bridge
/// than were originally sent in via CCIP. On incoming messages the value is increased, and on outgoing messages,
/// the value is decreased. For pools with existing tokens in circulation, the value may not be known at deployment
/// time, and thus should be set later using the setoutstandingTokens() function.
uint256 internal s_outstandingTokens;

/// @dev Since burnAddress is expected to make the tokens unrecoverable, no check for the zero address needs to be
/// performed, as it is a valid input.
constructor(
Expand All @@ -48,32 +37,12 @@ contract BurnToAddressMintTokenPool is BurnMintTokenPoolAbstract, ITypeAndVersio
i_burnAddress = burnAddress;
}

/// @notice Mint tokens from the pool to the recipient, updating the internal accounting for an outflow of tokens.
/// @dev If the amount of tokens to be
function releaseOrMint(
Pool.ReleaseOrMintInV1 calldata releaseOrMintIn
) public virtual override returns (Pool.ReleaseOrMintOutV1 memory) {
// When minting tokens, the local outstanding supply increases. These tokens will be burned
// when they are sent back to the pool on an outgoing message.
s_outstandingTokens += releaseOrMintIn.amount;

return super.releaseOrMint(releaseOrMintIn);
}

/// @inheritdoc BurnMintTokenPoolAbstract
/// @notice Tokens are burned by sending to an address which can never transfer them,
/// making the tokens unrecoverable without reducing the total supply.
function _burn(
uint256 amount
) internal virtual override {
if (amount > s_outstandingTokens) {
revert InsufficientOutstandingTokens();
}

// When tokens are burned, the amount outstanding decreases. This ensures that more tokens cannot be sent out
// of the bridge than were originally sent in via CCIP.
s_outstandingTokens -= amount;

getToken().safeTransfer(i_burnAddress, amount);
}

Expand All @@ -82,22 +51,4 @@ contract BurnToAddressMintTokenPool is BurnMintTokenPoolAbstract, ITypeAndVersio
function getBurnAddress() public view returns (address burnAddress) {
return i_burnAddress;
}

/// @notice Return the amount of tokens which were minted by this contract and not yet burned.
/// @return outstandingTokens The amount of tokens which were minted by this token pool and not yet burned.
function getOutstandingTokens() public view returns (uint256 outstandingTokens) {
return s_outstandingTokens;
}

/// @notice Set the amount of tokens which were minted by this contract and not yet burned.
/// @param amount The new amount of tokens which were minted by this token pool and not yet burned.
function setOutstandingTokens(
uint256 amount
) external onlyOwner {
uint256 currentOutstandingTokens = s_outstandingTokens;

s_outstandingTokens = amount;

emit OutstandingTokensSet(amount, currentOutstandingTokens);
}
}
75 changes: 49 additions & 26 deletions contracts/src/v0.8/ccip/pools/SiloedLockReleaseTokenPool.sol
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,10 @@ contract SiloedLockReleaseTokenPool is TokenPool, ITypeAndVersion {
error InsufficientLiquidity(uint256 availableLiquidity, uint256 requestedAmount);
error ChainNotSiloed(uint64 remoteChainSelector);
error InvalidChainSelector(uint64 remoteChainSelector);
error LiquidityAmountCannotBeZero();

event LiquidityAdded(uint64 remoteChainSelector, address indexed provider, uint256 amount);
event LiquidityRemoved(uint64 remoteChainSelector, address indexed provider, uint256 amount);
event LiquidityRemoved(uint64 remoteChainSelector, address indexed remover, uint256 amount);
event ChainUnsiloed(uint64 remoteChainSelector, uint256 amountUnsiloed);
event ChainSiloed(uint64 remoteChainSelector, address rebalancer);
event SiloRebalancerSet(uint64 indexed remoteChainSelector, address oldRebalancer, address newRebalancer);
Expand Down Expand Up @@ -95,13 +96,16 @@ contract SiloedLockReleaseTokenPool is TokenPool, ITypeAndVersion {
// Save gas by using storage instead of memory as a value may need to be updated.
SiloConfig storage remoteConfig = s_chainConfigs[releaseOrMintIn.remoteChainSelector];

// Since remoteConfig.isSiloed is used more than once, caching in memory saves gas instead of multiple SLOADs.
bool isSiloed = remoteConfig.isSiloed;

// Prevent A silent underflow by explicitly ensuring that enough funds are available to release
uint256 availableLiquidity = remoteConfig.isSiloed ? remoteConfig.tokenBalance : s_unsiloedTokenBalance;
uint256 availableLiquidity = isSiloed ? remoteConfig.tokenBalance : s_unsiloedTokenBalance;
if (localAmount > availableLiquidity) revert InsufficientLiquidity(availableLiquidity, localAmount);

// Tracking balances independently by chain is a security measure to prevent liquidity for one chain from being
// released by another chain.
if (remoteConfig.isSiloed) {
if (isSiloed) {
remoteConfig.tokenBalance -= localAmount;
} else {
s_unsiloedTokenBalance -= localAmount;
Expand All @@ -115,16 +119,6 @@ contract SiloedLockReleaseTokenPool is TokenPool, ITypeAndVersion {
return Pool.ReleaseOrMintOutV1({destinationAmount: localAmount});
}

/// @notice Returns whether the tokens locked for a given remote chain should be siloed independently
/// from all other remote chains.
/// @param remoteChainSelector the CCIP specific selector for the remote chain being interacted with.
/// @return isSiloed Whether the funds should be isolated from all the others.
function isSiloed(
uint64 remoteChainSelector
) external view returns (bool) {
return s_chainConfigs[remoteChainSelector].isSiloed;
}

/// @notice Returns the amount of tokens in the token pool that were siloed for a specific remote chain selector.
/// @param remoteChainSelector the CCIP specific selector for the remote chain being interacted with.
/// @return lockedTokens The tokens locked into this token pool for the given selector. If the chain is not siloed,
Expand All @@ -145,11 +139,24 @@ contract SiloedLockReleaseTokenPool is TokenPool, ITypeAndVersion {
return s_unsiloedTokenBalance;
}

// ================================================================
// │ Chain Management │
// ================================================================

/// @notice Returns whether the tokens locked for a given remote chain should be siloed independently
/// from all other remote chains.
/// @param remoteChainSelector the CCIP specific selector for the remote chain being interacted with.
/// @return isSiloed Whether the funds should be isolated from all the others.
function isSiloed(
uint64 remoteChainSelector
) external view returns (bool) {
return s_chainConfigs[remoteChainSelector].isSiloed;
}

/// @notice Updates designations for chains on whether to mark funds as Siloed or not
/// @param removes A list of chain selectors to disable Siloing. Their funds will be moved into the unsiloed pool.
/// If a chain is not siloed, and attempted to be removed, the function will revert.
/// @param adds A list of chain selectors to enable Siloing. Adding a chain to siloing will not set the rebalancer.
/// The rebalancer will need to be set separately.
/// @param adds A list of chain selectors to enable Siloing.
function updateSiloDesignations(uint64[] calldata removes, SiloConfigUpdate[] calldata adds) external onlyOwner {
for (uint256 i = 0; i < removes.length; ++i) {
if (!s_chainConfigs[removes[i]].isSiloed) revert ChainNotSiloed(removes[i]);
Expand All @@ -167,46 +174,51 @@ contract SiloedLockReleaseTokenPool is TokenPool, ITypeAndVersion {

for (uint256 i = 0; i < adds.length; ++i) {
// Since the zero chain selector is used to designate unsiloed chains, it should never be used for siloed chains.
if (adds[i].remoteChainSelector == 0) {
revert InvalidChainSelector(0);
if (adds[i].remoteChainSelector == 0 || s_chainConfigs[adds[i].remoteChainSelector].isSiloed) {
revert InvalidChainSelector(adds[i].remoteChainSelector);
}

SiloConfig memory newConfig = SiloConfig({tokenBalance: 0, rebalancer: adds[i].rebalancer, isSiloed: true});

s_chainConfigs[adds[i].remoteChainSelector] = newConfig;
s_chainConfigs[adds[i].remoteChainSelector] =
SiloConfig({tokenBalance: 0, rebalancer: adds[i].rebalancer, isSiloed: true});

emit ChainSiloed(adds[i].remoteChainSelector, adds[i].rebalancer);
}
}

/// @notice Gets the rebalancer able to provide liquidity for a remote chain selector
/// @param remoteChainSelector The CCIP specific selector for the remote chain being interacted with.
/// @return The current liquidity manager, contract owner if the chain's funds are not siloed.
/// @return The current liquidity manager for the given siloed chain, or the unsiloed rebalancer if the chain is not siloed.
function getSiloRebalancer(
uint64 remoteChainSelector
) public view returns (address) {
SiloConfig memory remoteConfig = s_chainConfigs[remoteChainSelector];
SiloConfig storage remoteConfig = s_chainConfigs[remoteChainSelector];
if (remoteConfig.isSiloed) {
return remoteConfig.rebalancer;
}

return s_rebalancer;
}

/// @notice Gets the rebalancer for the unsiloed chains.
/// @return The current liquidity manager for the unsiloed chains.
function getRebalancer() external view returns (address) {
return s_rebalancer;
}

/// @notice Sets the Rebalancer address for a given remoteChainSelector.
/// @dev Only callable by the owner.
/// @param remoteChainSelector the remote chain to set.
/// @param newRebalancer the address allowed to add liquidity for the given siloed chain.
function setSiloRebalancer(uint64 remoteChainSelector, address newRebalancer) external onlyOwner {
SiloConfig memory remoteConfig = s_chainConfigs[remoteChainSelector];
SiloConfig storage remoteConfig = s_chainConfigs[remoteChainSelector];

if (!remoteConfig.isSiloed) revert ChainNotSiloed(remoteChainSelector);

address oldRebalancer = remoteConfig.rebalancer;

s_chainConfigs[remoteChainSelector].rebalancer = newRebalancer;
remoteConfig.rebalancer = newRebalancer;

emit SiloRebalancerSet(remoteChainSelector, newRebalancer, oldRebalancer);
emit SiloRebalancerSet(remoteChainSelector, oldRebalancer, newRebalancer);
}

/// @notice Sets the Rebalancer address for unsiloed chains.
Expand All @@ -219,15 +231,20 @@ contract SiloedLockReleaseTokenPool is TokenPool, ITypeAndVersion {

s_rebalancer = newRebalancer;

emit UnsiloedRebalancerSet(newRebalancer, oldRebalancer);
emit UnsiloedRebalancerSet(oldRebalancer, newRebalancer);
}

// ================================================================
// │ Provide Liquidity │
// ================================================================

/// @notice Adds liquidity to the pool. The tokens should be approved first.
/// @param remoteChainSelector the remote chain to set. If the chain is not siloed, the liquidity will be shared among all
/// non-siloed chains.
/// @param amount The amount of liquidity to provide.
/// @dev Only the rebalancer for the chain can add liquidity
function provideSiloedLiquidity(uint64 remoteChainSelector, uint256 amount) external {
if (remoteChainSelector == 0) revert InvalidChainSelector(0);
_provideLiquidity(remoteChainSelector, amount);
}

Expand All @@ -242,6 +259,7 @@ contract SiloedLockReleaseTokenPool is TokenPool, ITypeAndVersion {
}

function _provideLiquidity(uint64 remoteChainSelector, uint256 amount) internal {
if (amount == 0) revert LiquidityAmountCannotBeZero();
if (msg.sender != getSiloRebalancer(remoteChainSelector)) revert Unauthorized(msg.sender);

// Storage is used instead of memory to save gas, as the state may need to be updated if the chain is siloed.
Expand All @@ -257,6 +275,10 @@ contract SiloedLockReleaseTokenPool is TokenPool, ITypeAndVersion {
emit LiquidityAdded(remoteChainSelector, msg.sender, amount);
}

// ================================================================
// │ Withdraw Liquidity │
// ================================================================

/// @notice Removes liquidity from the pool for unsiloed chains. Function is used to support legacy liquidity operations
/// by using a function selector available to previous L/R pools.
/// @dev Since the remoteChainSelector 0 should never be applied to a real chain, it is used to designate unsiloed chains.
Expand All @@ -277,6 +299,7 @@ contract SiloedLockReleaseTokenPool is TokenPool, ITypeAndVersion {
}

function _withdrawLiquidity(uint64 remoteChainSelector, uint256 amount) internal {
if (amount == 0) revert LiquidityAmountCannotBeZero();
if (msg.sender != getSiloRebalancer(remoteChainSelector)) revert Unauthorized(msg.sender);

// Save gas by using storage as multiple values may need to be read/written.
Expand Down
Loading

0 comments on commit 7b747f8

Please sign in to comment.