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: add Solana interact task #192

Merged
merged 3 commits into from
Aug 30, 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
5 changes: 5 additions & 0 deletions omnichain/swap/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,14 @@ coverage
coverage.json
typechain
typechain-types
dependencies

# Hardhat files
cache
artifacts

# Foundry files
out
cache_forge

access_token
178 changes: 5 additions & 173 deletions omnichain/swap/README.md
Original file line number Diff line number Diff line change
@@ -1,183 +1,15 @@
# Template for a ZetaChain Hardhat Project

This is a simple Hardhat template that provides a starting point for developing
smart contract applications on the ZetaChain blockchain.

## Prerequisites

Before getting started, ensure that you have
[Node.js](https://nodejs.org/en/download) and [Yarn](https://yarnpkg.com/)
installed on your system.
# ZetaChain Contracts Template

## Getting Started

To get started, install the necessary dependencies:
Install dependencies:

```
yarn
```

## Hardhat Tasks

This template includes Hardhat tasks that can be used make it easier to build
with ZetaChain.

### Generating a Random Wallet

To generate a random wallet:

```
npx hardhat account --save
```

This command generates a random wallet, prints information about the wallet to
the terminal, and saves the private key to a `.env` file to make it accessible
to Hardhat. If you don't want to save the wallet (for example, if you just need
an address to send tokens to for testing purposes), you can run the command
without the `--save` flag.

### Querying for Token Balances

To query for token balances:

```
npx hardhat balances
```

This command queries token balances for the account address derived from the
private key specified in the `.env`.

If you want to query for token balances for a different account, you can use the
`--address` flag:

```
npx hardhat balances --address ADDRESS
```

### Requesting Tokens from the Faucet

To request ZETA tokens from the faucet:

```
npx hardhat faucet
```

This command requests tokens from the faucet for the account address derived
from the private key specified in the `.env`. Tokens sent to the address on
ZetaChain. To specify a different chain use a flag:

```
npx hardhat faucet --chain goerli_testnet
```

You can also specify a different address to send the tokens to:

```
npx hardhat faucet --address ADDRESS
```

Alternatively, you can install a standalone faucet CLI:

```
yarn global add @zetachain/faucet-cli@athens3
```

You can then use it with the following command:

```
zetafaucet -h
```

### Creating an Omnichain Contract

To create a new omnichain contract:

```
npx hardhat omnichain MyContract
```

This command creates a new omnichain contract in `contracts/MyContract.sol`, a
task to deploy the contract in `tasks/deploy.ts`, and a task to interact with
the contract in `tasks/interact.ts`.

When an omnichain contract is called, it can receive data in the `data` field of
a transaction. This data is passed to the `message` parameter of the contract's
`onCrossChainCall` function. To specify the fields of the `message` parameter,
use positional arguments:

```
npx hardhat omnichain MyContract recepient:address description quantity:uint256
```

A field may have a type specified after the field name, separated by a colon. If
no type is specified, the type defaults to `bytes32`.

Learn more about omnichain contracts by following the
[tutorials](https://www.zetachain.com/docs/developers/omnichain/tutorials/hello/).

### Tracking a Cross-Chain Transaction

After broadcasting a cross-chain transaction on a connected chain either to a
cross-chain messaging contract or to trigger an omnichain contract, you can
track its status:

```
npx hardhat cctx --tx TX_HASH
```

### Verifying a Contract

To verify a contract deployed on ZetaChain:

```
npx hardhat verify:zeta --contract ADDRESS
```

Select the contract to verify:

```
? Select a contract to verify: (Use arrow keys)
@zetachain/zevm-protocol-contracts/contracts/interfaces/IZRC20.sol:IZRC20
@zetachain/zevm-protocol-contracts/contracts/interfaces/zContract.sol:zContract
❯ contracts/Withdraw.sol:Withdraw
```

After the confirmation the contract will be verified.

### Sending Tokens

Sending ZETA from ZetaChain to Goerli:

```
npx hardhat send-zeta --amount 1 --network zeta_testnet --destination goerli_testnet
```

Sending ZETA from Goerli to ZetaChain:

```
npx hardhat send-zeta --amount 1 --network goerli_testnet --destination zeta_testnet
```

Depositing gETH to ZetaChain as ZRC-20:

```
npx hardhat send-zrc20 --amount 1 --network goerli_testnet --destination zeta_testnet
```

Withdrawing ZRC-20 from ZetaChain go Goerli as gETH:

```
npx hardhat send-zrc20 --amount 1 --network zeta_testnet --destination goerli_testnet
```

Depositing tBTC from the Bitcoin testnet to ZetaChain:

```
npx hardhat send-btc --amount 1 --recipient TSS_ADDRESS --memo RECIPIENT_ADDRESS_WITHOUT_0x
```

## Next Steps

To learn more about building decentralized apps on ZetaChain, follow the
tutorials available in
[the documentation](https://www.zetachain.com/docs/developers/overview/).
Ready to dive in? Follow our [**🚀 smart contract
tutorials**](https://www.zetachain.com/docs/developers/tutorials/intro/) to
start building universal app contracts.
21 changes: 6 additions & 15 deletions omnichain/swap/contracts/Swap.sol
Original file line number Diff line number Diff line change
Expand Up @@ -42,38 +42,29 @@ contract Swap is zContract, OnlySystem {
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(targetToken).withdrawGasFee();
(gasZRC20, gasFee) = IZRC20(params.target).withdrawGasFee();

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

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

IZRC20(gasZRC20).approve(targetToken, gasFee);
IZRC20(targetToken).withdraw(recipient, outputAmount);
IZRC20(gasZRC20).approve(params.target, gasFee);
IZRC20(params.target).withdraw(params.to, outputAmount);
Comment on lines +49 to +68
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add comments explaining the new params structure.

The changes improve the clarity and consistency of the function but could benefit from additional comments explaining the new params structure.

Add comments to explain the new params structure.

Apply this diff to enhance the function:

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

    if (context.chainID == BITCOIN) {
        params.target = BytesHelperLib.bytesToAddress(message, 0);
        params.to = abi.encodePacked(
            BytesHelperLib.bytesToAddress(message, 20)
        );
    } else {
        (address targetToken, bytes memory recipient) = abi.decode(
            message,
            (address, bytes)
        );
        params.target = targetToken;
        params.to = recipient;
    }

    uint256 inputForGas;
    address gasZRC20;
    uint256 gasFee;

    // Retrieve the gas fee for the target token
    (gasZRC20, gasFee) = IZRC20(params.target).withdrawGasFee();

    // Swap tokens for the exact amount of gas fee
    inputForGas = SwapHelperLib.swapTokensForExactTokens(
        systemContract,
        zrc20,
        gasFee,
        gasZRC20,
        amount
    );

    // Swap the remaining tokens for the target token
    uint256 outputAmount = SwapHelperLib.swapExactTokensForTokens(
        systemContract,
        zrc20,
        amount - inputForGas,
        params.target,
        0
    );

    // Approve the target token for withdrawal
    IZRC20(gasZRC20).approve(params.target, gasFee);
    // Withdraw the target token to the recipient
    IZRC20(params.target).withdraw(params.to, outputAmount);
}
Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
(gasZRC20, gasFee) = IZRC20(params.target).withdrawGasFee();
inputForGas = SwapHelperLib.swapTokensForExactTokens(
systemContract,
inputToken,
zrc20,
gasFee,
gasZRC20,
amount
);
uint256 outputAmount = SwapHelperLib.swapExactTokensForTokens(
systemContract,
inputToken,
zrc20,
amount - inputForGas,
targetToken,
params.target,
0
);
IZRC20(gasZRC20).approve(targetToken, gasFee);
IZRC20(targetToken).withdraw(recipient, outputAmount);
IZRC20(gasZRC20).approve(params.target, gasFee);
IZRC20(params.target).withdraw(params.to, outputAmount);
function onCrossChainCall(
zContext calldata context,
address zrc20,
uint256 amount,
bytes calldata message
) external virtual override onlySystem(systemContract) {
Params memory params = Params({target: address(0), to: bytes("")});
if (context.chainID == BITCOIN) {
params.target = BytesHelperLib.bytesToAddress(message, 0);
params.to = abi.encodePacked(
BytesHelperLib.bytesToAddress(message, 20)
);
} else {
(address targetToken, bytes memory recipient) = abi.decode(
message,
(address, bytes)
);
params.target = targetToken;
params.to = recipient;
}
uint256 inputForGas;
address gasZRC20;
uint256 gasFee;
// Retrieve the gas fee for the target token
(gasZRC20, gasFee) = IZRC20(params.target).withdrawGasFee();
// Swap tokens for the exact amount of gas fee
inputForGas = SwapHelperLib.swapTokensForExactTokens(
systemContract,
zrc20,
gasFee,
gasZRC20,
amount
);
// Swap the remaining tokens for the target token
uint256 outputAmount = SwapHelperLib.swapExactTokensForTokens(
systemContract,
zrc20,
amount - inputForGas,
params.target,
0
);
// Approve the target token for withdrawal
IZRC20(gasZRC20).approve(params.target, gasFee);
// Withdraw the target token to the recipient
IZRC20(params.target).withdraw(params.to, outputAmount);
}

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

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

uint256 constant BITCOIN = 18332;
Expand All @@ -24,8 +23,6 @@ contract SwapToAnyToken is zContract, OnlySystem {
bool withdraw;
}

receive() external payable {}

function onCrossChainCall(
zContext calldata context,
address zrc20,
Expand All @@ -40,49 +37,30 @@ contract SwapToAnyToken is zContract, OnlySystem {

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 (withdraw) {
(gasZRC20, gasFee) = IZRC20(targetToken).withdrawGasFee();
if (params.withdraw) {
(gasZRC20, gasFee) = IZRC20(params.target).withdrawGasFee();

inputForGas = SwapHelperLib.swapTokensForExactTokens(
systemContract,
inputToken,
zrc20,
gasFee,
gasZRC20,
amount
Expand All @@ -91,45 +69,17 @@ contract SwapToAnyToken is zContract, OnlySystem {

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

if (withdraw) {
IZRC20(gasZRC20).approve(targetToken, gasFee);
IZRC20(targetToken).withdraw(recipient, outputAmount);
if (params.withdraw) {
IZRC20(gasZRC20).approve(params.target, gasFee);
IZRC20(params.target).withdraw(params.to, outputAmount);
} else {
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();
}
IWETH9(params.target).transfer(address(uint160(bytes20(params.to))), outputAmount);
}
}

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);
}
}
2 changes: 1 addition & 1 deletion omnichain/swap/contracts/shared/TestUniswapCore.sol
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.5.16;

import "@zetachain/toolkit/contracts/shared/TestUniswapCore.sol";
import "@zetachain/toolkit/contracts/shared/TestUniswapCore.sol";
2 changes: 1 addition & 1 deletion omnichain/swap/contracts/shared/TestUniswapRouter.sol
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.7;

import "@zetachain/toolkit/contracts/shared/TestUniswapRouter.sol";
import "@zetachain/toolkit/contracts/shared/TestUniswapRouter.sol";
Loading
Loading