Skip to content
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions chains/evm/.changeset/large-places-pick.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@chainlink/contracts-ccip": patch
---

Bugfixes for CCTP V2 Contracts
27 changes: 14 additions & 13 deletions chains/evm/.gas-snapshot
Original file line number Diff line number Diff line change
Expand Up @@ -488,16 +488,16 @@ SiloedLockReleaseTokenPool_setRebalancer:test_setSiloRebalancer() (gas: 28778)
SiloedLockReleaseTokenPool_updateSiloDesignations:test_updateSiloDesignations() (gas: 183879)
SiloedLockReleaseTokenPool_withdrawLiqudity:test_withdrawLiquidity_SiloedFunds() (gas: 110516)
SiloedLockReleaseTokenPool_withdrawLiqudity:test_withdrawLiquidity_UnsiloedFunds_LegacyFunctionSelector() (gas: 112193)
SiloedUSDCTokenPool_burnLockedUSDC:test_burnLockedUSDC() (gas: 414564)
SiloedUSDCTokenPool_burnLockedUSDC:test_burnLockedUSDC() (gas: 414744)
SiloedUSDCTokenPool_cancelExistingCCTPMigrationProposal:test_cancelExistingCCTPMigrationProposal() (gas: 30532)
SiloedUSDCTokenPool_cancelExistingCCTPMigrationProposal:test_cancelExistingCCTPMigrationProposal_EmitsEvent() (gas: 29852)
SiloedUSDCTokenPool_cancelExistingCCTPMigrationProposal:test_cancelExistingCCTPMigrationProposal_ResetsExcludedTokens() (gas: 185948)
SiloedUSDCTokenPool_excludeTokensFromBurn:test_excludeTokensFromBurn() (gas: 192396)
SiloedUSDCTokenPool_excludeTokensFromBurn:test_excludeTokensFromBurn_EmitsEvent() (gas: 192244)
SiloedUSDCTokenPool_lockOrBurn:test_lockOrBurn_MultipleLocks() (gas: 161173)
SiloedUSDCTokenPool_lockOrBurn:test_lockOrBurn_Success() (gas: 135564)
SiloedUSDCTokenPool_lockOrBurn:test_lockOrBurn_UpdatesLockedTokensAccounting() (gas: 132654)
SiloedUSDCTokenPool_lockOrBurn:test_lockOrBurn_UpdatesSiloedTokensAccounting() (gas: 134596)
SiloedUSDCTokenPool_lockOrBurn:test_lockOrBurn_MultipleLocks() (gas: 161621)
SiloedUSDCTokenPool_lockOrBurn:test_lockOrBurn_Success() (gas: 135788)
SiloedUSDCTokenPool_lockOrBurn:test_lockOrBurn_UpdatesLockedTokensAccounting() (gas: 132878)
SiloedUSDCTokenPool_lockOrBurn:test_lockOrBurn_UpdatesSiloedTokensAccounting() (gas: 134820)
SiloedUSDCTokenPool_proposeCCTPMigration:test_proposeCCTPMigration() (gas: 37970)
SiloedUSDCTokenPool_proposeCCTPMigration:test_proposeCCTPMigration_AfterCancellation() (gas: 50568)
SiloedUSDCTokenPool_releaseOrMint:test_releaseOrMint_SubtractsFromExcludedTokens() (gas: 352087)
Expand Down Expand Up @@ -550,11 +550,12 @@ TokenPool_validateReleaseOrMint:test_validateReleaseOrMint_Success() (gas: 39621
USDCSourcePoolDataCodec__calculateDepositHash:test__calculateDepositHash() (gas: 9917)
USDCSourcePoolDataCodec__decodeSourceTokenDataPayloadV2:test__decodeSourceTokenDataPayloadV2_CCTPV2() (gas: 5161)
USDCSourcePoolDataCodec__decodeSourceTokenDataPayloadV2:test__decodeSourceTokenDataPayloadV2_CCTPV2CCV() (gas: 4916)
USDCTokenPoolCCTPV2_constructor:test_constructor() (gas: 3429212)
USDCTokenPoolCCTPV2_lockOrBurn:test_lockOrBurn() (gas: 129326)
USDCTokenPoolCCTPV2_lockOrBurn:test_lockOrBurn_MintRecipientOverride() (gas: 156828)
USDCTokenPoolCCTPV2_constructor:test_constructor() (gas: 3470365)
USDCTokenPoolCCTPV2_lockOrBurn:test_lockOrBurn() (gas: 129947)
USDCTokenPoolCCTPV2_lockOrBurn:test_lockOrBurn_MinFeeNotSupported() (gas: 114123)
USDCTokenPoolCCTPV2_lockOrBurn:test_lockOrBurn_MintRecipientOverride() (gas: 157604)
USDCTokenPoolCCTPV2_releaseOrMint:test_releaseOrMint_RealTx() (gas: 265533)
USDCTokenPoolProxy_constructor:test_constructor() (gas: 1816306)
USDCTokenPoolProxy_constructor:test_constructor() (gas: 1823082)
USDCTokenPoolProxy_generateNewReleaseOrMintIn:test_generateNewReleaseOrMintIn() (gas: 35218)
USDCTokenPoolProxy_lockOrBurn:test_lockOrBurn_CCTPV1() (gas: 104790)
USDCTokenPoolProxy_lockOrBurn:test_lockOrBurn_CCTPV2() (gas: 94306)
Expand All @@ -571,9 +572,9 @@ USDCTokenPoolProxy_updateLockReleasePoolAddresses:test_updateLockReleasePoolAddr
USDCTokenPoolProxy_updateLockReleasePoolAddresses:test_updateLockReleasePoolAddresses_Single() (gas: 52529)
USDCTokenPoolProxy_updateLockReleasePoolAddresses:test_updateLockReleasePoolAddresses_ZeroAddress() (gas: 25431)
USDCTokenPoolProxy_updatePoolAddresses:test_updatePoolAddresses() (gas: 55499)
USDCTokenPool_constructor:test_constructor() (gas: 3377431)
USDCTokenPool_lockOrBurn:test_LockOrBurn() (gas: 130827)
USDCTokenPool_lockOrBurn:test_LockOrBurn_LegacySourcePoolDataFormat() (gas: 143734)
USDCTokenPool_lockOrBurn:test_LockOrBurn_MintRecipientOverride() (gas: 159276)
USDCTokenPool_constructor:test_constructor() (gas: 3377453)
USDCTokenPool_lockOrBurn:test_LockOrBurn() (gas: 130897)
USDCTokenPool_lockOrBurn:test_LockOrBurn_LegacySourcePoolDataFormat() (gas: 143822)
USDCTokenPool_lockOrBurn:test_LockOrBurn_MintRecipientOverride() (gas: 159364)
USDCTokenPool_releaseOrMint:test_ReleaseOrMintRealTx() (gas: 265775)
USDCTokenPool_supportsInterface:test_SupportsInterface() (gas: 9990)
21 changes: 20 additions & 1 deletion chains/evm/contracts/pools/USDC/SiloedUSDCTokenPool.sol
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
pragma solidity ^0.8.24;

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

import {AuthorizedCallers} from "@chainlink/contracts/src/v0.8/shared/access/AuthorizedCallers.sol";
Expand Down Expand Up @@ -90,7 +91,9 @@ contract SiloedUSDCTokenPool is SiloedLockReleaseTokenPool, AuthorizedCallers {
// No excluded tokens is the common path, as it means no migration has occured yet, and any released
// tokens should come from the stored token balance of previously deposited tokens.
if (excludedTokens == 0) {
if (localAmount > remoteConfig.tokenBalance) revert InsufficientLiquidity(remoteConfig.tokenBalance, localAmount);
if (localAmount > remoteConfig.tokenBalance) {
revert InsufficientLiquidity(remoteConfig.tokenBalance, localAmount);
}

remoteConfig.tokenBalance -= localAmount;

Expand All @@ -115,6 +118,22 @@ contract SiloedUSDCTokenPool is SiloedLockReleaseTokenPool, AuthorizedCallers {
return Pool.ReleaseOrMintOutV1({destinationAmount: localAmount});
}

/// @inheritdoc SiloedLockReleaseTokenPool
/// @dev This function is overridden to encode the LOCK_RELEASE_FLAG into the destPoolData, as the destination pool.
/// will be a BurnMintWithLockReleaseFlagTokenPool and may need to be processed by a proxy first.
function lockOrBurn(
Pool.LockOrBurnInV1 calldata lockOrBurnIn
) public virtual override returns (Pool.LockOrBurnOutV1 memory) {
// Call the parent contract's lockOrBurn function to get the base output. All functionality of this child
// is inherited from the parent contract except for the overwritten destPoolData.
Pool.LockOrBurnOutV1 memory baseOutput = super.lockOrBurn(lockOrBurnIn);

// Encode the LOCK_RELEASE_FLAG into the destPoolData
baseOutput.destPoolData = abi.encode(USDCSourcePoolDataCodec.LOCK_RELEASE_FLAG);

return baseOutput;
}

/// @dev This function is overridden to prevent providing liquidity to a chain that has already been migrated, and thus should use CCTP-proper instead of a Lock/Release mechanism.
function _provideLiquidity(uint64 remoteChainSelector, uint256 amount) internal override {
if (s_migratedChains.contains(remoteChainSelector)) {
Expand Down
17 changes: 15 additions & 2 deletions chains/evm/contracts/pools/USDC/USDCTokenPoolCCTPV2.sol
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ contract USDCTokenPoolCCTPV2 is USDCTokenPool {
error InvalidExecutionFinalityThreshold(uint32 expected, uint32 got);
error InvalidDepositHash(bytes32 expected, bytes32 got);
error InvalidBurnToken(address expected, address got);
error InvalidMinFee(uint256 maxAcceptableFee, uint256 actualFee);

/// @dev CCTP's max fee is based on the use of fast-burn. Since this pool does not utilize that feature, max fee should be 0.
uint32 public constant MAX_FEE = 0;
Expand Down Expand Up @@ -64,6 +65,18 @@ contract USDCTokenPoolCCTPV2 is USDCTokenPool {
revert InvalidReceiver(lockOrBurnIn.receiver);
}

// Some CCTP-V2 chains support a configurable fee switch, but not all. It is therefore
// necessary to check via a try-catch block. If the call reverts, then the fee switch is not supported and the
// standard transfer fee will be zero, and no further action is required.
try i_tokenMessenger.getMinFeeAmount(lockOrBurnIn.amount) returns (uint256 minFee) {
// This token pool only supports zero-fee standard transfers. If the minFee is non-zero
// then the function should revert as the message may not be able to be successfully
// delivered on destination due to unexpected minting fees.
if (minFee > MAX_FEE) {
revert InvalidMinFee(MAX_FEE, minFee);
}
} catch {}

bytes32 decodedReceiver;
// For EVM chains, the mintRecipient is not used, but is needed for Solana, where the mintRecipient will
// be a PDA owned by the pool, and will forward the tokens to its final destination after minting.
Expand Down Expand Up @@ -166,8 +179,8 @@ contract USDCTokenPoolCCTPV2 is USDCTokenPool {
/// * sender 32 bytes32 44
/// * recipient 32 bytes32 76
/// * destinationCaller 32 bytes32 108
/// * minFinalityThreshold 32 uint32 140
/// * finalityThresholdExecuted 32 uint32 144
/// * minFinalityThreshold 4 uint32 140
/// * finalityThresholdExecuted 4 uint32 144
/// * messageBody dynamic bytes 148

/// @dev Message Body for USDC.
Expand Down
3 changes: 2 additions & 1 deletion chains/evm/contracts/pools/USDC/USDCTokenPoolProxy.sol
Original file line number Diff line number Diff line change
Expand Up @@ -146,7 +146,8 @@ contract USDCTokenPoolProxy is Ownable2StepMsgSender, IPoolV1, ITypeAndVersion {
function supportsInterface(
bytes4 interfaceId
) public pure override returns (bool) {
return interfaceId == type(IPoolV1).interfaceId || interfaceId == type(IERC165).interfaceId;
return interfaceId == type(IPoolV1).interfaceId || interfaceId == Pool.CCIP_POOL_V1
|| interfaceId == type(IERC165).interfaceId;
}

/// @inheritdoc IPoolV1
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -108,4 +108,11 @@ interface ITokenMessenger {
/// to/from remote domainsmessage transmitter for this token messenger.
/// @dev immutable
function localMessageTransmitter() external view returns (address);

/// Returns the minimum fee required for a deposit for burn message.
/// @dev This function is only available for CCTP V2, and not every CCTP-V2 compatible
/// chain supports this configurable fee switch.
function getMinFeeAmount(
uint256 amount
) external view returns (uint256);
}
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,13 @@ contract MockE2EUSDCTokenMessenger is ITokenMessenger {
return i_transmitter;
}

/// @dev This function is only available for CCTP V2
function getMinFeeAmount(
uint256
) external pure returns (uint256) {
return 0;
}

/**
* @notice Sends a BurnMessage through the local message transmitter
* @dev calls local message transmitter's sendMessage() function if `_destinationCaller` == bytes32(0),
Expand Down
7 changes: 7 additions & 0 deletions chains/evm/contracts/test/mocks/MockUSDCTokenMessenger.sol
Original file line number Diff line number Diff line change
Expand Up @@ -75,4 +75,11 @@ contract MockUSDCTokenMessenger is ITokenMessenger {
function localMessageTransmitter() external view returns (address) {
return i_transmitter;
}

/// @dev This function is only available for CCTP V2
function getMinFeeAmount(
uint256
) external pure returns (uint256) {
return 0;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {Pool} from "../../../../libraries/Pool.sol";
import {USDCSourcePoolDataCodec} from "../../../../libraries/USDCSourcePoolDataCodec.sol";
import {TokenPool} from "../../../../pools/TokenPool.sol";
import {USDCTokenPool} from "../../../../pools/USDC/USDCTokenPool.sol";
import {USDCTokenPoolCCTPV2} from "../../../../pools/USDC/USDCTokenPoolCCTPV2.sol";
import {USDCTokenPoolCCTPV2Setup} from "./USDCTokenPoolCCTPV2Setup.t.sol";

import {AuthorizedCallers} from "@chainlink/contracts/src/v0.8/shared/access/AuthorizedCallers.sol";
Expand Down Expand Up @@ -135,6 +136,28 @@ contract USDCTokenPoolCCTPV2_lockOrBurn is USDCTokenPoolCCTPV2Setup {
assertEq(sourceTokenDataPayload.sourceDomain, DEST_DOMAIN_IDENTIFIER, "sourceDomain is incorrect");
}

function test_lockOrBurn_MinFeeNotSupported() public {
bytes32 receiver = bytes32(uint256(uint160(STRANGER)));
uint256 amount = 1000;
s_USDCToken.transfer(address(s_usdcTokenPool), amount);

vm.startPrank(s_routerAllowedOnRamp);

vm.mockCallRevert(
address(s_mockUSDCTokenMessenger), abi.encodeWithSelector(ITokenMessenger.getMinFeeAmount.selector), ""
);

s_usdcTokenPool.lockOrBurn(
Pool.LockOrBurnInV1({
originalSender: OWNER,
receiver: abi.encodePacked(receiver),
amount: amount,
remoteChainSelector: DEST_CHAIN_SELECTOR,
localToken: address(s_USDCToken)
})
);
}

function testFuzz_lockOrBurn_Success(bytes32 destinationReceiver, uint256 amount) public {
vm.assume(destinationReceiver != bytes32(0));
amount = bound(amount, 1, _getOutboundRateLimiterConfig().capacity);
Expand Down Expand Up @@ -263,4 +286,33 @@ contract USDCTokenPoolCCTPV2_lockOrBurn is USDCTokenPoolCCTPV2Setup {
})
);
}

function test_lockOrBurn_RevertWhen_InvalidMinFee() public {
bytes32 receiver = bytes32(uint256(uint160(STRANGER)));

vm.startPrank(s_routerAllowedOnRamp);
uint256 amount = 1000;

vm.mockCall(
address(s_mockUSDCTokenMessenger),
abi.encodeWithSelector(ITokenMessenger.getMinFeeAmount.selector),
abi.encode(s_usdcTokenPool.MAX_FEE() + 1)
);

vm.expectRevert(
abi.encodeWithSelector(
USDCTokenPoolCCTPV2.InvalidMinFee.selector, s_usdcTokenPool.MAX_FEE(), s_usdcTokenPool.MAX_FEE() + 1
)
);

s_usdcTokenPool.lockOrBurn(
Pool.LockOrBurnInV1({
originalSender: OWNER,
receiver: abi.encodePacked(receiver),
amount: amount,
remoteChainSelector: DEST_CHAIN_SELECTOR,
localToken: address(s_USDCToken)
})
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ pragma solidity ^0.8.24;

import {IPoolV1} from "../../../../interfaces/IPool.sol";

import {Pool} from "../../../../libraries/Pool.sol";
import {USDCTokenPoolProxy} from "../../../../pools/USDC/USDCTokenPoolProxy.sol";
import {USDCSetup} from "../USDCSetup.t.sol";

Expand Down Expand Up @@ -33,7 +34,9 @@ contract USDCTokenPoolProxy_constructor is USDCSetup {
assertEq(pools.cctpV2Pool, s_cctpV2Pool);

assertTrue(proxy.supportsInterface(type(IPoolV1).interfaceId));
assertTrue(proxy.supportsInterface(Pool.CCIP_POOL_V1));
assertTrue(proxy.supportsInterface(type(IERC165).interfaceId));

assertTrue(proxy.isSupportedToken(address(s_USDCToken)));
}

Expand Down
Loading
Loading