Skip to content

Commit

Permalink
Merge Swap and SwapToAnyToken into a single example
Browse files Browse the repository at this point in the history
  • Loading branch information
fadeev committed Dec 6, 2024
1 parent 08d15c5 commit 0ee8a64
Show file tree
Hide file tree
Showing 5 changed files with 143 additions and 290 deletions.
123 changes: 88 additions & 35 deletions examples/swap/contracts/Swap.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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();
Expand All @@ -41,6 +43,7 @@ contract Swap is UniversalContract {
struct Params {
address target;
bytes to;
bool withdraw;
}

function onCall(
Expand All @@ -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(
Expand All @@ -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,
Expand All @@ -97,55 +146,58 @@ 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(
Params memory params,
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 {
Expand All @@ -158,6 +210,7 @@ contract Swap is UniversalContract {
context.amount,
zrc20
);

gateway.withdraw(
abi.encodePacked(sender),
out,
Expand Down
51 changes: 51 additions & 0 deletions examples/swap/contracts/SwapCompanion.sol
Original file line number Diff line number Diff line change
@@ -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)
);
}

Check warning

Code scanning / Slither

Unused return Medium


receive() external payable {}

fallback() external payable {}
}
Loading

0 comments on commit 0ee8a64

Please sign in to comment.