From 0ee8a64c3d52f8ab8fd7f4e3da8ca4174d32a611 Mon Sep 17 00:00:00 2001 From: Denis Fadeev Date: Fri, 6 Dec 2024 14:25:34 +0300 Subject: [PATCH] Merge Swap and SwapToAnyToken into a single example --- examples/swap/contracts/Swap.sol | 123 +++++++---- examples/swap/contracts/SwapCompanion.sol | 51 +++++ examples/swap/contracts/SwapToAnyToken.sol | 231 --------------------- examples/swap/scripts/localnet.sh | 26 +-- examples/swap/tasks/swapFromZetaChain.ts | 2 +- 5 files changed, 143 insertions(+), 290 deletions(-) create mode 100644 examples/swap/contracts/SwapCompanion.sol delete mode 100644 examples/swap/contracts/SwapToAnyToken.sol diff --git a/examples/swap/contracts/Swap.sol b/examples/swap/contracts/Swap.sol index 1c112c63..e7a93d4b 100644 --- a/examples/swap/contracts/Swap.sol +++ b/examples/swap/contracts/Swap.sol @@ -9,6 +9,7 @@ import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import {RevertContext, RevertOptions} from "@zetachain/protocol-contracts/contracts/Revert.sol"; import "@zetachain/protocol-contracts/contracts/zevm/interfaces/UniversalContract.sol"; import "@zetachain/protocol-contracts/contracts/zevm/interfaces/IGatewayZEVM.sol"; +import "@zetachain/protocol-contracts/contracts/zevm/interfaces/IWZETA.sol"; import {GatewayZEVM} from "@zetachain/protocol-contracts/contracts/zevm/GatewayZEVM.sol"; contract Swap is UniversalContract { @@ -20,6 +21,7 @@ contract Swap is UniversalContract { error InvalidAddress(); error Unauthorized(); error ApprovalFailed(); + error TransferFailed(); modifier onlyGateway() { if (msg.sender != address(gateway)) revert Unauthorized(); @@ -41,6 +43,7 @@ contract Swap is UniversalContract { struct Params { address target; bytes to; + bool withdraw; } function onCall( @@ -49,19 +52,29 @@ contract Swap is UniversalContract { uint256 amount, bytes calldata message ) external onlyGateway { - Params memory params = Params({target: address(0), to: bytes("")}); + Params memory params = Params({ + target: address(0), + to: bytes(""), + withdraw: true + }); + if (context.chainID == BITCOIN) { params.target = BytesHelperLib.bytesToAddress(message, 0); params.to = abi.encodePacked( BytesHelperLib.bytesToAddress(message, 20) ); + if (message.length >= 41) { + params.withdraw = BytesHelperLib.bytesToBool(message, 40); + } } else { - (address targetToken, bytes memory recipient) = abi.decode( - message, - (address, bytes) - ); + ( + address targetToken, + bytes memory recipient, + bool withdrawFlag + ) = abi.decode(message, (address, bytes, bool)); params.target = targetToken; params.to = recipient; + params.withdraw = withdrawFlag; } (uint256 out, address gasZRC20, uint256 gasFee) = handleGasAndSwap( @@ -72,6 +85,42 @@ contract Swap is UniversalContract { withdraw(params, context.sender, gasFee, gasZRC20, out, zrc20); } + function swap( + address inputToken, + uint256 amount, + address targetToken, + bytes memory recipient, + bool withdrawFlag + ) public { + bool success = IZRC20(inputToken).transferFrom( + msg.sender, + address(this), + amount + ); + if (!success) { + revert TransferFailed(); + } + + (uint256 out, address gasZRC20, uint256 gasFee) = handleGasAndSwap( + inputToken, + amount, + targetToken + ); + + withdraw( + Params({ + target: targetToken, + to: recipient, + withdraw: withdrawFlag + }), + msg.sender, + gasFee, + gasZRC20, + out, + inputToken + ); + } + function handleGasAndSwap( address inputToken, uint256 amount, @@ -97,14 +146,14 @@ contract Swap is UniversalContract { swapAmount = amount - inputForGas; } - uint256 outputAmount = SwapHelperLib.swapExactTokensForTokens( + uint256 out = SwapHelperLib.swapExactTokensForTokens( uniswapRouter, inputToken, swapAmount, targetToken, 0 ); - return (outputAmount, gasZRC20, gasFee); + return (out, gasZRC20, gasFee); } function withdraw( @@ -112,40 +161,43 @@ contract Swap is UniversalContract { address sender, uint256 gasFee, address gasZRC20, - uint256 outputAmount, + uint256 out, address inputToken ) public { - if (gasZRC20 == params.target) { - if ( - !IZRC20(gasZRC20).approve( - address(gateway), - outputAmount + gasFee - ) - ) { - revert ApprovalFailed(); + if (params.withdraw) { + if (gasZRC20 == params.target) { + if (!IZRC20(gasZRC20).approve(address(gateway), out + gasFee)) { + revert ApprovalFailed(); + } + } else { + if (!IZRC20(gasZRC20).approve(address(gateway), gasFee)) { + revert ApprovalFailed(); + } + if (!IZRC20(params.target).approve(address(gateway), out)) { + revert ApprovalFailed(); + } } + gateway.withdraw( + abi.encodePacked(params.to), + out, + params.target, + RevertOptions({ + revertAddress: address(this), + callOnRevert: true, + abortAddress: address(0), + revertMessage: abi.encode(sender, inputToken), + onRevertGasLimit: gasLimit + }) + ); } else { - if (!IZRC20(gasZRC20).approve(address(gateway), gasFee)) { - revert ApprovalFailed(); - } - if ( - !IZRC20(params.target).approve(address(gateway), outputAmount) - ) { - revert ApprovalFailed(); + bool success = IWETH9(params.target).transfer( + address(uint160(bytes20(params.to))), + out + ); + if (!success) { + revert TransferFailed(); } } - gateway.withdraw( - params.to, - outputAmount, - params.target, - RevertOptions({ - revertAddress: address(this), - callOnRevert: true, - abortAddress: address(0), - revertMessage: abi.encode(sender, inputToken), - onRevertGasLimit: gasLimit - }) - ); } function onRevert(RevertContext calldata context) external onlyGateway { @@ -158,6 +210,7 @@ contract Swap is UniversalContract { context.amount, zrc20 ); + gateway.withdraw( abi.encodePacked(sender), out, diff --git a/examples/swap/contracts/SwapCompanion.sol b/examples/swap/contracts/SwapCompanion.sol new file mode 100644 index 00000000..7d47e150 --- /dev/null +++ b/examples/swap/contracts/SwapCompanion.sol @@ -0,0 +1,51 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.26; + +import "@zetachain/protocol-contracts/contracts/evm/GatewayEVM.sol"; +import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; + +contract SwapCompanion { + using SafeERC20 for IERC20; + + GatewayEVM public immutable gateway; + + constructor(address payable gatewayAddress) { + gateway = GatewayEVM(gatewayAddress); + } + + function swapNativeGas( + address universalSwapContract, + address targetToken, + bytes memory recipient, + bool withdraw + ) public payable { + gateway.depositAndCall{value: msg.value}( + universalSwapContract, + abi.encode(targetToken, recipient, withdraw), + RevertOptions(msg.sender, false, address(0), "", 0) + ); + } + + function swapERC20( + address universalSwapContract, + address targetToken, + bytes memory recipient, + uint256 amount, + address asset, + bool withdraw + ) public { + IERC20(asset).safeTransferFrom(msg.sender, address(this), amount); + IERC20(asset).approve(address(gateway), amount); + gateway.depositAndCall( + universalSwapContract, + amount, + asset, + abi.encode(targetToken, recipient, withdraw), + RevertOptions(msg.sender, false, address(0), "", 0) + ); + } + + receive() external payable {} + + fallback() external payable {} +} diff --git a/examples/swap/contracts/SwapToAnyToken.sol b/examples/swap/contracts/SwapToAnyToken.sol deleted file mode 100644 index 4c25b08d..00000000 --- a/examples/swap/contracts/SwapToAnyToken.sol +++ /dev/null @@ -1,231 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity 0.8.26; - -import {SystemContract, IZRC20} from "@zetachain/toolkit/contracts/SystemContract.sol"; -import {SwapHelperLib} from "@zetachain/toolkit/contracts/SwapHelperLib.sol"; -import {BytesHelperLib} from "@zetachain/toolkit/contracts/BytesHelperLib.sol"; -import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; - -import {RevertContext, RevertOptions} from "@zetachain/protocol-contracts/contracts/Revert.sol"; -import "@zetachain/protocol-contracts/contracts/zevm/interfaces/UniversalContract.sol"; -import "@zetachain/protocol-contracts/contracts/zevm/interfaces/IGatewayZEVM.sol"; -import "@zetachain/protocol-contracts/contracts/zevm/interfaces/IWZETA.sol"; -import {GatewayZEVM} from "@zetachain/protocol-contracts/contracts/zevm/GatewayZEVM.sol"; - -contract SwapToAnyToken is UniversalContract { - address public immutable uniswapRouter; - GatewayZEVM public gateway; - uint256 constant BITCOIN = 18332; - uint256 public immutable gasLimit; - - error InvalidAddress(); - error Unauthorized(); - error ApprovalFailed(); - error TransferFailed(); - - modifier onlyGateway() { - if (msg.sender != address(gateway)) revert Unauthorized(); - _; - } - - constructor( - address payable gatewayAddress, - address uniswapRouterAddress, - uint256 gasLimitAmount - ) { - if (gatewayAddress == address(0) || uniswapRouterAddress == address(0)) - revert InvalidAddress(); - uniswapRouter = uniswapRouterAddress; - gateway = GatewayZEVM(gatewayAddress); - gasLimit = gasLimitAmount; - } - - struct Params { - address target; - bytes to; - bool withdraw; - } - - function onCall( - MessageContext calldata context, - address zrc20, - uint256 amount, - bytes calldata message - ) external onlyGateway { - Params memory params = Params({ - target: address(0), - to: bytes(""), - withdraw: true - }); - - if (context.chainID == BITCOIN) { - params.target = BytesHelperLib.bytesToAddress(message, 0); - params.to = abi.encodePacked( - BytesHelperLib.bytesToAddress(message, 20) - ); - if (message.length >= 41) { - params.withdraw = BytesHelperLib.bytesToBool(message, 40); - } - } else { - ( - address targetToken, - bytes memory recipient, - bool withdrawFlag - ) = abi.decode(message, (address, bytes, bool)); - params.target = targetToken; - params.to = recipient; - params.withdraw = withdrawFlag; - } - - (uint256 out, address gasZRC20, uint256 gasFee) = handleGasAndSwap( - zrc20, - amount, - params.target - ); - withdraw(params, context.sender, gasFee, gasZRC20, out, zrc20); - } - - function swap( - address inputToken, - uint256 amount, - address targetToken, - bytes memory recipient, - bool withdrawFlag - ) public { - bool success = IZRC20(inputToken).transferFrom( - msg.sender, - address(this), - amount - ); - if (!success) { - revert TransferFailed(); - } - - (uint256 out, address gasZRC20, uint256 gasFee) = handleGasAndSwap( - inputToken, - amount, - targetToken - ); - - withdraw( - Params({ - target: targetToken, - to: recipient, - withdraw: withdrawFlag - }), - msg.sender, - gasFee, - gasZRC20, - out, - inputToken - ); - } - - function handleGasAndSwap( - address inputToken, - uint256 amount, - address targetToken - ) internal returns (uint256, address, uint256) { - uint256 inputForGas; - address gasZRC20; - uint256 gasFee; - uint256 swapAmount; - - (gasZRC20, gasFee) = IZRC20(targetToken).withdrawGasFee(); - - if (gasZRC20 == inputToken) { - swapAmount = amount - gasFee; - } else { - inputForGas = SwapHelperLib.swapTokensForExactTokens( - uniswapRouter, - inputToken, - gasFee, - gasZRC20, - amount - ); - swapAmount = amount - inputForGas; - } - - uint256 out = SwapHelperLib.swapExactTokensForTokens( - uniswapRouter, - inputToken, - swapAmount, - targetToken, - 0 - ); - return (out, gasZRC20, gasFee); - } - - function withdraw( - Params memory params, - address sender, - uint256 gasFee, - address gasZRC20, - uint256 out, - address inputToken - ) public { - if (params.withdraw) { - if (gasZRC20 == params.target) { - if (!IZRC20(gasZRC20).approve(address(gateway), out + gasFee)) { - revert ApprovalFailed(); - } - } else { - if (!IZRC20(gasZRC20).approve(address(gateway), gasFee)) { - revert ApprovalFailed(); - } - if (!IZRC20(params.target).approve(address(gateway), out)) { - revert ApprovalFailed(); - } - } - gateway.withdraw( - abi.encodePacked(params.to), - out, - params.target, - RevertOptions({ - revertAddress: address(this), - callOnRevert: true, - abortAddress: address(0), - revertMessage: abi.encode(sender, inputToken), - onRevertGasLimit: gasLimit - }) - ); - } else { - bool success = IWETH9(params.target).transfer( - address(uint160(bytes20(params.to))), - out - ); - if (!success) { - revert TransferFailed(); - } - } - } - - function onRevert(RevertContext calldata context) external onlyGateway { - (address sender, address zrc20) = abi.decode( - context.revertMessage, - (address, address) - ); - (uint256 out, , ) = handleGasAndSwap( - context.asset, - context.amount, - zrc20 - ); - - gateway.withdraw( - abi.encodePacked(sender), - out, - zrc20, - RevertOptions({ - revertAddress: sender, - callOnRevert: false, - abortAddress: address(0), - revertMessage: "", - onRevertGasLimit: gasLimit - }) - ); - } - - fallback() external payable {} - - receive() external payable {} -} diff --git a/examples/swap/scripts/localnet.sh b/examples/swap/scripts/localnet.sh index 0ea56366..76f3bc1d 100755 --- a/examples/swap/scripts/localnet.sh +++ b/examples/swap/scripts/localnet.sh @@ -1,6 +1,8 @@ #!/bin/bash set -e +set -x +set -o pipefail if [ "$1" = "start" ]; then npx hardhat localnet --exit-on-error & sleep 10; fi @@ -18,9 +20,6 @@ SENDER=0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266 CONTRACT_SWAP=$(npx hardhat deploy --name Swap --network localhost --gateway "$GATEWAY_ZETACHAIN" --uniswap-router "$UNISWAP_ROUTER" --json | jq -r '.contractAddress') echo -e "\nšŸš€ Deployed Swap contract on ZetaChain: $CONTRACT_SWAP" -CONTRACT_SWAPTOANY=$(npx hardhat deploy --name SwapToAnyToken --network localhost --gateway "$GATEWAY_ZETACHAIN" --uniswap-router "$UNISWAP_ROUTER" --json | jq -r '.contractAddress') -echo -e "\nšŸš€ Deployed SwapToAny contract on ZetaChain: $CONTRACT_SWAPTOANY" - npx hardhat swap-from-evm \ --network localhost \ --receiver "$CONTRACT_SWAP" \ @@ -40,28 +39,9 @@ npx hardhat swap-from-evm \ npx hardhat localnet-check -npx hardhat swap-from-evm \ - --network localhost \ - --receiver "$CONTRACT_SWAPTOANY" \ - --amount 1 \ - --target "$ZRC20_BNB" \ - --recipient "$SENDER" - -npx hardhat localnet-check - -npx hardhat swap-from-evm \ - --network localhost \ - --receiver "$CONTRACT_SWAPTOANY" \ - --amount 1 \ - --target "$ZRC20_BNB" \ - --recipient "$SENDER" \ - --withdraw false - -npx hardhat localnet-check - npx hardhat swap-from-zetachain \ --network localhost \ - --contract "$CONTRACT_SWAPTOANY" \ + --contract "$CONTRACT_SWAP" \ --amount 1 \ --zrc20 "$ZRC20_BNB" \ --target "$ZRC20_ETHEREUM" \ diff --git a/examples/swap/tasks/swapFromZetaChain.ts b/examples/swap/tasks/swapFromZetaChain.ts index a98fc0ea..cd1c4b4f 100644 --- a/examples/swap/tasks/swapFromZetaChain.ts +++ b/examples/swap/tasks/swapFromZetaChain.ts @@ -7,7 +7,7 @@ import ZRC20 from "@zetachain/protocol-contracts/abi/ZRC20.sol/ZRC20.json"; const main = async (args: any, hre: HardhatRuntimeEnvironment) => { const [signer] = await hre.ethers.getSigners(); - const factory = await hre.ethers.getContractFactory("SwapToAnyToken"); + const factory = await hre.ethers.getContractFactory("Swap"); const contract = factory.attach(args.contract); const zrc20 = new ethers.Contract(args.zrc20, ZRC20.abi, signer);