diff --git a/CCTPRelayer/src/CCTPRelayer.sol b/CCTPRelayer/src/CCTPRelayer.sol index b6bb355..6e35102 100644 --- a/CCTPRelayer/src/CCTPRelayer.sol +++ b/CCTPRelayer/src/CCTPRelayer.sol @@ -24,6 +24,13 @@ contract CCTPRelayer is ICCTPRelayer, Initializable, UUPSUpgradeable, Ownable2St reentrant = false; } + modifier withExpiry(uint256 _expiryTimestamp) { + if (_expiryTimestamp > 0) { + require(block.timestamp >= _expiryTimestamp, "Expired"); + } + _; + } + constructor() { _disableInitializers(); } @@ -63,7 +70,7 @@ contract CCTPRelayer is ICCTPRelayer, Initializable, UUPSUpgradeable, Ownable2St bytes32 mintRecipient, address burnToken, uint256 feeAmount - ) external { + ) public { if (transferAmount == 0) revert PaymentCannotBeZero(); if (feeAmount == 0) revert PaymentCannotBeZero(); // In order to save gas do the transfer only once, of both transfer amount and fee amount. @@ -86,7 +93,7 @@ contract CCTPRelayer is ICCTPRelayer, Initializable, UUPSUpgradeable, Ownable2St address burnToken, uint256 feeAmount, bytes32 destinationCaller - ) external { + ) public { if (transferAmount == 0) revert PaymentCannotBeZero(); if (feeAmount == 0) revert PaymentCannotBeZero(); // In order to save gas do the transfer only once, of both transfer amount and fee amount. @@ -112,72 +119,15 @@ contract CCTPRelayer is ICCTPRelayer, Initializable, UUPSUpgradeable, Ownable2St bytes32 mintRecipient, address burnToken, uint256 feeAmount - ) external payable nonReentrant { + ) public payable nonReentrant { if (inputAmount == 0) revert PaymentCannotBeZero(); if (feeAmount == 0) revert PaymentCannotBeZero(); uint256 outputAmount; if (inputToken == address(0)) { - IERC20 token = IERC20(inputToken); - - // Native Token - if (inputAmount != msg.value) revert InsufficientNativeToken(); - - // Get the contract's balances previous to the swap - uint256 preInputBalance = address(this).balance - inputAmount; - uint256 preOutputBalance = usdc.balanceOf(address(this)); - - // Call the swap router and perform the swap - (bool success,) = swapRouter.call{value: inputAmount}(swapCalldata); - if (!success) revert SwapFailed(); - - // Get the contract's balances after the swap - uint256 postInputBalance = address(this).balance; - uint256 postOutputBalance = usdc.balanceOf(address(this)); - - // Check that the contract's native token balance has increased - if (preOutputBalance >= postOutputBalance) revert InsufficientSwapOutput(); - outputAmount = postOutputBalance - preOutputBalance; - - // Refund the remaining ETH - uint256 dust = postInputBalance - preInputBalance; - if (dust != 0) { - (bool ethSuccess,) = msg.sender.call{value: dust}(""); - if (!ethSuccess) revert ETHSendFailed(); - } + outputAmount = _swapNativeAndReturnDust(inputAmount, swapCalldata); } else { - IERC20 token = IERC20(inputToken); - - // Get the contract's balances previous to the swap - uint256 preInputBalance = token.balanceOf(address(this)); - uint256 preOutputBalance = usdc.balanceOf(address(this)); - - // Transfer input ERC20 tokens to the contract - token.transferFrom(msg.sender, address(this), inputAmount); - - // Approve the swap router to spend the input tokens - token.approve(swapRouter, inputAmount); - - // Call the swap router and perform the swap - (bool success,) = swapRouter.call(swapCalldata); - if (!success) revert SwapFailed(); - - // Get the contract's balances after the swap - uint256 postInputBalance = token.balanceOf(address(this)); - uint256 postOutputBalance = usdc.balanceOf(address(this)); - - // Check that the contract's output token balance has increased - if (preOutputBalance >= postOutputBalance) revert InsufficientSwapOutput(); - outputAmount = postOutputBalance - preOutputBalance; - - // Refund the remaining amount - uint256 dust = postInputBalance - preInputBalance; - if (dust != 0) { - token.transfer(msg.sender, dust); - - // Revoke Approval - token.approve(swapRouter, 0); - } + outputAmount = _swapAndReturnDust(inputToken, inputAmount, swapCalldata); } // Check that output amount is enough to cover the fee @@ -203,72 +153,15 @@ contract CCTPRelayer is ICCTPRelayer, Initializable, UUPSUpgradeable, Ownable2St address burnToken, uint256 feeAmount, bytes32 destinationCaller - ) external payable nonReentrant { + ) public payable nonReentrant { if (inputAmount == 0) revert PaymentCannotBeZero(); if (feeAmount == 0) revert PaymentCannotBeZero(); uint256 outputAmount; if (inputToken == address(0)) { - // Native Token - if (inputAmount != msg.value) revert InsufficientNativeToken(); - - IERC20 token = IERC20(inputToken); - - // Get the contract's balances previous to the swap - uint256 preInputBalance = address(this).balance - inputAmount; - uint256 preOutputBalance = usdc.balanceOf(address(this)); - - // Call the swap router and perform the swap - (bool success,) = swapRouter.call{value: inputAmount}(swapCalldata); - if (!success) revert SwapFailed(); - - // Get the contract's balances after the swap - uint256 postInputBalance = address(this).balance; - uint256 postOutputBalance = usdc.balanceOf(address(this)); - - // Check that the contract's native token balance has increased - if (preOutputBalance >= postOutputBalance) revert InsufficientSwapOutput(); - outputAmount = postOutputBalance - preOutputBalance; - - // Refund the remaining ETH - uint256 dust = postInputBalance - preInputBalance; - if (dust != 0) { - (bool ethSuccess,) = msg.sender.call{value: dust}(""); - if (!ethSuccess) revert ETHSendFailed(); - } + outputAmount = _swapNativeAndReturnDust(inputAmount, swapCalldata); } else { - IERC20 token = IERC20(inputToken); - - // Get the contract's balances previous to the swap - uint256 preInputBalance = token.balanceOf(address(this)); - uint256 preOutputBalance = usdc.balanceOf(address(this)); - - // Transfer input ERC20 tokens to the contract - token.transferFrom(msg.sender, address(this), inputAmount); - - // Approve the swap router to spend the input tokens - token.approve(swapRouter, inputAmount); - - // Call the swap router and perform the swap - (bool success,) = swapRouter.call(swapCalldata); - if (!success) revert SwapFailed(); - - // Get the contract's balances after the swap - uint256 postInputBalance = token.balanceOf(address(this)); - uint256 postOutputBalance = usdc.balanceOf(address(this)); - - // Check that the contract's output token balance has increased - if (preOutputBalance >= postOutputBalance) revert InsufficientSwapOutput(); - outputAmount = postOutputBalance - preOutputBalance; - - // Refund the remaining amount - uint256 dust = postInputBalance - preInputBalance; - if (dust != 0) { - token.transfer(msg.sender, dust); - - // Revoke Approval - token.approve(swapRouter, 0); - } + outputAmount = _swapAndReturnDust(inputToken, inputAmount, swapCalldata); } // Check that output amount is enough to cover the fee @@ -287,6 +180,64 @@ contract CCTPRelayer is ICCTPRelayer, Initializable, UUPSUpgradeable, Ownable2St emit PaymentForRelay(nonce, feeAmount); } + function requestCCTPTransfer( + uint256 transferAmount, + uint32 destinationDomain, + bytes32 mintRecipient, + address burnToken, + uint256 feeAmount, + uint256 expiryTimestamp, + string memory memo + ) external withExpiry(expiryTimestamp) { + requestCCTPTransfer(transferAmount, destinationDomain, mintRecipient, burnToken, feeAmount); + emit RelayMemo(memo); + } + + function requestCCTPTransferWithCaller( + uint256 transferAmount, + uint32 destinationDomain, + bytes32 mintRecipient, + address burnToken, + uint256 feeAmount, + bytes32 destinationCaller, + uint256 expiryTimestamp, + string memory memo + ) external withExpiry(expiryTimestamp) { + requestCCTPTransferWithCaller(transferAmount, destinationDomain, mintRecipient, burnToken, feeAmount, destinationCaller); + emit RelayMemo(memo); + } + + function swapAndRequestCCTPTransfer( + address inputToken, + uint256 inputAmount, + bytes memory swapCalldata, + uint32 destinationDomain, + bytes32 mintRecipient, + address burnToken, + uint256 feeAmount, + uint256 expiryTimestamp, + string memory memo + ) external payable nonReentrant withExpiry(expiryTimestamp) { + swapAndRequestCCTPTransfer(inputToken, inputAmount, swapCalldata, destinationDomain, mintRecipient, burnToken, feeAmount); + emit RelayMemo(memo); + } + + function swapAndRequestCCTPTransferWithCaller( + address inputToken, + uint256 inputAmount, + bytes memory swapCalldata, + uint32 destinationDomain, + bytes32 mintRecipient, + address burnToken, + uint256 feeAmount, + bytes32 destinationCaller, + uint256 expiryTimestamp, + string memory memo + ) external payable nonReentrant withExpiry(expiryTimestamp) { + swapAndRequestCCTPTransferWithCaller(inputToken, inputAmount, swapCalldata, destinationDomain, mintRecipient, burnToken, feeAmount, destinationCaller); + emit RelayMemo(memo); + } + function batchReceiveMessage(ICCTPRelayer.ReceiveCall[] memory receiveCalls) external { // Save gas by not retrieving the length on each loop. uint256 length = receiveCalls.length; @@ -320,4 +271,73 @@ contract CCTPRelayer is ICCTPRelayer, Initializable, UUPSUpgradeable, Ownable2St receive() external payable {} function _authorizeUpgrade(address newImplementation) internal override onlyOwner {} + + function _swapAndReturnDust(address inputToken, uint256 inputAmount, bytes memory swapCalldata) + internal + returns (uint256 outputAmount) + { + IERC20 token = IERC20(inputToken); + + // Get the contract's balances previous to the swap + uint256 preInputBalance = token.balanceOf(address(this)); + uint256 preOutputBalance = usdc.balanceOf(address(this)); + + // Transfer input ERC20 tokens to the contract + token.transferFrom(msg.sender, address(this), inputAmount); + + // Approve the swap router to spend the input tokens + token.approve(swapRouter, inputAmount); + + // Call the swap router and perform the swap + (bool success,) = swapRouter.call(swapCalldata); + if (!success) revert SwapFailed(); + + // Get the contract's balances after the swap + uint256 postInputBalance = token.balanceOf(address(this)); + uint256 postOutputBalance = usdc.balanceOf(address(this)); + + // Check that the contract's output token balance has increased + if (preOutputBalance >= postOutputBalance) revert InsufficientSwapOutput(); + outputAmount = postOutputBalance - preOutputBalance; + + // Refund the remaining amount + uint256 dust = postInputBalance - preInputBalance; + if (dust != 0) { + token.transfer(msg.sender, dust); + + // Revoke Approval + token.approve(swapRouter, 0); + } + } + + function _swapNativeAndReturnDust(uint256 inputAmount, bytes memory swapCalldata) + internal + returns (uint256 outputAmount) + { + // Native Token + if (inputAmount != msg.value) revert InsufficientNativeToken(); + + // Get the contract's balances previous to the swap + uint256 preInputBalance = address(this).balance - inputAmount; + uint256 preOutputBalance = usdc.balanceOf(address(this)); + + // Call the swap router and perform the swap + (bool success,) = swapRouter.call{value: inputAmount}(swapCalldata); + if (!success) revert SwapFailed(); + + // Get the contract's balances after the swap + uint256 postInputBalance = address(this).balance; + uint256 postOutputBalance = usdc.balanceOf(address(this)); + + // Check that the contract's native token balance has increased + if (preOutputBalance >= postOutputBalance) revert InsufficientSwapOutput(); + outputAmount = postOutputBalance - preOutputBalance; + + // Refund the remaining ETH + uint256 dust = postInputBalance - preInputBalance; + if (dust != 0) { + (bool ethSuccess,) = msg.sender.call{value: dust}(""); + if (!ethSuccess) revert ETHSendFailed(); + } + } } diff --git a/CCTPRelayer/src/interfaces/ICCTPRelayer.sol b/CCTPRelayer/src/interfaces/ICCTPRelayer.sol index e1830a8..570bf04 100644 --- a/CCTPRelayer/src/interfaces/ICCTPRelayer.sol +++ b/CCTPRelayer/src/interfaces/ICCTPRelayer.sol @@ -17,6 +17,8 @@ interface ICCTPRelayer { error Reentrancy(); event PaymentForRelay(uint64 nonce, uint256 paymentAmount); + + event RelayMemo(string memo); event FailedReceiveMessage(bytes message, bytes attestation);