-
Notifications
You must be signed in to change notification settings - Fork 49
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: check withdraw fee in Swap, callMulti example, universal NFT/FT #229
base: main
Are you sure you want to change the base?
Changes from 6 commits
c7ba7e5
142c36c
926a9f9
d07edd7
05d2288
272329d
21cd7be
c0a77f4
63b4a4b
de77a5f
4799aec
c181622
947d450
81325b3
a34b866
d8732f7
0272470
468bece
846d720
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -29,7 +29,7 @@ | |
"@typescript-eslint/eslint-plugin": "^5.59.9", | ||
"@typescript-eslint/parser": "^5.59.9", | ||
"@zetachain/localnet": "4.0.0-rc6", | ||
"@zetachain/toolkit": "13.0.0-rc7", | ||
"@zetachain/toolkit": "13.0.0-rc12", | ||
"axios": "^1.3.6", | ||
"chai": "^4.2.0", | ||
"dotenv": "^16.0.3", | ||
|
@@ -57,6 +57,8 @@ | |
"@solana-developers/helpers": "^2.4.0", | ||
"@solana/spl-memo": "^0.2.5", | ||
"@solana/web3.js": "^1.95.8", | ||
"@zetachain/protocol-contracts": "11.0.0-rc3" | ||
"@zetachain/networks": "10.0.0-rc4", | ||
"@zetachain/protocol-contracts": "11.0.0-rc3", | ||
"@zetachain/protocol-contracts-solana": "2.0.0-rc1" | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 💡 Codebase verification Remove or document unused Solana dependency The
🔗 Analysis chainVerify Solana contract integration requirements. The addition of Run this script to check for Solana-related configuration and documentation: 🏁 Scripts executedThe following scripts were executed for the analysis: Script: #!/bin/bash
# Description: Check for Solana configuration and documentation
# Check for Solana configuration files
echo "Checking for Solana configuration files:"
fd -e yaml -e json "solana" examples/call/
# Check for Solana documentation
echo -e "\nChecking for Solana documentation:"
rg -i "solana" examples/call/README.md || echo "No Solana documentation found in README"
Length of output: 346 Script: #!/bin/bash
# Check for imports and usage of Solana contracts
echo "Checking for Solana contract imports and usage:"
rg -i "@zetachain/protocol-contracts-solana" examples/call/ -A 2
echo -e "\nChecking for Solana-related code patterns:"
rg -i "solana" examples/call/ --type ts --type js --type tsx --type jsx
Length of output: 1163 |
||
} | ||
} | ||
} |
Large diffs are not rendered by default.
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -29,7 +29,7 @@ | |
"@typescript-eslint/eslint-plugin": "^5.59.9", | ||
"@typescript-eslint/parser": "^5.59.9", | ||
"@zetachain/localnet": "4.0.0-rc6", | ||
"@zetachain/toolkit": "13.0.0-rc7", | ||
"@zetachain/toolkit": "13.0.0-rc12", | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 💡 Codebase verification Update dependency versions to align with published releases
🔗 Analysis chainVerify compatibility with release candidate dependencies. The changes introduce release candidate versions of ZetaChain packages:
Run this script to check for breaking changes and compatibility: Also applies to: 60-60 🏁 Scripts executedThe following scripts were executed for the analysis: Script: #!/bin/bash
# Description: Check for breaking changes in the release candidates
# Check the changelog or release notes for breaking changes
gh api repos/zeta-chain/toolkit/releases | jq -r '.[].body' | grep -i "breaking"
gh api repos/zeta-chain/networks/releases | jq -r '.[].body' | grep -i "breaking"
# Check if these versions are the latest available
for pkg in "@zetachain/toolkit" "@zetachain/networks"; do
echo "Checking latest version for $pkg:"
npm view "$pkg" versions --json | jq -r '.[]' | tail -n 5
done
Length of output: 737 |
||
"axios": "^1.3.6", | ||
"chai": "^4.2.0", | ||
"dotenv": "^16.0.3", | ||
|
@@ -57,6 +57,7 @@ | |
"@solana-developers/helpers": "^2.4.0", | ||
"@solana/spl-memo": "^0.2.5", | ||
"@solana/web3.js": "^1.95.8", | ||
"@zetachain/networks": "10.0.0-rc4", | ||
"@zetachain/protocol-contracts": "11.0.0-rc3" | ||
} | ||
} | ||
} |
Large diffs are not rendered by default.
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -17,3 +17,5 @@ cache_forge | |
access_token | ||
|
||
localnet.json | ||
|
||
.openzeppelin |
Original file line number | Diff line number | Diff line change | ||||||||
---|---|---|---|---|---|---|---|---|---|---|
|
@@ -5,6 +5,7 @@ import {SystemContract, IZRC20} from "@zetachain/toolkit/contracts/SystemContrac | |||||||||
import {SwapHelperLib} from "@zetachain/toolkit/contracts/SwapHelperLib.sol"; | ||||||||||
import {BytesHelperLib} from "@zetachain/toolkit/contracts/BytesHelperLib.sol"; | ||||||||||
import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; | ||||||||||
import "@uniswap/v2-periphery/contracts/interfaces/IUniswapV2Router02.sol"; | ||||||||||
|
||||||||||
import {RevertContext, RevertOptions} from "@zetachain/protocol-contracts/contracts/Revert.sol"; | ||||||||||
import "@zetachain/protocol-contracts/contracts/zevm/interfaces/UniversalContract.sol"; | ||||||||||
|
@@ -31,7 +32,8 @@ contract Swap is | |||||||||
error InvalidAddress(); | ||||||||||
error Unauthorized(); | ||||||||||
error ApprovalFailed(); | ||||||||||
error TransferFailed(); | ||||||||||
error TransferFailed(string); | ||||||||||
error InsufficientAmount(string); | ||||||||||
fadeev marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||||
|
||||||||||
event TokenSwap( | ||||||||||
address sender, | ||||||||||
|
@@ -73,6 +75,9 @@ contract Swap is | |||||||||
bool withdraw; | ||||||||||
} | ||||||||||
|
||||||||||
/** | ||||||||||
* @notice Swap tokens from a connected chain to another connected chain or ZetaChain | ||||||||||
*/ | ||||||||||
function onCall( | ||||||||||
MessageContext calldata context, | ||||||||||
address zrc20, | ||||||||||
|
@@ -120,6 +125,9 @@ contract Swap is | |||||||||
withdraw(params, context.sender, gasFee, gasZRC20, out, zrc20); | ||||||||||
} | ||||||||||
|
||||||||||
/** | ||||||||||
* @notice Swap tokens from ZetaChain optionally withdrawing to a connected chain | ||||||||||
*/ | ||||||||||
function swap( | ||||||||||
address inputToken, | ||||||||||
uint256 amount, | ||||||||||
|
@@ -133,7 +141,9 @@ contract Swap is | |||||||||
amount | ||||||||||
); | ||||||||||
if (!success) { | ||||||||||
revert TransferFailed(); | ||||||||||
revert TransferFailed( | ||||||||||
"Failed to transfer ZRC-20 tokens from the sender to the contract" | ||||||||||
); | ||||||||||
fadeev marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||||
} | ||||||||||
|
||||||||||
(uint256 out, address gasZRC20, uint256 gasFee) = handleGasAndSwap( | ||||||||||
|
@@ -163,6 +173,9 @@ contract Swap is | |||||||||
); | ||||||||||
} | ||||||||||
|
||||||||||
/** | ||||||||||
* @notice Swaps enough tokens to pay gas fees, then swaps the remainder to the target token | ||||||||||
*/ | ||||||||||
function handleGasAndSwap( | ||||||||||
address inputToken, | ||||||||||
uint256 amount, | ||||||||||
|
@@ -175,6 +188,13 @@ contract Swap is | |||||||||
|
||||||||||
(gasZRC20, gasFee) = IZRC20(targetToken).withdrawGasFee(); | ||||||||||
|
||||||||||
uint256 minInput = quoteMinInput(inputToken, targetToken); | ||||||||||
if (amount < minInput) { | ||||||||||
revert InsufficientAmount( | ||||||||||
"The input amount is less than the min amount required to cover the withdraw gas fee" | ||||||||||
); | ||||||||||
Comment on lines
+193
to
+195
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion Provide detailed error information using fixed-size data types To offer precise feedback while maintaining gas efficiency, update the Apply this diff: if (amount < minInput) {
- revert InsufficientAmount(
- "The input amount is less than the min amount required to cover the withdraw gas fee"
- );
+ revert InsufficientAmount(minInput, amount);
}
|
||||||||||
} | ||||||||||
|
||||||||||
if (gasZRC20 == inputToken) { | ||||||||||
swapAmount = amount - gasFee; | ||||||||||
} else { | ||||||||||
|
@@ -198,6 +218,9 @@ contract Swap is | |||||||||
return (out, gasZRC20, gasFee); | ||||||||||
} | ||||||||||
|
||||||||||
/** | ||||||||||
* @notice Transfer tokens to the recipient on ZetaChain or withdraw to a connected chain | ||||||||||
*/ | ||||||||||
function withdraw( | ||||||||||
Params memory params, | ||||||||||
address sender, | ||||||||||
|
@@ -237,11 +260,17 @@ contract Swap is | |||||||||
out | ||||||||||
); | ||||||||||
if (!success) { | ||||||||||
revert TransferFailed(); | ||||||||||
revert TransferFailed( | ||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I should check but the good part of custom errors is that reduce extremely bytecode size because uses the hash instead of strings. if you hardcode a string as msg IMO will store the same size we don't want to... params in custom errors should be to extra info like values, not messages. If you want to differentiate two errores create a new one. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The problem with custom errors without messages is that when looking up a tx, it shows the error as A message on the other hand can be easily decoded. Bytecode size isn't particularly important on ZetaChain, because the gas costs are low. |
||||||||||
"Failed to transfer target tokens to the recipient on ZetaChain" | ||||||||||
); | ||||||||||
Comment on lines
+263
to
+265
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion Align revert statement with updated Following the optimization of the Apply this diff: if (!success) {
- revert TransferFailed(
- "Failed to transfer target tokens to the recipient on ZetaChain"
- );
+ revert TransferFailed();
} 📝 Committable suggestion
Suggested change
|
||||||||||
} | ||||||||||
} | ||||||||||
} | ||||||||||
|
||||||||||
/** | ||||||||||
* @notice onRevert handles an edge-case when a swap fails when the recipient | ||||||||||
* on the destination chain is a contract that cannot accept tokens. | ||||||||||
*/ | ||||||||||
function onRevert(RevertContext calldata context) external onlyGateway { | ||||||||||
(address sender, address zrc20) = abi.decode( | ||||||||||
context.revertMessage, | ||||||||||
|
@@ -267,6 +296,40 @@ contract Swap is | |||||||||
); | ||||||||||
} | ||||||||||
|
||||||||||
/** | ||||||||||
* @notice Returns the minimum amount of input tokens required to cover the gas fee for withdrawal | ||||||||||
*/ | ||||||||||
function quoteMinInput( | ||||||||||
address inputToken, | ||||||||||
address targetToken | ||||||||||
) public view returns (uint256) { | ||||||||||
(address gasZRC20, uint256 gasFee) = IZRC20(targetToken) | ||||||||||
.withdrawGasFee(); | ||||||||||
|
||||||||||
if (inputToken == gasZRC20) { | ||||||||||
return gasFee; | ||||||||||
} | ||||||||||
|
||||||||||
address zeta = IUniswapV2Router01(uniswapRouter).WETH(); | ||||||||||
|
||||||||||
address[] memory path; | ||||||||||
if (inputToken == zeta || gasZRC20 == zeta) { | ||||||||||
path = new address[](2); | ||||||||||
path[0] = inputToken; | ||||||||||
path[1] = gasZRC20; | ||||||||||
} else { | ||||||||||
path = new address[](3); | ||||||||||
path[0] = inputToken; | ||||||||||
path[1] = zeta; | ||||||||||
path[2] = gasZRC20; | ||||||||||
} | ||||||||||
|
||||||||||
uint256[] memory amountsIn = IUniswapV2Router02(uniswapRouter) | ||||||||||
.getAmountsIn(gasFee, path); | ||||||||||
|
||||||||||
return amountsIn[0]; | ||||||||||
} | ||||||||||
|
||||||||||
function _authorizeUpgrade( | ||||||||||
address newImplementation | ||||||||||
) internal override onlyOwner {} | ||||||||||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -3,13 +3,51 @@ import type { HardhatRuntimeEnvironment } from "hardhat/types"; | |
|
||
import { ZetaChainClient } from "@zetachain/toolkit/client"; | ||
|
||
export const isInputAmountSufficient = async ({ | ||
hre, | ||
client, | ||
erc20, | ||
amount, | ||
target, | ||
}: any) => { | ||
Comment on lines
+6
to
+12
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion Add proper TypeScript types instead of using 'any'. Replace the 'any' type with a proper interface definition for better type safety and code maintainability. +interface InputAmountCheckParams {
+ hre: HardhatRuntimeEnvironment;
+ client: ZetaChainClient;
+ erc20?: string;
+ amount: string;
+ target: string;
+}
-export const isInputAmountSufficient = async ({
- hre,
- client,
- erc20,
- amount,
- target,
-}: any) => {
+export const isInputAmountSufficient = async ({
+ hre,
+ client,
+ erc20,
+ amount,
+ target,
+}: InputAmountCheckParams) => {
|
||
const inputZRC20 = await (erc20 | ||
? client.getZRC20FromERC20(erc20) | ||
: client.getZRC20GasToken(hre.network.name)); | ||
|
||
const minAmount = await client.getWithdrawFeeInInputToken(inputZRC20, target); | ||
|
||
const minAmountFormatted = hre.ethers.utils.formatUnits( | ||
minAmount.amount, | ||
minAmount.decimals | ||
); | ||
|
||
const value = hre.ethers.utils.parseUnits(amount, minAmount.decimals); | ||
Comment on lines
+19
to
+24
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ensure consistent decimal units when parsing amounts When parsing the Apply this diff: const minAmountFormatted = hre.ethers.utils.formatUnits(
minAmount.amount,
- minAmount.decimals
+ inputTokenDecimals
);
- const value = hre.ethers.utils.parseUnits(amount, minAmount.decimals);
+ const value = hre.ethers.utils.parseUnits(amount, inputTokenDecimals); Ensure that const inputTokenDecimals = minAmount.decimals; // Or fetch from inputZRC20 if necessary |
||
|
||
if (value.lt(minAmount.amount)) { | ||
throw new Error( | ||
`Input amount ${amount} is less than minimum amount ${minAmountFormatted} required for a withdrawal to the destination chain` | ||
); | ||
} | ||
}; | ||
|
||
export const evmDepositAndCall = async ( | ||
args: any, | ||
hre: HardhatRuntimeEnvironment | ||
) => { | ||
try { | ||
const [signer] = await hre.ethers.getSigners(); | ||
const client = new ZetaChainClient({ network: "testnet", signer }); | ||
|
||
if (!args.skipChecks) { | ||
await isInputAmountSufficient({ | ||
hre, | ||
client, | ||
amount: args.amount, | ||
erc20: args.erc20, | ||
target: args.target, | ||
}); | ||
} | ||
|
||
const tx = await client.evmDepositAndCall({ | ||
amount: args.amount, | ||
erc20: args.erc20, | ||
|
@@ -72,6 +110,7 @@ task("evm-swap", "Swap tokens from EVM", evmDepositAndCall) | |
.addOptionalParam("revertMessage", "Revert message", "0x") | ||
.addParam("amount", "amount of ETH to send with the transaction") | ||
.addOptionalParam("erc20", "ERC-20 token address") | ||
.addFlag("skipChecks", "Skip checks for minimum amount") | ||
.addParam("target", "ZRC-20 address of the token to swap for") | ||
.addParam("recipient", "Recipient address") | ||
.addOptionalParam( | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
💡 Codebase verification
Version inconsistency detected in example projects
The
@zetachain/toolkit
package has inconsistent versions across examples:13.0.0-rc12
: hello, swap, call13.0.0-rc8
: nft, tokenConsider updating all examples to use the latest release candidate version
13.0.0-rc12
for consistency.🔗 Analysis chain
Ensure version consistency across example projects.
The changes introduce multiple release candidate dependencies:
@zetachain/toolkit
to13.0.0-rc12
@zetachain/networks
at10.0.0-rc4
@zetachain/protocol-contracts-solana
at2.0.0-rc1
Run this script to verify version consistency across all example projects:
Also applies to: 60-62
🏁 Scripts executed
The following scripts were executed for the analysis:
Script:
Length of output: 3335
Script:
Length of output: 2613