Skip to content

Commit

Permalink
feat: Update "Swap to ZETA" tutorial to "Swap to Any Token" (#181)
Browse files Browse the repository at this point in the history
  • Loading branch information
lukema95 authored Jun 25, 2024
1 parent a543dd2 commit e1a2ae4
Show file tree
Hide file tree
Showing 6 changed files with 1,709 additions and 1,662 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import "@zetachain/toolkit/contracts/BytesHelperLib.sol";
import "@zetachain/protocol-contracts/contracts/zevm/interfaces/IWZETA.sol";
import "@zetachain/toolkit/contracts/OnlySystem.sol";

contract SwapToZeta is zContract, OnlySystem {
contract SwapToAnyToken is zContract, OnlySystem {
SystemContract public systemContract;

uint256 constant BITCOIN = 18332;
Expand All @@ -17,35 +17,46 @@ contract SwapToZeta is zContract, OnlySystem {
systemContract = SystemContract(systemContractAddress);
}

struct Params {
address target;
bytes to;
bool withdraw;
}

function onCrossChainCall(
zContext calldata context,
address zrc20,
uint256 amount,
bytes calldata message
) external virtual override onlySystem(systemContract) {
address target;
bytes memory to;
Params memory params = Params({
target: address(0),
to: bytes(""),
withdraw: true
});

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

address wzeta = systemContract.wZetaContractAddress();
bool isTargetZeta = target == wzeta;
uint256 inputForGas;
address gasZRC20;
uint256 gasFee;

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

inputForGas = SwapHelperLib.swapTokensForExactTokens(
systemContract,
Expand All @@ -59,16 +70,16 @@ contract SwapToZeta is zContract, OnlySystem {
uint256 outputAmount = SwapHelperLib.swapExactTokensForTokens(
systemContract,
zrc20,
isTargetZeta ? amount : amount - inputForGas,
target,
params.withdraw ? amount - inputForGas : amount,
params.target,
0
);

if (isTargetZeta) {
IWETH9(wzeta).transfer(address(uint160(bytes20(to))), outputAmount);
if (params.withdraw) {
IZRC20(gasZRC20).approve(params.target, gasFee);
IZRC20(params.target).withdraw(params.to, outputAmount);
} else {
IZRC20(gasZRC20).approve(target, gasFee);
IZRC20(target).withdraw(to, outputAmount);
IWETH9(params.target).transfer(address(uint160(bytes20(params.to))), outputAmount);
}
}
}
2 changes: 1 addition & 1 deletion omnichain/swap/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
"@types/node": ">=12.0.0",
"@typescript-eslint/eslint-plugin": "^5.59.9",
"@typescript-eslint/parser": "^5.59.9",
"@zetachain/toolkit": "9.0.0",
"@zetachain/toolkit": "10.0.0",
"axios": "^1.3.6",
"chai": "^4.2.0",
"dotenv": "^16.0.3",
Expand Down
12 changes: 9 additions & 3 deletions omnichain/swap/tasks/interact.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,15 @@ const main = async (args: any, hre: HardhatRuntimeEnvironment) => {
recipient = args.recipient;
}

let withdraw = true;
if (args.withdraw != undefined) {
withdraw = JSON.parse(args.withdraw);
}

const data = prepareData(
args.contract,
["address", "bytes"],
[args.targetToken, recipient]
["address", "bytes", "bool"],
[args.targetToken, recipient, withdraw]
);

let tx;
Expand Down Expand Up @@ -75,4 +80,5 @@ task("interact", "Interact with the contract", main)
.addOptionalParam("token", "The address of the token to send")
.addFlag("json", "Output in JSON")
.addParam("targetToken")
.addParam("recipient");
.addParam("recipient")
.addOptionalParam("withdraw");
168 changes: 168 additions & 0 deletions omnichain/swap/test/SwapToAnyToken.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
import { SignerWithAddress } from "@nomiclabs/hardhat-ethers/signers";
import { deployUniswap, deployWZETA, evmSetup } from "@zetachain/toolkit/test";
import { expect } from "chai";
import { defaultAbiCoder, parseEther } from "ethers/lib/utils";
import { ethers } from "hardhat";

import {
MockSystemContract,
MockZRC20,
SwapToAnyToken,
SwapToAnyToken__factory,
TestUniswapRouter,
UniswapV2Factory,
WZETA,
} from "../typechain-types";

describe("SwapToAnyToken", function () {
let uniswapFactory: UniswapV2Factory;
let uniswapRouter: TestUniswapRouter;
let swapToAnyToken: SwapToAnyToken;
let accounts: SignerWithAddress[];
let deployer: SignerWithAddress;
let systemContract: MockSystemContract;
let ZRC20Contracts: MockZRC20[];
let wGasToken: WZETA;

beforeEach(async function () {
accounts = await ethers.getSigners();
[deployer] = accounts;

const wZETA = await deployWZETA(deployer);
wGasToken = wZETA;

const deployResult = await deployUniswap(deployer, wGasToken.address);
uniswapFactory = deployResult.uniswapFactory;
uniswapRouter = deployResult.uniswapRouter;

const evmSetupResult = await evmSetup(
wGasToken.address,
uniswapFactory.address,
uniswapRouter.address
);
ZRC20Contracts = evmSetupResult.ZRC20Contracts;
systemContract = evmSetupResult.systemContract;

const SwapToAnyTokenFactory = (await ethers.getContractFactory(
"SwapToAnyToken"
)) as SwapToAnyToken__factory;

swapToAnyToken = (await SwapToAnyTokenFactory.deploy(systemContract.address)) as SwapToAnyToken;
await swapToAnyToken.deployed();
});

describe("SwapToAnyToken", function () {
it("Should do swap from EVM Chain and withdraw", async function () {
const amount = parseEther("1");
await ZRC20Contracts[0].transfer(systemContract.address, amount);

const initBalance = await ZRC20Contracts[1].balanceOf(deployer.address);

const recipient = ethers.utils.arrayify(deployer.address);

const withdraw = true;

const params = defaultAbiCoder.encode(
["address", "bytes", "bool"],
[ZRC20Contracts[1].address, recipient, withdraw]
);

await systemContract.onCrossChainCall(
1, // ETH chain id
swapToAnyToken.address,
ZRC20Contracts[0].address,
amount,
params,
{ gasLimit: 10_000_000 }
);

const endBalance = await ZRC20Contracts[1].balanceOf(deployer.address);

expect(endBalance).to.be.gt(initBalance);
});

it("Should do swap from EVM Chain and no withdraw", async function () {
const amount = parseEther("1");
await ZRC20Contracts[0].transfer(systemContract.address, amount);

const initBalance = await ZRC20Contracts[1].balanceOf(deployer.address);

const recipient = ethers.utils.arrayify(deployer.address);

const withdraw = false;

const params = defaultAbiCoder.encode(
["address", "bytes", "bool"],
[ZRC20Contracts[1].address, recipient, withdraw]
);

await systemContract.onCrossChainCall(
1, // ETH chain id
swapToAnyToken.address,
ZRC20Contracts[0].address,
amount,
params,
{ gasLimit: 10_000_000 }
);

const endBalance = await ZRC20Contracts[1].balanceOf(deployer.address);
expect(endBalance).to.be.gt(initBalance);
});

it("Should do swap from Bitcoin Chain with withdraw", async function () {
const amount = parseEther("1");
await ZRC20Contracts[0].transfer(systemContract.address, amount);

const initBalance = await ZRC20Contracts[1].balanceOf(deployer.address);
const withdraw = "0x01".slice(2);

const rawMemo = `${ZRC20Contracts[1].address}${deployer.address.slice(
2
)}${withdraw}`;
const rawMemoBytes = ethers.utils.arrayify(rawMemo);

const params = ethers.utils.solidityPack(["bytes"], [rawMemoBytes]);

await systemContract.onCrossChainCall(
18332, // Bitcoin chain id
swapToAnyToken.address,
ZRC20Contracts[0].address,
amount,
params,
{ gasLimit: 10_000_000 }
);

const endBalance = await ZRC20Contracts[1].balanceOf(deployer.address);

expect(endBalance).to.be.gt(initBalance);
});

it("Should do swap from Bitcoin Chain with no withdraw", async function () {
const amount = parseEther("1");
await ZRC20Contracts[0].transfer(systemContract.address, amount);

const initBalance = await ZRC20Contracts[1].balanceOf(deployer.address);
const withdraw = "0x00".slice(2);

const rawMemo = `${ZRC20Contracts[1].address}${deployer.address.slice(
2
)}${withdraw}`;
const rawMemoBytes = ethers.utils.arrayify(rawMemo);

const params = ethers.utils.solidityPack(["bytes"], [rawMemoBytes]);

await systemContract.onCrossChainCall(
18332, // Bitcoin chain id
swapToAnyToken.address,
ZRC20Contracts[0].address,
amount,
params,
{ gasLimit: 10_000_000 }
);

const endBalance = await ZRC20Contracts[1].balanceOf(deployer.address);

expect(endBalance).to.be.gt(initBalance);
});
});
});
Loading

0 comments on commit e1a2ae4

Please sign in to comment.