Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[API-3250] extend cctp contract functions to take an expiry timestamp and a memo #13

Closed
wants to merge 4 commits into from
Closed
Show file tree
Hide file tree
Changes from all 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
264 changes: 142 additions & 122 deletions CCTPRelayer/src/CCTPRelayer.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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();
}
Expand Down Expand Up @@ -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.
Expand All @@ -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.
Expand All @@ -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
Expand All @@ -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
Expand All @@ -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;
Expand Down Expand Up @@ -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();
}
}
}
2 changes: 2 additions & 0 deletions CCTPRelayer/src/interfaces/ICCTPRelayer.sol
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ interface ICCTPRelayer {
error Reentrancy();

event PaymentForRelay(uint64 nonce, uint256 paymentAmount);

event RelayMemo(string memo);

event FailedReceiveMessage(bytes message, bytes attestation);

Expand Down
Loading