Skip to content

Commit

Permalink
USDCPool review (#259)
Browse files Browse the repository at this point in the history
  • Loading branch information
connorwstein authored Nov 7, 2023
1 parent 77d0fdf commit 35feee9
Show file tree
Hide file tree
Showing 11 changed files with 187 additions and 273 deletions.
25 changes: 10 additions & 15 deletions contracts/gas-snapshots/ccip.gas-snapshot
Original file line number Diff line number Diff line change
Expand Up @@ -406,19 +406,14 @@ TokenProxy_getFee:testGetFeeGasShouldBeZeroReverts() (gas: 16746)
TokenProxy_getFee:testGetFeeInvalidTokenReverts() (gas: 12628)
TokenProxy_getFee:testGetFeeNoDataAllowedReverts() (gas: 15775)
TokenProxy_getFee:testGetFeeSuccess() (gas: 74453)
USDCTokenPool__validateMessage:testValidateInvalidMessageReverts() (gas: 25338)
USDCTokenPool_lockOrBurn:testLockOrBurnSuccess() (gas: 92355)
USDCTokenPool_lockOrBurn:testLockOrBurnWithAllowListReverts() (gas: 21946)
USDCTokenPool_lockOrBurn:testPermissionsErrorReverts() (gas: 12476)
USDCTokenPool_lockOrBurn:testUnknownDomainReverts() (gas: 183544)
USDCTokenPool_releaseOrMint:testReleaseOrMintRealTxSuccess() (gas: 56035)
USDCTokenPool_releaseOrMint:testTokenMaxCapacityExceededReverts() (gas: 27922)
USDCTokenPool_releaseOrMint:testUnlockingUSDCFailedReverts() (gas: 50764)
USDCTokenPool_setConfig:testInvalidConfigReverts() (gas: 15241)
USDCTokenPool_setConfig:testInvalidMessageVersionReverts() (gas: 11741)
USDCTokenPool_setConfig:testInvalidTokenMessengerVersionReverts() (gas: 287496)
USDCTokenPool_setConfig:testOnlyOwnerReverts() (gas: 15709)
USDCTokenPool_setConfig:testSetConfigSuccess() (gas: 352451)
USDCTokenPool_setDomains:testInvalidDomainReverts() (gas: 66100)
USDCTokenPool_setDomains:testOnlyOwnerReverts() (gas: 15427)
USDCTokenPool__validateMessage:testValidateInvalidMessageReverts() (gas: 25228)
USDCTokenPool_lockOrBurn:testLockOrBurnSuccess() (gas: 116047)
USDCTokenPool_lockOrBurn:testLockOrBurnWithAllowListReverts() (gas: 21898)
USDCTokenPool_lockOrBurn:testPermissionsErrorReverts() (gas: 12452)
USDCTokenPool_lockOrBurn:testUnknownDomainReverts() (gas: 183683)
USDCTokenPool_releaseOrMint:testReleaseOrMintRealTxSuccess() (gas: 53908)
USDCTokenPool_releaseOrMint:testTokenMaxCapacityExceededReverts() (gas: 27874)
USDCTokenPool_releaseOrMint:testUnlockingUSDCFailedReverts() (gas: 45849)
USDCTokenPool_setDomains:testInvalidDomainReverts() (gas: 66064)
USDCTokenPool_setDomains:testOnlyOwnerReverts() (gas: 15415)
USDCTokenPool_supportsInterface:testSupportsInterfaceSuccess() (gas: 8405)
2 changes: 2 additions & 0 deletions contracts/src/v0.8/ccip/onRamp/EVM2EVMOnRamp.sol
Original file line number Diff line number Diff line change
Expand Up @@ -351,6 +351,8 @@ contract EVM2EVMOnRamp is IEVM2AnyOnRamp, ILinkAvailable, AggregateRateLimiter,
newMessage.messageId = Internal._hash(newMessage, i_metadataHash);

// Emit message request
// Note this must happen after pools, some tokens (eg USDC) emit events that we
// expect to directly precede this event.
emit CCIPSendRequested(newMessage);
return newMessage.messageId;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
*/
pragma solidity ^0.8.0;

interface IMessageReceiver {
interface IMessageTransmitter {
/// @notice Unlocks USDC tokens on the destination chain
/// @param message The original message on the source chain
/// * Message format:
Expand All @@ -35,4 +35,12 @@ interface IMessageReceiver {
/// If incorrect number of signatures or duplicate signatures are supplied,
/// signature verification will fail.
function receiveMessage(bytes calldata message, bytes calldata attestation) external returns (bool success);

/// Returns domain of chain on which the contract is deployed.
/// @dev immutable
function localDomain() external view returns (uint32);

/// Returns message format version.
/// @dev immutable
function version() external view returns (uint32);
}
7 changes: 6 additions & 1 deletion contracts/src/v0.8/ccip/pools/USDC/ITokenMessenger.sol
Original file line number Diff line number Diff line change
Expand Up @@ -56,5 +56,10 @@ interface ITokenMessenger {

/// Returns the version of the message body format.
/// @dev immutable
function messageBodyVersion() external returns (uint32);
function messageBodyVersion() external view returns (uint32);

/// Returns local Message Transmitter responsible for sending and receiving messages
/// to/from remote domainsmessage transmitter for this token messenger.
/// @dev immutable
function localMessageTransmitter() external view returns (address);
}
84 changes: 31 additions & 53 deletions contracts/src/v0.8/ccip/pools/USDC/USDCTokenPool.sol
Original file line number Diff line number Diff line change
Expand Up @@ -2,22 +2,22 @@
pragma solidity 0.8.19;

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

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

import {IERC20} from "../../../vendor/openzeppelin-solidity/v4.8.0/contracts/token/ERC20/IERC20.sol";
import {SafeERC20} from "../../../vendor/openzeppelin-solidity/v4.8.0/contracts/token/ERC20/utils/SafeERC20.sol";
import {IERC165} from "../../../vendor/openzeppelin-solidity/v4.8.0/contracts/utils/introspection/IERC165.sol";

/// @notice This pool mints and burns USDC tokens through the Cross Chain Transfer
/// Protocol (CCTP).
contract USDCTokenPool is TokenPool, ITypeAndVersion {
using SafeERC20 for IERC20;

event DomainsSet(DomainUpdate[]);
event ConfigSet(USDCConfig);
event ConfigSet(address tokenMessenger);

error UnknownDomain(uint64 domain);
error UnlockingUSDCFailed();
Expand All @@ -44,13 +44,6 @@ contract USDCTokenPool is TokenPool, ITypeAndVersion {
bool enabled; // ─────────────╯ Whether the domain is enabled
}

// Contains the contracts for sending and receiving USDC tokens
struct USDCConfig {
uint32 version; // ──────────╮ CCTP internal version
address tokenMessenger; // ──╯ Contract to burn tokens
address messageTransmitter; // Contract to mint tokens
}

struct SourceTokenDataPayload {
uint64 nonce;
uint32 sourceDomain;
Expand All @@ -59,16 +52,20 @@ contract USDCTokenPool is TokenPool, ITypeAndVersion {
// solhint-disable-next-line chainlink-solidity/all-caps-constant-storage-variables
string public constant override typeAndVersion = "USDCTokenPool 1.2.0";

uint32 public immutable i_localDomainIdentifier;
// We restrict to the first version. New pool may be required for subsequent versions.
uint32 public constant SUPPORTED_USDC_VERSION = 0;

// The local USDC config
USDCConfig private s_config;
ITokenMessenger public immutable i_tokenMessenger;
IMessageTransmitter public immutable i_messageTransmitter;
uint32 public immutable i_localDomainIdentifier;

// The unique USDC pool flag to signal through EIP 165 that this is a USDC token pool.
bytes4 private constant USDC_INTERFACE_ID = bytes4(keccak256("USDC"));

// A domain is a USDC representation of a chain.
/// A domain is a USDC representation of a destination chain.
/// @dev Zero is a valid domain identifier.
/// @dev The address to mint on the destination chain is the corresponding USDC pool.
struct Domain {
bytes32 allowedCaller; // Address allowed to mint on the domain
uint32 domainIdentifier; // ─╮ Unique domain ID
Expand All @@ -79,22 +76,31 @@ contract USDCTokenPool is TokenPool, ITypeAndVersion {
mapping(uint64 chainSelector => Domain CCTPDomain) private s_chainToDomain;

constructor(
USDCConfig memory config,
IBurnMintERC20 token,
ITokenMessenger tokenMessenger,
IERC20 token,
address[] memory allowlist,
address armProxy,
uint32 localDomainIdentifier
address armProxy
) TokenPool(token, allowlist, armProxy) {
_setConfig(config);
i_localDomainIdentifier = localDomainIdentifier;
if (address(tokenMessenger) == address(0)) revert InvalidConfig();
IMessageTransmitter transmitter = IMessageTransmitter(tokenMessenger.localMessageTransmitter());
uint32 transmitterVersion = transmitter.version();
if (transmitterVersion != SUPPORTED_USDC_VERSION) revert InvalidMessageVersion(transmitterVersion);
uint32 tokenMessengerVersion = tokenMessenger.messageBodyVersion();
if (tokenMessengerVersion != SUPPORTED_USDC_VERSION) revert InvalidTokenMessengerVersion(tokenMessengerVersion);

i_tokenMessenger = tokenMessenger;
i_messageTransmitter = transmitter;
i_localDomainIdentifier = transmitter.localDomain();
i_token.safeApprove(address(i_tokenMessenger), type(uint256).max);
emit ConfigSet(address(tokenMessenger));
}

/// @notice returns the USDC interface flag used for EIP165 identification.
function getUSDCInterfaceId() public pure returns (bytes4) {
return USDC_INTERFACE_ID;
}

// @inheritdoc IERC165
/// @inheritdoc IERC165
function supportsInterface(bytes4 interfaceId) public pure override returns (bool) {
return interfaceId == USDC_INTERFACE_ID || super.supportsInterface(interfaceId);
}
Expand All @@ -104,6 +110,7 @@ contract USDCTokenPool is TokenPool, ITypeAndVersion {
/// Benefits of rate limiting here does not justify the extra gas cost.
/// @param amount Amount to burn
/// @dev emits ITokenMessenger.DepositForBurn
/// @dev Assumes caller has validated destinationReceiver
function lockOrBurn(
address originalSender,
bytes calldata destinationReceiver,
Expand All @@ -118,7 +125,7 @@ contract USDCTokenPool is TokenPool, ITypeAndVersion {
// Since this pool is the msg sender of the CCTP transaction, only this contract
// is able to call replaceDepositForBurn. Since this contract does not implement
// replaceDepositForBurn, the tokens cannot be maliciously re-routed to another address.
uint64 nonce = ITokenMessenger(s_config.tokenMessenger).depositForBurnWithCaller(
uint64 nonce = i_tokenMessenger.depositForBurnWithCaller(
amount,
domain.domainIdentifier,
receiver,
Expand Down Expand Up @@ -157,12 +164,8 @@ contract USDCTokenPool is TokenPool, ITypeAndVersion {

_validateMessage(msgAndAttestation.message, sourceTokenData);

if (
!IMessageReceiver(s_config.messageTransmitter).receiveMessage(
msgAndAttestation.message,
msgAndAttestation.attestation
)
) revert UnlockingUSDCFailed();
if (!i_messageTransmitter.receiveMessage(msgAndAttestation.message, msgAndAttestation.attestation))
revert UnlockingUSDCFailed();
emit Minted(msg.sender, receiver, amount);
}

Expand Down Expand Up @@ -215,38 +218,13 @@ contract USDCTokenPool is TokenPool, ITypeAndVersion {
// │ Config │
// ================================================================

/// @notice Gets the current config
function getConfig() external view returns (USDCConfig memory) {
return s_config;
}

/// @notice Sets the config
function setConfig(USDCConfig memory config) external onlyOwner {
_setConfig(config);
}

/// @notice Sets the config
function _setConfig(USDCConfig memory config) internal {
if (config.version != SUPPORTED_USDC_VERSION) revert InvalidMessageVersion(config.version);
if (config.messageTransmitter == address(0) || config.tokenMessenger == address(0)) revert InvalidConfig();
uint32 tokenMessengerVersion = ITokenMessenger(config.tokenMessenger).messageBodyVersion();
if (tokenMessengerVersion != SUPPORTED_USDC_VERSION) revert InvalidTokenMessengerVersion(tokenMessengerVersion);

// Revoke approval for previous token messenger
if (s_config.tokenMessenger != address(0)) i_token.safeApprove(s_config.tokenMessenger, 0);
// Approve new token messenger. New tokenMessenger must have an allowance of 0, otherwise safeApprove reverts.
// Since we set allowance to 0 for existing tokenMessenger before approving a new one, this condition is always met.
i_token.safeApprove(config.tokenMessenger, type(uint256).max);
s_config = config;
emit ConfigSet(config);
}

/// @notice Gets the CCTP domain for a given CCIP chain selector.
function getDomain(uint64 chainSelector) external view returns (Domain memory) {
return s_chainToDomain[chainSelector];
}

/// @notice Sets the CCTP domain for a CCIP chain selector.
/// @dev Must verify mapping of selectors -> (domain, caller) offchain.
function setDomains(DomainUpdate[] calldata domains) external onlyOwner {
for (uint256 i = 0; i < domains.length; ++i) {
DomainUpdate memory domain = domains[i];
Expand Down
8 changes: 4 additions & 4 deletions contracts/src/v0.8/ccip/test/helpers/USDCTokenPoolHelper.sol
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,15 @@ pragma solidity 0.8.19;
import {IBurnMintERC20} from "../../../shared/token/ERC20/IBurnMintERC20.sol";

import {USDCTokenPool} from "../../pools/USDC/USDCTokenPool.sol";
import {ITokenMessenger} from "../../pools/USDC/ITokenMessenger.sol";

contract USDCTokenPoolHelper is USDCTokenPool {
constructor(
USDCConfig memory config,
ITokenMessenger tokenMessenger,
IBurnMintERC20 token,
address[] memory allowlist,
address armProxy,
uint32 localDomainIdentifier
) USDCTokenPool(config, token, allowlist, armProxy, localDomainIdentifier) {}
address armProxy
) USDCTokenPool(tokenMessenger, token, allowlist, armProxy) {}

function validateMessage(bytes memory usdcMessage, SourceTokenDataPayload memory sourceTokenData) external view {
return _validateMessage(usdcMessage, sourceTokenData);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,22 +2,21 @@
pragma solidity 0.8.19;

import {ITokenMessenger} from "../../pools/USDC/ITokenMessenger.sol";
import {IMessageReceiver} from "../../pools/USDC/IMessageReceiver.sol";
import {IMessageTransmitter} from "../../pools/USDC/IMessageTransmitter.sol";
import {IBurnMintERC20} from "../../../shared/token/ERC20/IBurnMintERC20.sol";

// This contract mocks both the ITokenMessenger and IMessageReceiver
// This contract mocks both the ITokenMessenger and IMessageTransmitter
// contracts involved with the Cross Chain Token Protocol.
contract MockUSDC is ITokenMessenger, IMessageReceiver {
contract MockUSDCTokenMessenger is ITokenMessenger {
uint32 private immutable i_messageBodyVersion;
bytes32 public constant i_destinationTokenMessenger = keccak256("i_destinationTokenMessenger");

// Indicated whether the receiveMessage() call should succeed.
bool public s_shouldSucceed;
uint64 public s_nonce;
address private i_transmitter;

constructor(uint32 version) {
constructor(uint32 version, address transmitter) {
i_messageBodyVersion = version;
s_nonce = 1;
s_shouldSucceed = true;
i_transmitter = transmitter;
}

function depositForBurnWithCaller(
Expand All @@ -27,6 +26,8 @@ contract MockUSDC is ITokenMessenger, IMessageReceiver {
address burnToken,
bytes32 destinationCaller
) external returns (uint64) {
IBurnMintERC20(burnToken).transferFrom(msg.sender, address(this), amount);
IBurnMintERC20(burnToken).burn(amount);
emit DepositForBurn(
s_nonce,
burnToken,
Expand All @@ -40,15 +41,11 @@ contract MockUSDC is ITokenMessenger, IMessageReceiver {
return s_nonce++;
}

function receiveMessage(bytes calldata, bytes calldata) external view returns (bool success) {
return s_shouldSucceed;
}

function messageBodyVersion() external view returns (uint32) {
return i_messageBodyVersion;
}

function setShouldSucceed(bool shouldSucceed) external {
s_shouldSucceed = shouldSucceed;
function localMessageTransmitter() external view returns (address) {
return i_transmitter;
}
}
33 changes: 33 additions & 0 deletions contracts/src/v0.8/ccip/test/mocks/MockUSDCTransmitter.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.0;

import "../../pools/USDC/IMessageTransmitter.sol";

contract MockUSDCTransmitter is IMessageTransmitter {
// Indicated whether the receiveMessage() call should succeed.
bool public s_shouldSucceed;
uint32 private immutable i_version;
uint32 private immutable i_localDomain;

constructor(uint32 version, uint32 localDomain) {
i_version = version;
i_localDomain = localDomain;
s_shouldSucceed = true;
}

function receiveMessage(bytes calldata, bytes calldata) external view returns (bool success) {
return s_shouldSucceed;
}

function setShouldSucceed(bool shouldSucceed) external {
s_shouldSucceed = shouldSucceed;
}

function version() external view returns (uint32) {
return i_version;
}

function localDomain() external view returns (uint32) {
return i_localDomain;
}
}
Loading

0 comments on commit 35feee9

Please sign in to comment.