Skip to content

Commit

Permalink
Split RampTokenAmount into EVM2AnyTokenTransfer and Any2EVMTokenTrans…
Browse files Browse the repository at this point in the history
…fer (#1435)
  • Loading branch information
0xsuryansh authored Sep 13, 2024
1 parent 258959a commit ef3e366
Show file tree
Hide file tree
Showing 21 changed files with 531 additions and 482 deletions.
439 changes: 219 additions & 220 deletions contracts/gas-snapshots/ccip.gas-snapshot

Large diffs are not rendered by default.

18 changes: 9 additions & 9 deletions contracts/src/v0.8/ccip/FeeQuoter.sol
Original file line number Diff line number Diff line change
Expand Up @@ -862,13 +862,13 @@ contract FeeQuoter is AuthorizedCallers, IFeeQuoter, ITypeAndVersion, IReceiver,
}

/// @inheritdoc IFeeQuoter
/// @dev precondition - rampTokenAmounts and sourceTokenAmounts lengths must be equal
/// @dev precondition - onRampTokenTransfers and sourceTokenAmounts lengths must be equal
function processMessageArgs(
uint64 destChainSelector,
address feeToken,
uint256 feeTokenAmount,
bytes calldata extraArgs,
Internal.RampTokenAmount[] calldata rampTokenAmounts,
Internal.EVM2AnyTokenTransfer[] calldata onRampTokenTransfers,
Client.EVMTokenAmount[] calldata sourceTokenAmounts
)
external
Expand All @@ -894,37 +894,37 @@ contract FeeQuoter is AuthorizedCallers, IFeeQuoter, ITypeAndVersion, IReceiver,
// We can parse unvalidated args since this message is called after getFee (which will already validate the params)
Client.EVMExtraArgsV2 memory parsedExtraArgs = _parseUnvalidatedEVMExtraArgsFromBytes(extraArgs, defaultTxGasLimit);
isOutOfOrderExecution = parsedExtraArgs.allowOutOfOrderExecution;
destExecDataPerToken = _processPoolReturnData(destChainSelector, rampTokenAmounts, sourceTokenAmounts);
destExecDataPerToken = _processPoolReturnData(destChainSelector, onRampTokenTransfers, sourceTokenAmounts);

return (msgFeeJuels, isOutOfOrderExecution, Client._argsToBytes(parsedExtraArgs), destExecDataPerToken);
}

/// @notice Validates pool return data
/// @param destChainSelector Destination chain selector to which the token amounts are sent to
/// @param rampTokenAmounts Token amounts with populated pool return data
/// @param onRampTokenTransfers Token amounts with populated pool return data
/// @param sourceTokenAmounts Token amounts originally sent in a Client.EVM2AnyMessage message
/// @return destExecDataPerToken Destination chain execution data
function _processPoolReturnData(
uint64 destChainSelector,
Internal.RampTokenAmount[] calldata rampTokenAmounts,
Internal.EVM2AnyTokenTransfer[] calldata onRampTokenTransfers,
Client.EVMTokenAmount[] calldata sourceTokenAmounts
) internal view returns (bytes[] memory destExecDataPerToken) {
bytes4 chainFamilySelector = s_destChainConfigs[destChainSelector].chainFamilySelector;
destExecDataPerToken = new bytes[](rampTokenAmounts.length);
for (uint256 i = 0; i < rampTokenAmounts.length; ++i) {
destExecDataPerToken = new bytes[](onRampTokenTransfers.length);
for (uint256 i = 0; i < onRampTokenTransfers.length; ++i) {
address sourceToken = sourceTokenAmounts[i].token;

// Since the DON has to pay for the extraData to be included on the destination chain, we cap the length of the
// extraData. This prevents gas bomb attacks on the NOPs. As destBytesOverhead accounts for both
// extraData and offchainData, this caps the worst case abuse to the number of bytes reserved for offchainData.
uint256 destPoolDataLength = rampTokenAmounts[i].extraData.length;
uint256 destPoolDataLength = onRampTokenTransfers[i].extraData.length;
if (destPoolDataLength > Pool.CCIP_LOCK_OR_BURN_V1_RET_BYTES) {
if (destPoolDataLength > s_tokenTransferFeeConfig[destChainSelector][sourceToken].destBytesOverhead) {
revert SourceTokenDataTooLarge(sourceToken);
}
}

_validateDestFamilyAddress(chainFamilySelector, rampTokenAmounts[i].destTokenAddress);
_validateDestFamilyAddress(chainFamilySelector, onRampTokenTransfers[i].destTokenAddress);
FeeQuoter.TokenTransferFeeConfig memory tokenTransferFeeConfig =
s_tokenTransferFeeConfig[destChainSelector][sourceToken];
uint32 defaultGasOverhead = s_destChainConfigs[destChainSelector].defaultTokenDestGasOverhead;
Expand Down
4 changes: 2 additions & 2 deletions contracts/src/v0.8/ccip/interfaces/IFeeQuoter.sol
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ interface IFeeQuoter is IPriceRegistry {
/// @param feeToken Fee token address used to pay for message fees
/// @param feeTokenAmount Fee token amount
/// @param extraArgs Message extra args that were passed in by the client
/// @param rampTokenAmounts Token amounts with populated pool return data
/// @param onRampTokenTransfers Token amounts with populated pool return data
/// @param sourceTokenAmounts Token amounts originally sent in a Client.EVM2AnyMessage message
/// @return msgFeeJuels message fee in juels
/// @return isOutOfOrderExecution true if the message should be executed out of order
Expand All @@ -32,7 +32,7 @@ interface IFeeQuoter is IPriceRegistry {
address feeToken,
uint256 feeTokenAmount,
bytes memory extraArgs,
Internal.RampTokenAmount[] calldata rampTokenAmounts,
Internal.EVM2AnyTokenTransfer[] calldata onRampTokenTransfers,
Client.EVMTokenAmount[] calldata sourceTokenAmounts
)
external
Expand Down
50 changes: 32 additions & 18 deletions contracts/src/v0.8/ccip/libraries/Internal.sol
Original file line number Diff line number Diff line change
Expand Up @@ -140,11 +140,12 @@ library Internal {
uint256 public constant ANY_2_EVM_MESSAGE_FIXED_BYTES = 32 * 14;

/// @dev Each token transfer adds 1 RampTokenAmount
/// RampTokenAmount has 5 fields, 3 of which are bytes type, 1 uint256 and 1 uint32.
/// RampTokenAmount has 5 fields, 2 of which are bytes type, 1 Address, 1 uint256 and 1 uint32.
/// Each bytes type takes 1 slot for length, 1 slot for data and 1 slot for the offset.
/// address
/// uint256 amount takes 1 slot.
/// uint32 destGasAmount takes 1 slot.
uint256 public constant ANY_2_EVM_MESSAGE_FIXED_BYTES_PER_TOKEN = 32 * ((3 * 3) + 2);
uint256 public constant ANY_2_EVM_MESSAGE_FIXED_BYTES_PER_TOKEN = 32 * ((2 * 3) + 3);

bytes32 internal constant EVM_2_EVM_MESSAGE_HASH = keccak256("EVM2EVMMessageHashV2");

Expand Down Expand Up @@ -277,12 +278,21 @@ library Internal {
Execution
}

/// @notice Family-agnostic token amounts used for both OnRamp & OffRamp messages
struct RampTokenAmount {
// The source pool address, abi encoded. This value is trusted as it was obtained through the onRamp. It can be
/// @notice Family-agnostic header for OnRamp & OffRamp messages.
/// The messageId is not expected to match hash(message), since it may originate from another ramp family
struct RampMessageHeader {
bytes32 messageId; // Unique identifier for the message, generated with the source chain's encoding scheme (i.e. not necessarily abi.encoded)
uint64 sourceChainSelector; // ──╮ the chain selector of the source chain, note: not chainId
uint64 destChainSelector; // | the chain selector of the destination chain, note: not chainId
uint64 sequenceNumber; // │ sequence number, not unique across lanes
uint64 nonce; // ────────────────╯ nonce for this lane for this sender, not unique across senders/lanes
}

struct EVM2AnyTokenTransfer {
// The source pool EVM address. This value is trusted as it was obtained through the onRamp. It can be
// relied upon by the destination pool to validate the source pool.
bytes sourcePoolAddress;
// The address of the destination token, abi encoded in the case of EVM chains
address sourcePoolAddress;
// The EVM address of the destination token
// This value is UNTRUSTED as any pool owner can return whatever value they want.
bytes destTokenAddress;
// Optional pool data to be transferred to the destination chain. Be default this is capped at
Expand All @@ -291,18 +301,22 @@ library Internal {
bytes extraData;
uint256 amount; // Amount of tokens.
// Destination chain specific execution data encoded in bytes
//(for EVM destination it consists of the amount of gas available for the releaseOrMint and transfer calls on the offRamp
// for an EVM destination, it consists of the amount of gas available for the releaseOrMint
// and transfer calls made by the offRamp
bytes destExecData;
}

/// @notice Family-agnostic header for OnRamp & OffRamp messages.
/// The messageId is not expected to match hash(message), since it may originate from another ramp family
struct RampMessageHeader {
bytes32 messageId; // Unique identifier for the message, generated with the source chain's encoding scheme (i.e. not necessarily abi.encoded)
uint64 sourceChainSelector; // ──╮ the chain selector of the source chain, note: not chainId
uint64 destChainSelector; // | the chain selector of the destination chain, note: not chainId
uint64 sequenceNumber; // │ sequence number, not unique across lanes
uint64 nonce; // ────────────────╯ nonce for this lane for this sender, not unique across senders/lanes
struct Any2EVMTokenTransfer {
// The source pool EVM address encoded to bytes. This value is trusted as it is obtained through the onRamp. It can be
// relied upon by the destination pool to validate the source pool.
bytes sourcePoolAddress;
address destTokenAddress; // ───╮ Address of destination token
uint32 destGasAmount; //────────╯ The amount of gas available for the releaseOrMint and transfer calls on the offRamp.
// Optional pool data to be transferred to the destination chain. Be default this is capped at
// CCIP_LOCK_OR_BURN_V1_RET_BYTES bytes. If more data is required, the TokenTransferFeeConfig.destBytesOverhead
// has to be set for the specific token.
bytes extraData;
uint256 amount; // Amount of tokens.
}

/// @notice Family-agnostic message routed to an OffRamp
Expand All @@ -314,7 +328,7 @@ library Internal {
bytes data; // arbitrary data payload supplied by the message sender
address receiver; // receiver address on the destination chain
uint256 gasLimit; // user supplied maximum gas amount available for dest chain execution
RampTokenAmount[] tokenAmounts; // array of tokens and amounts to transfer
Any2EVMTokenTransfer[] tokenAmounts; // array of tokens and amounts to transfer
}

/// @notice Family-agnostic message emitted from the OnRamp
Expand All @@ -328,7 +342,7 @@ library Internal {
bytes extraArgs; // destination-chain specific extra args, such as the gasLimit for EVM chains
address feeToken; // fee token
uint256 feeTokenAmount; // fee token amount
RampTokenAmount[] tokenAmounts; // array of tokens and amounts to transfer
EVM2AnyTokenTransfer[] tokenAmounts; // array of tokens and amounts to transfer
}

// bytes4(keccak256("CCIP ChainFamilySelector EVM"))
Expand Down
14 changes: 7 additions & 7 deletions contracts/src/v0.8/ccip/offRamp/OffRamp.sol
Original file line number Diff line number Diff line change
Expand Up @@ -299,7 +299,7 @@ contract OffRamp is ITypeAndVersion, MultiOCR3Base {
for (uint256 tokenIndex = 0; tokenIndex < message.tokenAmounts.length; ++tokenIndex) {
uint256 tokenGasOverride = msgGasLimitOverrides[msgIndex].tokenGasOverrides[tokenIndex];
if (tokenGasOverride != 0) {
uint32 destGasAmount = abi.decode(message.tokenAmounts[tokenIndex].destExecData, (uint32));
uint256 destGasAmount = message.tokenAmounts[tokenIndex].destGasAmount;
if (tokenGasOverride < destGasAmount) {
revert InvalidManualExecutionTokenGasOverride(
message.header.messageId, tokenIndex, destGasAmount, tokenGasOverride
Expand Down Expand Up @@ -620,15 +620,16 @@ contract OffRamp is ITypeAndVersion, MultiOCR3Base {
/// @param offchainTokenData Data fetched offchain by the DON.
/// @return destTokenAmount local token address with amount
function _releaseOrMintSingleToken(
Internal.RampTokenAmount memory sourceTokenAmount,
Internal.Any2EVMTokenTransfer memory sourceTokenAmount,
bytes memory originalSender,
address receiver,
uint64 sourceChainSelector,
bytes memory offchainTokenData
) internal returns (Client.EVMTokenAmount memory destTokenAmount) {
// We need to safely decode the token address from the sourceTokenData, as it could be wrong,
// in which case it doesn't have to be a valid EVM address.
address localToken = Internal._validateEVMAddress(sourceTokenAmount.destTokenAddress);
// We assume this destTokenAddress has already been fully validated by a (trusted) OnRamp.
address localToken = sourceTokenAmount.destTokenAddress;
// We check with the token admin registry if the token has a pool on this chain.
address localPoolAddress = ITokenAdminRegistry(i_tokenAdminRegistry).getPool(localToken);
// This will call the supportsInterface through the ERC165Checker, and not directly on the pool address.
Expand All @@ -640,8 +641,7 @@ contract OffRamp is ITypeAndVersion, MultiOCR3Base {
}

// We retrieve the local token balance of the receiver before the pool call.
(uint256 balancePre, uint256 gasLeft) =
_getBalanceOfReceiver(receiver, localToken, abi.decode(sourceTokenAmount.destExecData, (uint32)));
(uint256 balancePre, uint256 gasLeft) = _getBalanceOfReceiver(receiver, localToken, sourceTokenAmount.destGasAmount);

// We determined that the pool address is a valid EVM address, but that does not mean the code at this
// address is a (compatible) pool contract. _callWithExactGasSafeReturnData will check if the location
Expand Down Expand Up @@ -733,7 +733,7 @@ contract OffRamp is ITypeAndVersion, MultiOCR3Base {
/// any non-rate limiting errors that may occur. If we encounter a rate limiting related error
/// we bubble it up. If we encounter a non-rate limiting error we wrap it in a TokenHandlingError.
function _releaseOrMintTokens(
Internal.RampTokenAmount[] memory sourceTokenAmounts,
Internal.Any2EVMTokenTransfer[] memory sourceTokenAmounts,
bytes memory originalSender,
address receiver,
uint64 sourceChainSelector,
Expand All @@ -745,7 +745,7 @@ contract OffRamp is ITypeAndVersion, MultiOCR3Base {
for (uint256 i = 0; i < sourceTokenAmounts.length; ++i) {
if (!isTokenGasOverridesEmpty) {
if (tokenGasOverrides[i] != 0) {
sourceTokenAmounts[i].destExecData = abi.encode(tokenGasOverrides[i]);
sourceTokenAmounts[i].destGasAmount = tokenGasOverrides[i];
}
}
destTokenAmounts[i] = _releaseOrMintSingleToken(
Expand Down
10 changes: 5 additions & 5 deletions contracts/src/v0.8/ccip/onRamp/OnRamp.sol
Original file line number Diff line number Diff line change
Expand Up @@ -212,7 +212,7 @@ contract OnRamp is IEVM2AnyOnRampClient, ITypeAndVersion, OwnerIsCreator {
feeToken: message.feeToken,
feeTokenAmount: feeTokenAmount,
// Should be populated via lock / burn pool calls
tokenAmounts: new Internal.RampTokenAmount[](message.tokenAmounts.length)
tokenAmounts: new Internal.EVM2AnyTokenTransfer[](message.tokenAmounts.length)
});

// Lock / burn the tokens as last step. TokenPools may not always be trusted.
Expand Down Expand Up @@ -267,13 +267,13 @@ contract OnRamp is IEVM2AnyOnRampClient, ITypeAndVersion, OwnerIsCreator {
/// @param destChainSelector Target destination chain selector of the message
/// @param receiver Message receiver
/// @param originalSender Message sender
/// @return rampTokenAmount Ramp token and amount data
/// @return evm2AnyTokenTransfer EVM2Any token and amount data
function _lockOrBurnSingleToken(
Client.EVMTokenAmount memory tokenAndAmount,
uint64 destChainSelector,
bytes memory receiver,
address originalSender
) internal returns (Internal.RampTokenAmount memory) {
) internal returns (Internal.EVM2AnyTokenTransfer memory) {
if (tokenAndAmount.amount == 0) revert CannotSendZeroTokens();

IPoolV1 sourcePool = getPoolBySourceToken(destChainSelector, IERC20(tokenAndAmount.token));
Expand All @@ -295,8 +295,8 @@ contract OnRamp is IEVM2AnyOnRampClient, ITypeAndVersion, OwnerIsCreator {
);

// NOTE: pool data validations are outsourced to the FeeQuoter to handle family-specific logic handling
return Internal.RampTokenAmount({
sourcePoolAddress: abi.encode(sourcePool),
return Internal.EVM2AnyTokenTransfer({
sourcePoolAddress: address(sourcePool),
destTokenAddress: poolReturnData.destTokenAddress,
extraData: poolReturnData.destPoolData,
amount: tokenAndAmount.amount,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -267,8 +267,16 @@ contract MultiRampsE2E is OnRampSetup, OffRampSetup {
router.ccipSend(DEST_CHAIN_SELECTOR, message);
vm.pauseGasMetering();

Internal.Any2EVMTokenTransfer[] memory any2EVMTokenTransfer =
new Internal.Any2EVMTokenTransfer[](message.tokenAmounts.length);
for (uint256 i = 0; i < msgEvent.tokenAmounts.length; ++i) {
msgEvent.tokenAmounts[i].destExecData = abi.encode(MAX_TOKEN_POOL_RELEASE_OR_MINT_GAS);
any2EVMTokenTransfer[i] = Internal.Any2EVMTokenTransfer({
sourcePoolAddress: abi.encode(msgEvent.tokenAmounts[i].sourcePoolAddress),
destTokenAddress: abi.decode(msgEvent.tokenAmounts[i].destTokenAddress, (address)),
extraData: msgEvent.tokenAmounts[i].extraData,
amount: msgEvent.tokenAmounts[i].amount,
destGasAmount: abi.decode(msgEvent.tokenAmounts[i].destExecData, (uint32))
});
}

return Internal.Any2EVMRampMessage({
Expand All @@ -283,7 +291,7 @@ contract MultiRampsE2E is OnRampSetup, OffRampSetup {
data: msgEvent.data,
receiver: abi.decode(msgEvent.receiver, (address)),
gasLimit: s_feeQuoter.parseEVMExtraArgsFromBytes(msgEvent.extraArgs, DEST_CHAIN_SELECTOR).gasLimit,
tokenAmounts: msgEvent.tokenAmounts
tokenAmounts: any2EVMTokenTransfer
});
}
}
Loading

0 comments on commit ef3e366

Please sign in to comment.