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

Swap tutorial: ERC-20 support #209

Merged
merged 3 commits into from
Dec 21, 2023
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
2 changes: 1 addition & 1 deletion docs/developers/omnichain/system-contract.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ information you may need in your protocol. You can import this code into your
project and instance with the deployed address. You can find the up-to-date
address of the system contract using the ZetaChain's API:

https://zetachain-athens.blockpi.network/lcd/v1/public/zeta-chain/zetacore/fungible/system_contract
https://zetachain-athens.blockpi.network/lcd/v1/public/zeta-chain/fungible/system_contract

In this contract you will find:

Expand Down
2 changes: 1 addition & 1 deletion docs/developers/omnichain/tutorials/curve.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ it take a look to official script, does all the work for you out of the box
[deployment script](https://github.com/curvefi/curve-contract/blob/master/scripts/deploy.py)),
using the address of three ZRC-2020 tokens. You can find the ZetaChain addresses
of ZRC-20 tokens supported right now on the ZetaChain testnet using this
[endpoint](https://zetachain-athens.blockpi.network/lcd/v1/public/zeta-chain/zetacore/fungible/foreign_coins).
[endpoint](https://zetachain-athens.blockpi.network/lcd/v1/public/zeta-chain/fungible/foreign_coins).

## Implement a cross-chain stableswap

Expand Down
71 changes: 61 additions & 10 deletions docs/developers/omnichain/tutorials/hello.md
Original file line number Diff line number Diff line change
Expand Up @@ -98,27 +98,31 @@ The constructor function accepts the address of the system contract and stores
it in the `systemContract` state variable.

`onCrossChainCall` is a function that is called when the contract gets called by
a token transfer transaction sent to the TSS address on a connected chain. The
function receives the following inputs:
a token transfer transaction sent to the TSS address on a connected chain (when
a gas token is deposited) or a `deposit` method call on the ERC-20 custody
contract (when an ERC-20 token is deposited). The function receives the
following inputs:

- `context`: is a struct of type
[`zContext`](https://github.com/zeta-chain/protocol-contracts/blob/main/contracts/zevm/interfaces/zContract.sol)
that contains the following values:
- `origin`: EOA address that sent the token transfer transaction to the TSS
address (triggering the omnichain contract)
address (triggering the omnichain contract) or the value passed to the
`deposit` method call on the ERC-20 custody contract.
- `chainID`: interger ID of the connected chain from which the omnichain
contract was triggered.
- `sender` (reserved for future use, currently empty)
- `zrc20`: the address of the ZRC-20 token contract that represents an asset
from a connected chain on ZetaChain.
- `amount`: the amount of tokens that were transferred to the TSS address.
- `amount`: the amount of tokens that were transferred to the TSS address or an
amount of tokens that were deposited to the ERC-20 custody contract.
- `message`: the contents of the `data` field of the token transfer transaction.

The `onCrossChainCall` function should only be called by the system contract (in
other words, by the ZetaChain protocol) to prevent a caller from supplying
arbitrary values in `context`. The `onlySystem` modifier ensures that the
function is called only as a response to a token transfer transaction sent to
the TSS address.
the TSS address or an ERC-20 custody contract.

By default, the `onCrossChainCall` function doesn't do anything else. You will
implement the logic yourself based on your use case.
Expand Down Expand Up @@ -183,18 +187,40 @@ contract:
```ts title="tasks/interact.ts"
import { task } from "hardhat/config";
import { HardhatRuntimeEnvironment } from "hardhat/types";
import { parseEther } from "@ethersproject/units";
import { parseUnits } from "@ethersproject/units";
import { getAddress } from "@zetachain/protocol-contracts";
import ERC20Custody from "@zetachain/protocol-contracts/abi/evm/ERC20Custody.sol/ERC20Custody.json";
import { prepareData } from "@zetachain/toolkit/helpers";
import { utils, ethers } from "ethers";
import ERC20 from "@openzeppelin/contracts/build/contracts/ERC20.json";

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

const data = prepareData(args.contract, [], []);
const to = getAddress("tss", hre.network.name);
const value = parseEther(args.amount);

const tx = await signer.sendTransaction({ data, to, value });
let tx;

if (args.token) {
const custodyAddress = getAddress("erc20Custody", hre.network.name as any);
const custodyContract = new ethers.Contract(
custodyAddress,
ERC20Custody.abi,
signer
);
const tokenContract = new ethers.Contract(args.token, ERC20.abi, signer);
const decimals = await tokenContract.decimals();
const value = parseUnits(args.amount, decimals);
const approve = await tokenContract.approve(custodyAddress, value);
await approve.wait();

tx = await custodyContract.deposit(signer.address, args.token, value, data);
tx.wait();
} else {
const value = parseUnits(args.amount, 18);
const to = getAddress("tss", hre.network.name as any);
tx = await signer.sendTransaction({ data, to, value });
}

if (args.json) {
console.log(JSON.stringify(tx, null, 2));
Expand All @@ -203,13 +229,14 @@ const main = async (args: any, hre: HardhatRuntimeEnvironment) => {

console.log(`🚀 Successfully broadcasted a token transfer transaction on ${hre.network.name} network.
📝 Transaction hash: ${tx.hash}
`);
`);
}
};

task("interact", "Interact with the contract", main)
.addParam("contract", "The address of the withdraw contract on ZetaChain")
.addParam("amount", "Amount of tokens to send")
.addOptionalParam("token", "The address of the token to send")
.addFlag("json", "Output in JSON");
```

Expand All @@ -226,6 +253,30 @@ information:
In the code generated above there are no arguments, so the `data` field is
simply the address of the contract on ZetaChain.

Calling omnichain contracts is differs depending on whether a gas token is being
deposited or an ERC-20 token.

If an ERC-20 token address is passed to the `--token` optional parameter, the
interact task assumes you want to deposit an ERC-20 token in an omnichain
contract.

To deposit an ERC-20 token into an omnichain contract you need to call the
`deposit` method of the ERC-20 custody contract. The task first gets the address
of the custody contract on the current network, creates an instance of a token
contract, gets the number of decimals of the token, and approves the custody
contract to spend the specified amount of ERC-20 tokens. The task then calls the
`deposit` method of the custody contract, passing the following information:

- `signer.address`: the sender address that will be available in the `origin`
field of the `context` parameter of the `onCrossChainCall` function
- `args.token`: the address of the ERC-20 token being deposited
- `value`: the amount of tokens being deposited
- `data`: the contents of the `message`

If the `--token` optional parameter is not used, the interact task assumes you
want to deposit a gas token. To deposit a gas token you need to send a token
transfer transaction to the TSS address on a connected chain.

`getAddress` retrieves the address of the TSS on the current network.

The task then uses Ethers.js to send a token transfer transaction to the TSS
Expand Down
Loading
Loading