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

feat: added ability to swap token on and from ZetaChain #186

Merged
merged 5 commits into from
Aug 12, 2024
Merged
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
21 changes: 15 additions & 6 deletions omnichain/swap/contracts/Swap.sol
Original file line number Diff line number Diff line change
Expand Up @@ -42,29 +42,38 @@
params.to = recipient;
}

swapAndWithdraw(zrc20, amount, params.target, params.to);
}

function swapAndWithdraw(
address inputToken,
uint256 amount,
address targetToken,
bytes memory recipient
) internal {
uint256 inputForGas;
address gasZRC20;
uint256 gasFee;

(gasZRC20, gasFee) = IZRC20(params.target).withdrawGasFee();
(gasZRC20, gasFee) = IZRC20(targetToken).withdrawGasFee();

inputForGas = SwapHelperLib.swapTokensForExactTokens(
systemContract,
zrc20,
inputToken,
gasFee,
gasZRC20,
amount
);

uint256 outputAmount = SwapHelperLib.swapExactTokensForTokens(
systemContract,
zrc20,
inputToken,
amount - inputForGas,
params.target,
targetToken,
0
);

IZRC20(gasZRC20).approve(params.target, gasFee);
IZRC20(params.target).withdraw(params.to, outputAmount);
IZRC20(gasZRC20).approve(targetToken, gasFee);
IZRC20(targetToken).withdraw(recipient, outputAmount);
}

Check warning

Code scanning / Slither

Unused return Medium

Check warning

Code scanning / Slither

Unused return Medium

}
80 changes: 65 additions & 15 deletions omnichain/swap/contracts/SwapToAnyToken.sol
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import "@zetachain/toolkit/contracts/OnlySystem.sol";

contract SwapToAnyToken is zContract, OnlySystem {
error TransferFailed();
SystemContract public systemContract;

uint256 constant BITCOIN = 18332;
Expand All @@ -23,6 +24,8 @@
bool withdraw;
}

receive() external payable {}

function onCrossChainCall(
zContext calldata context,
address zrc20,
Expand All @@ -37,49 +40,96 @@

if (context.chainID == BITCOIN) {
params.target = BytesHelperLib.bytesToAddress(message, 0);
params.to = abi.encodePacked(BytesHelperLib.bytesToAddress(message, 20));
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)
);
(
address targetToken,
bytes memory recipient,
bool withdrawFlag
) = abi.decode(message, (address, bytes, bool));
params.target = targetToken;
params.to = recipient;
params.withdraw = withdrawFlag;
}

swapAndWithdraw(
zrc20,
amount,
params.target,
params.to,
params.withdraw
);
}

function swapAndWithdraw(
address inputToken,
uint256 amount,
address targetToken,
bytes memory recipient,
bool withdraw
) internal {
uint256 inputForGas;
address gasZRC20;
uint256 gasFee;

if (params.withdraw) {
(gasZRC20, gasFee) = IZRC20(params.target).withdrawGasFee();
if (withdraw) {
(gasZRC20, gasFee) = IZRC20(targetToken).withdrawGasFee();

inputForGas = SwapHelperLib.swapTokensForExactTokens(
systemContract,
zrc20,
inputToken,
gasFee,
gasZRC20,
amount
);
}

uint256 outputAmount = SwapHelperLib.swapExactTokensForTokens(
systemContract,
zrc20,
params.withdraw ? amount - inputForGas : amount,
params.target,
inputToken,
withdraw ? amount - inputForGas : amount,
targetToken,
0
);

if (params.withdraw) {
IZRC20(gasZRC20).approve(params.target, gasFee);
IZRC20(params.target).withdraw(params.to, outputAmount);
if (withdraw) {
IZRC20(gasZRC20).approve(targetToken, gasFee);
IZRC20(targetToken).withdraw(recipient, outputAmount);
} else {
IWETH9(params.target).transfer(address(uint160(bytes20(params.to))), outputAmount);
address wzeta = systemContract.wZetaContractAddress();
if (targetToken == wzeta) {
IWETH9(wzeta).withdraw(outputAmount);
address payable to = payable(
address(uint160(bytes20(recipient)))
);
(bool success, ) = to.call{value: outputAmount}("");
if (!success) revert TransferFailed();
} else {
address to = address(uint160(bytes20(recipient)));
bool success = IWETH9(targetToken).transfer(to, outputAmount);
if (!success) revert TransferFailed();
}
}
}

Check failure

Code scanning / Slither

Functions that send Ether to arbitrary destinations High

Check warning

Code scanning / Slither

Unused return Medium

Check warning

Code scanning / Slither

Unused return Medium

Check warning

Code scanning / Slither

Low-level calls Warning


function swap(
address inputToken,
uint256 amount,
address targetToken,
bytes memory recipient,
bool withdraw
) public {
bool success = IZRC20(inputToken).transferFrom(
msg.sender,
address(this),
amount
);
if (!success) revert TransferFailed();
swapAndWithdraw(inputToken, amount, targetToken, recipient, withdraw);
}
}
47 changes: 47 additions & 0 deletions omnichain/swap/tasks/swap.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import { task } from "hardhat/config";
import { HardhatRuntimeEnvironment } from "hardhat/types";
import { parseEther } from "@ethersproject/units";
import { ethers } from "ethers";

const main = async (args: any, hre: HardhatRuntimeEnvironment) => {
const [signer] = await hre.ethers.getSigners();

if (!/zeta_(testnet|mainnet)/.test(hre.network.name)) {
throw new Error('🚨 Please use either "zeta_testnet" or "zeta_mainnet".');
}

const factory = await hre.ethers.getContractFactory("SwapToAnyToken");
const contract = factory.attach(args.contract);

const amount = parseEther(args.amount);
const inputToken = args.inputToken;
const targetToken = args.targetToken;
const recipient = ethers.utils.arrayify(args.recipient);
const withdraw = JSON.parse(args.withdraw);

const erc20Factory = await hre.ethers.getContractFactory("ERC20");
const inputTokenContract = erc20Factory.attach(args.inputToken);

const approval = await inputTokenContract.approve(args.contract, amount);
await approval.wait();

const tx = await contract.swap(
inputToken,
amount,
targetToken,
recipient,
withdraw
);

await tx.wait();
console.log(`Transaction hash: ${tx.hash}`);
};
fadeev marked this conversation as resolved.
Show resolved Hide resolved

task("swap", "Interact with the Swap contract from ZetaChain", main)
.addFlag("json", "Output JSON")
.addParam("contract", "Contract address")
.addParam("amount", "Token amount to send")
.addParam("inputToken", "Input token address")
.addParam("targetToken", "Target token address")
.addParam("recipient", "Recipient address")
.addParam("withdraw", "Withdraw flag (true/false)");
fadeev marked this conversation as resolved.
Show resolved Hide resolved
Loading