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: Gateway methods and Solana deposit #179

Merged
merged 18 commits into from
Sep 11, 2024
34 changes: 27 additions & 7 deletions contracts/SwapHelperLib.sol
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,11 @@ library SwapHelperLib {
if (!existsPairPool) {
return false;
}
uint256[] memory amounts = UniswapV2Library.getAmountsOut(uniswapV2Factory, amountIn, path);
uint256[] memory amounts = UniswapV2Library.getAmountsOut(
uniswapV2Factory,
amountIn,
path
);
return amounts[amounts.length - 1] >= minAmountOut;
}

Expand All @@ -114,7 +118,6 @@ library SwapHelperLib {
address targetZRC20,
uint256 minAmountOut
) internal returns (uint256) {

address[] memory path;
path = new address[](2);
path[0] = zrc20;
Expand All @@ -127,7 +130,8 @@ library SwapHelperLib {
path
);

bool isZETA = targetZRC20 == systemContract.wZetaContractAddress() || zrc20 == systemContract.wZetaContractAddress();
bool isZETA = targetZRC20 == systemContract.wZetaContractAddress() ||
zrc20 == systemContract.wZetaContractAddress();

if (!isSufficientLiquidity && !isZETA) {
path = new address[](3);
Expand Down Expand Up @@ -234,20 +238,36 @@ library SwapHelperLib {
return amounts[0];
}

function getMinOutAmount(SystemContract systemContract, address zrc20, address target, uint256 amountIn) public view returns (uint256 minOutAmount) {
function getMinOutAmount(
SystemContract systemContract,
address zrc20,
address target,
uint256 amountIn
) public view returns (uint256 minOutAmount) {
address[] memory path;

path = new address[](2);
path[0] = zrc20;
path[1] = target;
uint[] memory amounts1 = UniswapV2Library.getAmountsOut(systemContract.uniswapv2FactoryAddress(), amountIn, path);
uint[] memory amounts1 = UniswapV2Library.getAmountsOut(
systemContract.uniswapv2FactoryAddress(),
amountIn,
path
);

path = new address[](3);
path[0] = zrc20;
path[1] = systemContract.wZetaContractAddress();
path[2] = target;
uint[] memory amounts2 = UniswapV2Library.getAmountsOut(systemContract.uniswapv2FactoryAddress(), amountIn, path);
uint[] memory amounts2 = UniswapV2Library.getAmountsOut(
systemContract.uniswapv2FactoryAddress(),
amountIn,
path
);

minOutAmount = amounts1[amounts1.length - 1] > amounts2[amounts2.length - 1] ? amounts1[amounts1.length - 1] : amounts2[amounts2.length - 1];
minOutAmount = amounts1[amounts1.length - 1] >
amounts2[amounts2.length - 1]
? amounts1[amounts1.length - 1]
: amounts2[amounts2.length - 1];
}
}
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -87,10 +87,12 @@
"typechain-types"
],
"dependencies": {
"@coral-xyz/anchor": "^0.30.1",
"@inquirer/prompts": "^2.1.1",
"@inquirer/select": "1.1.3",
"@nomiclabs/hardhat-ethers": "^2.2.3",
"@openzeppelin/contracts": "^4.9.6",
"@solana/web3.js": "^1.95.3",
"@uniswap/v2-periphery": "^1.1.0-beta.0",
"@zetachain/faucet-cli": "^4.1.1",
"@zetachain/networks": "^10.0.0",
Expand Down
1 change: 1 addition & 0 deletions packages/client/src/abi/GatewayEVM.sol/GatewayEVM.json

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions packages/client/src/abi/GatewayZEVM.sol/GatewayZEVM.json

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions packages/client/src/abi/ZRC20.sol/ZRC20.json

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions packages/client/src/abi/ZRC20.sol/ZRC20Errors.json

Large diffs are not rendered by default.

14 changes: 14 additions & 0 deletions packages/client/src/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@ import merge from "lodash/merge";

import {
deposit,
evmCall,
evmDeposit,
evmDepositAndCall,
getBalances,
getChainId,
getEndpoint,
Expand All @@ -17,8 +20,12 @@ import {
getZRC20FromERC20,
getZRC20GasToken,
sendZeta,
solanaDeposit,
trackCCTX,
withdraw,
zetachainCall,
zetachainWithdraw,
zetachainWithdrawAndCall,
} from ".";

export interface ZetaChainClientParamsBase {
Expand Down Expand Up @@ -126,4 +133,11 @@ export class ZetaChainClient {
getRefundFee = getRefundFee;
getZRC20FromERC20 = getZRC20FromERC20;
getZRC20GasToken = getZRC20GasToken;
solanaDeposit = solanaDeposit;
zetachainWithdrawAndCall = zetachainWithdrawAndCall;
zetachainWithdraw = zetachainWithdraw;
zetachainCall = zetachainCall;
evmDepositAndCall = evmDepositAndCall;
evmCall = evmCall;
evmDeposit = evmDeposit;
}
48 changes: 48 additions & 0 deletions packages/client/src/evmCall.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import { ethers } from "ethers";

import GatewayABI from "./abi/GatewayEVM.sol/GatewayEVM.json";
import { ZetaChainClient } from "./client";

export const evmCall = async function (
this: ZetaChainClient,
args: {
callOnRevert: boolean;
gasLimit: number;
gasPrice: ethers.BigNumber;
gatewayEvm: string;
onRevertGasLimit: number;
receiver: string;
revertAddress: string;
revertMessage: string;
types: string;
values: any[];
}
) {
const signer = this.signer;
const { utils } = ethers;
const gateway = new ethers.Contract(args.gatewayEvm, GatewayABI.abi, signer);

const encodedParameters = utils.defaultAbiCoder.encode(
JSON.parse(args.types),
args.values
);
const tx = await gateway[
"call(address,bytes,(address,bool,address,bytes,uint256))"
](
args.receiver,
encodedParameters,
{
abortAddress: "0x0000000000000000000000000000000000000000",
callOnRevert: args.callOnRevert,
onRevertGasLimit: args.onRevertGasLimit,
revertAddress: args.revertAddress,
revertMessage: utils.hexlify(utils.toUtf8Bytes(args.revertMessage)),
},
{
gasLimit: args.gasLimit,
gasPrice: args.gasPrice,
}
);

return tx;
};
Comment on lines +6 to +48
Copy link
Contributor

Choose a reason for hiding this comment

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

Review of evmCall function: Potential security and maintainability concerns.

  1. Security Concern: JSON Parsing in Transaction Call
    Direct use of JSON.parse(args.types) in the transaction setup could lead to security vulnerabilities if the input is not properly validated. Consider validating or sanitizing the input before parsing to ensure it does not introduce code injection risks.

  2. Hardcoded Address Usage
    The abortAddress is hardcoded, which might limit flexibility. If this address might need to change based on the network or other conditions, consider making it a configurable parameter.

  3. Dynamic Method Access
    Accessing the contract method using a string key (gateway["call(address,bytes,(address,bool,address,bytes,uint256))"]) assumes the method name is stable. This could lead to maintenance issues if the ABI changes. Consider defining these method names as constants or ensuring they are updated when the ABI changes.

  4. Error Handling
    The function does not explicitly handle errors that could occur during the transaction call. Consider adding try-catch blocks around the transaction call to handle possible rejections or failures gracefully.

68 changes: 68 additions & 0 deletions packages/client/src/evmDeposit.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import ERC20_ABI from "@openzeppelin/contracts/build/contracts/ERC20.json";
import { ethers } from "ethers";

import GatewayABI from "./abi/GatewayEVM.sol/GatewayEVM.json";
import { ZetaChainClient } from "./client";

export const evmDeposit = async function (
this: ZetaChainClient,
args: {
amount: string;
callOnRevert: boolean;
erc20: string;
gasLimit: number;
gasPrice: ethers.BigNumber;
gatewayEvm: string;
onRevertGasLimit: number;
receiver: string;
revertAddress: string;
revertMessage: string;
}
) {
const signer = this.signer;
const { utils } = ethers;
const gateway = new ethers.Contract(args.gatewayEvm, GatewayABI.abi, signer);

const revertOptions = {
abortAddress: "0x0000000000000000000000000000000000000000",
callOnRevert: args.callOnRevert,
onRevertGasLimit: args.onRevertGasLimit,
revertAddress: args.revertAddress,
// not used
revertMessage: utils.hexlify(utils.toUtf8Bytes(args.revertMessage)),
};

const txOptions = {
gasLimit: args.gasLimit,
gasPrice: args.gasPrice,
};
let tx;
if (args.erc20) {
const erc20Contract = new ethers.Contract(
args.erc20,
ERC20_ABI.abi,
signer
);
const decimals = await erc20Contract.decimals();
const value = utils.parseUnits(args.amount, decimals);
await erc20Contract.connect(signer).approve(args.gatewayEvm, value);
const method =
"deposit(address,uint256,address,(address,bool,address,bytes,uint256))";
tx = await gateway[method](
args.receiver,
value,
args.erc20,
revertOptions,
txOptions
);
} else {
const value = utils.parseEther(args.amount);
const method = "deposit(address,(address,bool,address,bytes,uint256))";
tx = await gateway[method](args.receiver, revertOptions, {
...txOptions,
value,
});
}

return tx;
};
Comment on lines +7 to +68
Copy link
Contributor

Choose a reason for hiding this comment

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

Review of evmDeposit function: Good structure with some areas for improvement.

  1. Dynamic Method Access
    Similar to evmCall, the dynamic access of contract methods (gateway[method]) could lead to maintenance issues if the ABI changes. Consider using a more stable method of accessing these functions, possibly by defining method names as constants.

  2. Comment Clarity
    The comment // not used next to revertMessage in the revertOptions object is confusing. If this field is indeed not used, consider removing it to clean up the code. If it is used, update the comment to reflect its purpose.

  3. Error Handling
    As with evmCall, adding explicit error handling around the transaction calls could improve the robustness of the function. Consider implementing try-catch blocks to handle potential rejections or exceptions gracefully.

82 changes: 82 additions & 0 deletions packages/client/src/evmDepositAndCall.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
import ERC20_ABI from "@openzeppelin/contracts/build/contracts/ERC20.json";
import { ethers } from "ethers";

import GatewayABI from "./abi/GatewayEVM.sol/GatewayEVM.json";
import { ZetaChainClient } from "./client";

export const evmDepositAndCall = async function (
this: ZetaChainClient,
args: {
amount: string;
callOnRevert: boolean;
erc20: string;
gasLimit: number;
gasPrice: ethers.BigNumber;
gatewayEvm: string;
onRevertGasLimit: number;
receiver: string;
revertAddress: string;
revertMessage: string;
types: string;
values: any[];
}
) {
const signer = this.signer;
const { utils } = ethers;
const gateway = new ethers.Contract(args.gatewayEvm, GatewayABI.abi, signer);

const revertOptions = {
abortAddress: "0x0000000000000000000000000000000000000000",
callOnRevert: args.callOnRevert,
onRevertGasLimit: args.onRevertGasLimit,
revertAddress: args.revertAddress,
// not used
revertMessage: utils.hexlify(utils.toUtf8Bytes(args.revertMessage)),
};

const txOptions = {
gasLimit: args.gasLimit,
gasPrice: args.gasPrice,
};

const encodedParameters = utils.defaultAbiCoder.encode(
JSON.parse(args.types),
args.values
);
let tx;
if (args.erc20) {
const erc20Contract = new ethers.Contract(
args.erc20,
ERC20_ABI.abi,
signer
);
const decimals = await erc20Contract.decimals();
const value = utils.parseUnits(args.amount, decimals);
await erc20Contract.connect(signer).approve(args.gatewayEvm, value);
const method =
"depositAndCall(address,uint256,address,bytes,(address,bool,address,bytes,uint256))";
tx = await gateway[method](
args.receiver,
value,
args.erc20,
encodedParameters,
revertOptions,
txOptions
);
} else {
const value = utils.parseEther(args.amount);
const method =
"depositAndCall(address,bytes,(address,bool,address,bytes,uint256))";
tx = await gateway[method](
args.receiver,
encodedParameters,
revertOptions,
{
...txOptions,
value,
}
);
}

return tx;
};
Comment on lines +7 to +82
Copy link
Contributor

Choose a reason for hiding this comment

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

Review of evmDepositAndCall Function: Security and Efficiency Concerns

  1. Handling of ERC20 Tokens: The function interacts with ERC20 tokens for deposits. It's crucial to ensure that token addresses and amounts are validated to prevent issues like overflow or interactions with malicious tokens.
  2. Transaction Options Handling: The function configures transaction options such as gas limits and prices. Ensure these are optimized to prevent unnecessary high costs or failed transactions due to low gas limits.
  3. Error Handling and Reverts: The function should handle potential reverts or errors during the deposit and call operations, especially when interacting with external contracts.
  4. Use of ethers Library: Ensure that the ethers library functions are used correctly, particularly in parsing token amounts and handling contract approvals.

Enhance validation of token addresses and amounts. Optimize transaction options for cost-efficiency. Improve error handling to manage exceptions and reverts more effectively.

Loading
Loading