diff --git a/contracts/gas-snapshots/ccip.gas-snapshot b/contracts/gas-snapshots/ccip.gas-snapshot index 8c7653acfb..3935e13fcd 100644 --- a/contracts/gas-snapshots/ccip.gas-snapshot +++ b/contracts/gas-snapshots/ccip.gas-snapshot @@ -34,10 +34,16 @@ BurnWithFromMintTokenPool_lockOrBurn:test_ChainNotAllowed_Revert() (gas: 28675) BurnWithFromMintTokenPool_lockOrBurn:test_PoolBurnRevertNotHealthy_Revert() (gas: 55158) BurnWithFromMintTokenPool_lockOrBurn:test_PoolBurn_Success() (gas: 243568) BurnWithFromMintTokenPool_lockOrBurn:test_Setup_Success() (gas: 24260) -CCIPClientTest:test_ccipReceiveAndSendAck_Success() (gas: 331839) -CCIPClientTest:test_ccipSendAndReceiveAck_in_return_Success() (gas: 348275) +CCIPClientTest:test_HappyPath_Success() (gas: 192504) +CCIPClientTest:test_ccipReceiveAndSendAck_Success() (gas: 331795) +CCIPClientTest:test_ccipReceiver_ack_with_invalidAckMessageHeaderBytes_Revert() (gas: 438714) +CCIPClientTest:test_ccipSendAndReceiveAck_in_return_Success() (gas: 348231) +CCIPClientTest:test_ccipSend_withNonNativeFeetoken_andDestTokens_Success() (gas: 325792) +CCIPClientTest:test_ccipSend_withNonNativeFeetoken_andNoDestTokens_Success() (gas: 218834) CCIPClientTest:test_ccipSend_withNonNativeFeetoken_andNoDestTokens_Success() (gas: 241532) -CCIPClientTest:test_send_tokens_that_are_not_feeToken_Success() (gas: 552173) +CCIPClientTest:test_ccipSend_with_NativeFeeToken_andDestTokens_Success() (gas: 376797) +CCIPClientTest:test_modifyFeeToken_Success() (gas: 74452) +CCIPClientTest:test_send_tokens_that_are_not_feeToken_Success() (gas: 552182) CCIPConfigSetup:test_getCapabilityConfiguration_Success() (gas: 9495) CCIPConfig_ConfigStateMachine:test__computeConfigDigest_Success() (gas: 70755) CCIPConfig_ConfigStateMachine:test__computeNewConfigWithMeta_InitToRunning_Success() (gas: 357994) @@ -96,14 +102,14 @@ CCIPReceiverTest:test_modifyRouter_Success() (gas: 26446) CCIPReceiverTest:test_removeSender_from_approvedList_and_revert_Success() (gas: 427653) CCIPReceiverTest:test_retryFailedMessage_Success() (gas: 454419) CCIPReceiverTest:test_withdraw_nativeToken_to_owner_Success() (gas: 20667) -CCIPReceiverWithAckTest:test_ccipReceive_ack_message_Success() (gas: 56277) -CCIPReceiverWithAckTest:test_ccipReceive_and_respond_with_ack_Success() (gas: 331828) -CCIPReceiverWithAckTest:test_ccipReceiver_ack_with_invalidAckMessageHeaderBytes_Revert() (gas: 438737) +CCIPReceiverWithAckTest:test_ccipReceive_ack_message_Success() (gas: 56278) +CCIPReceiverWithAckTest:test_ccipReceive_and_respond_with_ack_Success() (gas: 331829) +CCIPReceiverWithAckTest:test_ccipReceiver_ack_with_invalidAckMessageHeaderBytes_Revert() (gas: 438738) CCIPReceiverWithAckTest:test_feeTokenApproval_in_constructor_Success() (gas: 2956788) -CCIPReceiverWithAckTest:test_modifyFeeToken_Success() (gas: 74835) -CCIPSenderTest:test_ccipSend_withNonNativeFeetoken_andDestTokens() (gas: 339572) -CCIPSenderTest:test_ccipSend_withNonNativeFeetoken_andNoDestTokens() (gas: 224467) -CCIPSenderTest:test_ccipSend_with_NativeFeeToken_andDestTokens() (gas: 368160) +CCIPReceiverWithAckTest:test_modifyFeeToken_Success() (gas: 74867) +CCIPSenderTest:test_ccipSend_withNonNativeFeetoken_andDestTokens_Success() (gas: 339615) +CCIPSenderTest:test_ccipSend_withNonNativeFeetoken_andNoDestTokens_Success() (gas: 224489) +CCIPSenderTest:test_ccipSend_with_NativeFeeToken_andDestTokens_Success() (gas: 368159) CommitStore_constructor:test_Constructor_Success() (gas: 3091326) CommitStore_isUnpausedAndRMNHealthy:test_RMN_Success() (gas: 73420) CommitStore_report:test_InvalidIntervalMinLargerThanMax_Revert() (gas: 28670) @@ -693,9 +699,10 @@ OCR2Base_transmit:test_UnAuthorizedTransmitter_Revert() (gas: 23484) OCR2Base_transmit:test_UnauthorizedSigner_Revert() (gas: 39665) OCR2Base_transmit:test_WrongNumberOfSignatures_Revert() (gas: 20557) OnRampTokenPoolReentrancy:test_OnRampTokenPoolReentrancy_Success() (gas: 380711) -PingPong_example_ccipReceive:test_CcipReceive_Success() (gas: 307350) -PingPong_example_plumbing:test_Pausing_Success() (gas: 17898) -PingPong_example_startPingPong:test_StartPingPong_Success() (gas: 234292) +PingPong_example_ccipReceive:test_CcipReceive_Success() (gas: 307328) +PingPong_example_plumbing:test_Pausing_Success() (gas: 17943) +PingPong_example_plumbing:test_typeAndVersion() (gas: 9745) +PingPong_example_startPingPong:test_StartPingPong_Success() (gas: 234270) PriceRegistry_applyFeeTokensUpdates:test_ApplyFeeTokensUpdates_Success() (gas: 79823) PriceRegistry_applyFeeTokensUpdates:test_OnlyCallableByOwner_Revert() (gas: 12580) PriceRegistry_constructor:test_InvalidStalenessThreshold_Revert() (gas: 67418) @@ -842,8 +849,8 @@ Router_routeMessage:test_ManualExec_Success() (gas: 35381) Router_routeMessage:test_OnlyOffRamp_Revert() (gas: 25116) Router_routeMessage:test_WhenNotHealthy_Revert() (gas: 44724) Router_setWrappedNative:test_OnlyOwner_Revert() (gas: 10985) -SelfFundedPingPong_ccipReceive:test_FundingIfNotANop_Revert() (gas: 289562) -SelfFundedPingPong_ccipReceive:test_Funding_Success() (gas: 448109) +SelfFundedPingPong_ccipReceive:test_FundingIfNotANop_Revert() (gas: 289627) +SelfFundedPingPong_ccipReceive:test_Funding_Success() (gas: 448499) SelfFundedPingPong_setCountIncrBeforeFunding:test_setCountIncrBeforeFunding() (gas: 20203) TokenAdminRegistry_acceptAdminRole:test_acceptAdminRole_OnlyPendingAdministrator_Revert() (gas: 51085) TokenAdminRegistry_acceptAdminRole:test_acceptAdminRole_Success() (gas: 43947) diff --git a/contracts/src/v0.8/ccip/applications/external/CCIPClient.sol b/contracts/src/v0.8/ccip/applications/external/CCIPClient.sol index c97b69302b..6643994325 100644 --- a/contracts/src/v0.8/ccip/applications/external/CCIPClient.sol +++ b/contracts/src/v0.8/ccip/applications/external/CCIPClient.sol @@ -18,9 +18,12 @@ contract CCIPClient is CCIPReceiver { IERC20 public s_feeToken; event MessageSent(bytes32 messageId); + event FeeTokenUpdated(address oldFeeToken, address newFeeToken); constructor(address router, IERC20 feeToken) CCIPReceiver(router) { s_feeToken = feeToken; + + IERC20(s_feeToken).safeApprove(s_ccipRouter, type(uint256).max); } /// @notice sends a message through CCIP to the router @@ -79,4 +82,21 @@ contract CCIPClient is CCIPReceiver { isValidSender(message.sourceChainSelector, message.sender) isValidChain(message.sourceChainSelector) {} + + function updateFeeToken(address token) external onlyOwner { + // If the current fee token is not-native, zero out the allowance to the router for safety + if (address(s_feeToken) != address(0)) { + s_feeToken.safeApprove(getRouter(), 0); + } + + address oldFeeToken = address(s_feeToken); + s_feeToken = IERC20(token); + + // Approve the router to spend the new fee token + if (token != address(0)) { + s_feeToken.safeIncreaseAllowance(getRouter(), type(uint256).max); + } + + emit FeeTokenUpdated(oldFeeToken, token); + } } diff --git a/contracts/src/v0.8/ccip/applications/external/CCIPReceiverWithACK.sol b/contracts/src/v0.8/ccip/applications/external/CCIPReceiverWithACK.sol index 31d164fe1c..c2f9639b83 100644 --- a/contracts/src/v0.8/ccip/applications/external/CCIPReceiverWithACK.sol +++ b/contracts/src/v0.8/ccip/applications/external/CCIPReceiverWithACK.sol @@ -24,7 +24,7 @@ contract CCIPReceiverWithACK is CCIPReceiver { event MessageSent(bytes32 indexed incomingMessageId, bytes32 indexed ACKMessageId); event MessageAckReceived(bytes32 messageId); - event FeeTokenModified(address indexed oldToken, address indexed newToken); + event FeeTokenUpdated(address indexed oldToken, address indexed newToken); enum MessageType { OUTGOING, // Indicates that a message is being sent for the first time to its recipient. @@ -61,7 +61,7 @@ contract CCIPReceiverWithACK is CCIPReceiver { } } - function modifyFeeToken(address token) external onlyOwner { + function updateFeeToken(address token) external onlyOwner { // If the current fee token is not-native, zero out the allowance to the router for safety if (address(s_feeToken) != address(0)) { s_feeToken.safeApprove(getRouter(), 0); @@ -75,7 +75,7 @@ contract CCIPReceiverWithACK is CCIPReceiver { s_feeToken.safeIncreaseAllowance(getRouter(), type(uint256).max); } - emit FeeTokenModified(oldFeeToken, token); + emit FeeTokenUpdated(oldFeeToken, token); } /// @notice Application-specific logic for incoming ccip messages. diff --git a/contracts/src/v0.8/ccip/test/applications/external/CCIPClientTest.t.sol b/contracts/src/v0.8/ccip/test/applications/external/CCIPClientTest.t.sol new file mode 100644 index 0000000000..79b48826b3 --- /dev/null +++ b/contracts/src/v0.8/ccip/test/applications/external/CCIPClientTest.t.sol @@ -0,0 +1,171 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import {CCIPBase} from "../../../applications/external/CCIPBase.sol"; +import {CCIPClient} from "../../../applications/external/CCIPClient.sol"; + +import {Client} from "../../../libraries/Client.sol"; +import {EVM2EVMOnRampSetup} from "../../onRamp/EVM2EVMOnRampSetup.t.sol"; + +import {IERC20} from "../../../../vendor/openzeppelin-solidity/v4.8.3/contracts/token/ERC20/IERC20.sol"; +import {IRouterClient} from "../../../interfaces/IRouterClient.sol"; + +contract CCIPClientTest is EVM2EVMOnRampSetup { + event MessageFailed(bytes32 indexed messageId, bytes reason); + event MessageSucceeded(bytes32 indexed messageId); + event MessageRecovered(bytes32 indexed messageId); + + CCIPClient internal s_sender; + + function setUp() public virtual override { + EVM2EVMOnRampSetup.setUp(); + + s_sender = new CCIPClient(address(s_sourceRouter), IERC20(s_sourceFeeToken)); + + CCIPBase.ChainUpdate[] memory chainUpdates = new CCIPBase.ChainUpdate[](1); + chainUpdates[0] = CCIPBase.ChainUpdate({ + chainSelector: DEST_CHAIN_SELECTOR, + allowed: true, + recipient: abi.encode(address(s_sender)), + extraArgsBytes: "" + }); + s_sender.applyChainUpdates(chainUpdates); + + CCIPBase.ApprovedSenderUpdate[] memory senderUpdates = new CCIPBase.ApprovedSenderUpdate[](1); + senderUpdates[0] = + CCIPBase.ApprovedSenderUpdate({destChainSelector: DEST_CHAIN_SELECTOR, sender: abi.encode(address(s_sender))}); + + s_sender.updateApprovedSenders(senderUpdates, new CCIPBase.ApprovedSenderUpdate[](0)); + } + + function test_ccipSend_withNonNativeFeetoken_andDestTokens_Success() public { + address token = address(s_sourceFeeToken); + uint256 amount = 111333333777; + Client.EVMTokenAmount[] memory destTokenAmounts = new Client.EVMTokenAmount[](1); + destTokenAmounts[0] = Client.EVMTokenAmount({token: token, amount: amount}); + + // Make sure we give the receiver contract enough tokens like CCIP would. + IERC20(token).approve(address(s_sender), type(uint256).max); + + Client.EVM2AnyMessage memory message = Client.EVM2AnyMessage({ + receiver: abi.encode(address(s_sender)), + data: "", + tokenAmounts: destTokenAmounts, + feeToken: s_sourceFeeToken, + extraArgs: "" + }); + + uint256 feeTokenAmount = s_sourceRouter.getFee(DEST_CHAIN_SELECTOR, message); + uint256 feeTokenBalanceBefore = IERC20(s_sourceFeeToken).balanceOf(OWNER); + + s_sender.ccipSend({destChainSelector: DEST_CHAIN_SELECTOR, tokenAmounts: destTokenAmounts, data: ""}); + + // Assert that tokens were transfered for bridging + fees + assertEq(IERC20(token).balanceOf(OWNER), feeTokenBalanceBefore - amount - feeTokenAmount); + } + + function test_ccipSend_withNonNativeFeetoken_andNoDestTokens_Success() public { + address token = address(s_sourceFeeToken); + Client.EVMTokenAmount[] memory destTokenAmounts = new Client.EVMTokenAmount[](0); + + // Make sure we give the receiver contract enough tokens like CCIP would. + IERC20(token).approve(address(s_sender), type(uint256).max); + + Client.EVM2AnyMessage memory message = Client.EVM2AnyMessage({ + receiver: abi.encode(address(s_sender)), + data: "", + tokenAmounts: destTokenAmounts, + feeToken: s_sourceFeeToken, + extraArgs: "" + }); + + uint256 feeTokenAmount = s_sourceRouter.getFee(DEST_CHAIN_SELECTOR, message); + uint256 feeTokenBalanceBefore = IERC20(s_sourceFeeToken).balanceOf(OWNER); + + s_sender.ccipSend({destChainSelector: DEST_CHAIN_SELECTOR, tokenAmounts: destTokenAmounts, data: ""}); + + // Assert that tokens were transfered for bridging + fees + assertEq(IERC20(token).balanceOf(OWNER), feeTokenBalanceBefore - feeTokenAmount); + } + + function test_modifyFeeToken_Success() public { + // WETH is used as a placeholder for any ERC20 token + address WETH = s_sourceRouter.getWrappedNative(); + + vm.expectEmit(); + emit IERC20.Approval(address(s_sender), address(s_sourceRouter), 0); + + vm.expectEmit(); + emit CCIPClient.FeeTokenUpdated(s_sourceFeeToken, WETH); + + s_sender.updateFeeToken(WETH); + + IERC20 newFeeToken = s_sender.s_feeToken(); + assertEq(address(newFeeToken), WETH); + assertEq(newFeeToken.allowance(address(s_sender), address(s_sourceRouter)), type(uint256).max); + assertEq(IERC20(s_sourceFeeToken).allowance(address(s_sender), address(s_sourceRouter)), 0); + } + + function test_ccipSend_with_NativeFeeToken_andDestTokens_Success() public { + address token = address(s_sourceFeeToken); + uint256 amount = 111333333777; + Client.EVMTokenAmount[] memory destTokenAmounts = new Client.EVMTokenAmount[](1); + destTokenAmounts[0] = Client.EVMTokenAmount({token: token, amount: amount}); + + s_sender.updateFeeToken(address(0)); + + // Make sure we give the receiver contract enough tokens like CCIP would. + IERC20(token).approve(address(s_sender), type(uint256).max); + + Client.EVM2AnyMessage memory message = Client.EVM2AnyMessage({ + receiver: abi.encode(address(s_sender)), + data: "", + tokenAmounts: destTokenAmounts, + extraArgs: "", + feeToken: address(s_sourceFeeToken) + }); + + uint256 feeTokenAmount = s_sourceRouter.getFee(DEST_CHAIN_SELECTOR, message); + uint256 tokenBalanceBefore = IERC20(token).balanceOf(OWNER); + uint256 nativeFeeTokenBalanceBefore = OWNER.balance; + + s_sender.ccipSend{value: feeTokenAmount}({ + destChainSelector: DEST_CHAIN_SELECTOR, + tokenAmounts: destTokenAmounts, + data: "" + }); + + // Assert that native fees are paid successfully and tokens are transferred + assertEq(IERC20(token).balanceOf(OWNER), tokenBalanceBefore - amount, "Tokens were not successfully delivered"); + assertEq( + OWNER.balance, nativeFeeTokenBalanceBefore - feeTokenAmount, "Native fee tokens were not successfully forwarded" + ); + } + + function test_HappyPath_Success() public { + bytes32 messageId = keccak256("messageId"); + address token = address(s_destFeeToken); + uint256 amount = 111333333777; + Client.EVMTokenAmount[] memory destTokenAmounts = new Client.EVMTokenAmount[](1); + destTokenAmounts[0] = Client.EVMTokenAmount({token: token, amount: amount}); + + // Make sure we give the receiver contract enough tokens like CCIP would. + deal(token, address(s_sender), amount); + + // The receiver contract will revert if the router is not the sender. + vm.startPrank(address(s_sourceRouter)); + + vm.expectEmit(); + emit MessageSucceeded(messageId); + + s_sender.ccipReceive( + Client.Any2EVMMessage({ + messageId: messageId, + sourceChainSelector: DEST_CHAIN_SELECTOR, + sender: abi.encode(address(s_sender)), // correct sender + data: "", + destTokenAmounts: destTokenAmounts + }) + ); + } +} diff --git a/contracts/src/v0.8/ccip/test/applications/external/CCIPClientWithACKTest.t.sol b/contracts/src/v0.8/ccip/test/applications/external/CCIPClientWithACKTest.t.sol index b867637b08..7907c25e8d 100644 --- a/contracts/src/v0.8/ccip/test/applications/external/CCIPClientWithACKTest.t.sol +++ b/contracts/src/v0.8/ccip/test/applications/external/CCIPClientWithACKTest.t.sol @@ -45,6 +45,38 @@ contract CCIPClientTest is EVM2EVMOnRampSetup { s_sender.updateApprovedSenders(senderUpdates, new CCIPBase.ApprovedSenderUpdate[](0)); } + function test_ccipReceiver_ack_with_invalidAckMessageHeaderBytes_Revert() public { + bytes32 messageId = keccak256("messageId"); + Client.EVMTokenAmount[] memory destTokenAmounts = new Client.EVMTokenAmount[](0); + + // The receiver contract will revert if the router is not the sender. + vm.startPrank(address(s_sourceRouter)); + + // Payload with incorrect ack message header should revert + CCIPReceiverWithACK.MessagePayload memory payload = CCIPReceiverWithACK.MessagePayload({ + version: "", + data: abi.encode("RANDOM_BYTES", messageId), + messageType: CCIPReceiverWithACK.MessageType.ACK + }); + + // Expect the processing to revert from invalid Ack Message Header + vm.expectEmit(); + emit MessageFailed(messageId, abi.encodeWithSelector(bytes4(CCIPReceiverWithACK.InvalidAckMessageHeader.selector))); + + s_sender.ccipReceive( + Client.Any2EVMMessage({ + messageId: messageId, + sourceChainSelector: destChainSelector, + sender: abi.encode(address(s_sender)), + data: abi.encode(payload), + destTokenAmounts: destTokenAmounts + }) + ); + + // Check that message status is failed + assertEq(s_sender.getMessageStatus(messageId), 1); + } + function test_ccipReceiveAndSendAck_Success() public { bytes32 messageId = keccak256("messageId"); bytes32 ackMessageId = 0x37ddbb21a51d4e07877b0de816905ea806b958e7607d951d307030631db076bd; diff --git a/contracts/src/v0.8/ccip/test/applications/external/CCIPReceiverWithAckTest.t.sol b/contracts/src/v0.8/ccip/test/applications/external/CCIPReceiverWithAckTest.t.sol index de0f4bf800..a5608814ea 100644 --- a/contracts/src/v0.8/ccip/test/applications/external/CCIPReceiverWithAckTest.t.sol +++ b/contracts/src/v0.8/ccip/test/applications/external/CCIPReceiverWithAckTest.t.sol @@ -161,9 +161,9 @@ contract CCIPReceiverWithAckTest is EVM2EVMOnRampSetup { emit IERC20.Approval(address(s_receiver), address(s_sourceRouter), 0); vm.expectEmit(); - emit CCIPReceiverWithACK.FeeTokenModified(s_sourceFeeToken, WETH); + emit CCIPReceiverWithACK.FeeTokenUpdated(s_sourceFeeToken, WETH); - s_receiver.modifyFeeToken(WETH); + s_receiver.updateFeeToken(WETH); IERC20 newFeeToken = s_receiver.s_feeToken(); assertEq(address(newFeeToken), WETH); diff --git a/contracts/src/v0.8/ccip/test/applications/external/CCIPSenderTest.t.sol b/contracts/src/v0.8/ccip/test/applications/external/CCIPSenderTest.t.sol index 5e74236b41..34be7d94c4 100644 --- a/contracts/src/v0.8/ccip/test/applications/external/CCIPSenderTest.t.sol +++ b/contracts/src/v0.8/ccip/test/applications/external/CCIPSenderTest.t.sol @@ -32,7 +32,7 @@ contract CCIPSenderTest is EVM2EVMOnRampSetup { s_sender.applyChainUpdates(chainUpdates); } - function test_ccipSend_withNonNativeFeetoken_andDestTokens() public { + function test_ccipSend_withNonNativeFeetoken_andDestTokens_Success() public { address token = address(s_sourceFeeToken); uint256 amount = 111333333777; Client.EVMTokenAmount[] memory destTokenAmounts = new Client.EVMTokenAmount[](1); @@ -63,7 +63,7 @@ contract CCIPSenderTest is EVM2EVMOnRampSetup { assertEq(IERC20(token).balanceOf(OWNER), feeTokenBalanceBefore - amount - feeTokenAmount); } - function test_ccipSend_withNonNativeFeetoken_andNoDestTokens() public { + function test_ccipSend_withNonNativeFeetoken_andNoDestTokens_Success() public { address token = address(s_sourceFeeToken); Client.EVMTokenAmount[] memory destTokenAmounts = new Client.EVMTokenAmount[](0); @@ -92,7 +92,7 @@ contract CCIPSenderTest is EVM2EVMOnRampSetup { assertEq(IERC20(token).balanceOf(OWNER), feeTokenBalanceBefore - feeTokenAmount); } - function test_ccipSend_with_NativeFeeToken_andDestTokens() public { + function test_ccipSend_with_NativeFeeToken_andDestTokens_Success() public { address token = address(s_sourceFeeToken); uint256 amount = 111333333777; Client.EVMTokenAmount[] memory destTokenAmounts = new Client.EVMTokenAmount[](1); diff --git a/contracts/src/v0.8/ccip/test/applications/internal/PingPongDemoTest.t.sol b/contracts/src/v0.8/ccip/test/applications/internal/PingPongDemoTest.t.sol index feb8b2ff7f..268680843a 100644 --- a/contracts/src/v0.8/ccip/test/applications/internal/PingPongDemoTest.t.sol +++ b/contracts/src/v0.8/ccip/test/applications/internal/PingPongDemoTest.t.sol @@ -120,4 +120,8 @@ contract PingPong_example_plumbing is PingPongDappSetup { assertTrue(s_pingPong.isPaused()); } + + function test_typeAndVersion() public view { + assertEq(s_pingPong.typeAndVersion(), "PingPongDemo 1.3.0"); + } }