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

Refactor: Gateway functions to get Gateway address dynamically #205

Draft
wants to merge 2 commits into
base: main
Choose a base branch
from
Draft
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 package.json
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@
"@solana/web3.js": "^1.95.3",
"@uniswap/v2-periphery": "^1.1.0-beta.0",
"@zetachain/faucet-cli": "^4.1.1",
"@zetachain/networks": "v10.0.0",
"@zetachain/networks": "v10.0.0-rc1",
"@zetachain/protocol-contracts": "11.0.0-rc3",
"axios": "^1.4.0",
"bech32": "^2.0.0",
Expand Down
71 changes: 71 additions & 0 deletions packages/client/src/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ import type { Wallet as SolanaWallet } from "@coral-xyz/anchor";
import type { WalletContextState } from "@solana/wallet-adapter-react";
import { PublicKey } from "@solana/web3.js";
import { networks } from "@zetachain/networks";
import mainnetAddresses from "@zetachain/protocol-contracts/dist/data/addresses.mainnet.json";
import testnetAddresses from "@zetachain/protocol-contracts/dist/data/addresses.testnet.json";
import type { Signer, Wallet } from "ethers";
import merge from "lodash/merge";

Expand Down Expand Up @@ -31,6 +33,7 @@ import {

export interface ZetaChainClientParamsBase {
chains?: { [key: string]: any };
contracts?: LocalnetAddress[] | MainnetTestnetAddress[];
network?: string;
}

Expand Down Expand Up @@ -68,13 +71,28 @@ export type ZetaChainClientParams = ZetaChainClientParamsBase &
}
);

interface MainnetTestnetAddress {
address: string;
category: string;
chain_id: number;
chain_name: string;
type: string;
}

interface LocalnetAddress {
address: string;
chain: string;
type: string;
}

export class ZetaChainClient {
public chains: { [key: string]: any };
public network: string;
public wallet: Wallet | undefined;
public signer: any | undefined;
public solanaWallet: SolanaWallet | undefined;
public solanaAdapter: WalletContextState | undefined;
private contracts: LocalnetAddress[] | MainnetTestnetAddress[];

/**
* Initializes ZetaChainClient instance.
Expand Down Expand Up @@ -136,6 +154,16 @@ export class ZetaChainClient {
this.chains = { ...networks };
this.network = params.network || "";

if (params.contracts) {
this.contracts = params.contracts;
} else if (this.network === "localnet" || this.network === "localhost") {
throw new Error("Localnet contracts are required");
} else {
this.contracts = this.network.includes("test")
? testnetAddresses
: mainnetAddresses;
}

this.mergeChains(params.chains);
}

Expand All @@ -147,6 +175,49 @@ export class ZetaChainClient {
});
}

public getGatewayAddress(): string {
if (this.network === "localnet" || this.network === "localhost") {
const gateway = (this.contracts as LocalnetAddress[]).find(
(item) => item.type === "gatewayZEVM"
);

if (!gateway) {
throw new Error("Gateway address not found in localnet configuration");
}

return gateway.address;
} else {
let gateway;
if (this.wallet) {
try {
gateway = (this.contracts as MainnetTestnetAddress[]).find(
async (item) =>
(await this.wallet!.getChainId()) === item.chain_id &&
item.type === "gateway"
);
} catch (error) {
throw new Error("Failed to get gateway address: " + error);
}
} else {
try {
gateway = (this.contracts as MainnetTestnetAddress[]).find(
async (item) =>
(await this.signer!.getChainId()) === item.chain_id &&
item.type === "gateway"
);
} catch (error) {
throw new Error("Failed to get gateway address: " + error);
}
}

if (!gateway) {
throw new Error(`Gateway address not found in signer or wallet`);
}

return gateway.address;
}
}
Comment on lines +178 to +219
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Async in Array.find across Lines 193-207 Might Not Behave as Expected

The code block uses an async callback inside Array.find, for example:

.find(async (item) => (await this.wallet!.getChainId()) === item.chain_id && item.type === "gateway")

However, Array.find does not wait for async callbacks to complete before deciding whether to include the element. This could result in unintended behavior, as the promise returned by the async callback is not utilized in a typical synchronous iteration.

Please consider one of the following approaches instead:

  1. Use a for-of loop with an await inside.
  2. Use Promise.all to compute chain IDs and then filter or find synchronously.

Below is a potential fix example using a simple for-of loop:

- gateway = (this.contracts as MainnetTestnetAddress[]).find(
-   async (item) =>
-     (await this.wallet!.getChainId()) === item.chain_id && item.type === "gateway"
- );

+ const chainId = await this.wallet!.getChainId();
+ gateway = (this.contracts as MainnetTestnetAddress[]).find(
+   (item) => chainId === item.chain_id && item.type === "gateway"
+ );


public getChains(): { [key: string]: any } {
return this.chains;
}
Expand Down
9 changes: 7 additions & 2 deletions packages/client/src/evmCall.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ import type { revertOptions, txOptions } from "./types";
export const evmCall = async function (
this: ZetaChainClient,
args: {
gatewayEvm: string;
gatewayEvm?: string;
receiver: string;
revertOptions: revertOptions;
txOptions: txOptions;
Expand All @@ -34,7 +34,12 @@ export const evmCall = async function (
) {
const signer = this.signer;
const { utils } = ethers;
const gateway = new ethers.Contract(args.gatewayEvm, GatewayABI.abi, signer);
const gatewayEvmAddress = args.gatewayEvm || this.getGatewayAddress();
const gateway = new ethers.Contract(
gatewayEvmAddress,
GatewayABI.abi,
signer
);

const valuesArray = args.values.map((value, index) => {
const type = args.types[index];
Expand Down
9 changes: 7 additions & 2 deletions packages/client/src/evmDeposit.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,15 +27,20 @@ export const evmDeposit = async function (
args: {
amount: string;
erc20: string;
gatewayEvm: string;
gatewayEvm?: string;
receiver: string;
revertOptions: revertOptions;
txOptions: txOptions;
}
) {
const signer = this.signer;
const { utils } = ethers;
const gateway = new ethers.Contract(args.gatewayEvm, GatewayABI.abi, signer);
const gatewayEvmAddress = args.gatewayEvm || this.getGatewayAddress();
const gateway = new ethers.Contract(
gatewayEvmAddress,
GatewayABI.abi,
signer
);
Comment on lines +38 to +43
Copy link
Contributor

Choose a reason for hiding this comment

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

💡 Codebase verification

Inconsistent gateway address usage detected

The code shows an inconsistency where gatewayEvmAddress is correctly resolved at the beginning of the function, but args.gatewayEvm is directly used in the ERC20 approve call. This should be updated to use the resolved gatewayEvmAddress instead to maintain consistency and respect the fallback logic.

  • Line 43: await erc20Contract.connect(signer).approve(args.gatewayEvm, value); should be changed to use gatewayEvmAddress
🔗 Analysis chain

Verify consistent gateway address usage

The gateway address resolution looks good, matching the pattern in evmCall.ts. However, we should verify that all gateway address usages within the function are updated to use the resolved address.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Search for any remaining direct uses of args.gatewayEvm
rg "args\.gatewayEvm" "packages/client/src/evmDeposit.ts"

Length of output: 320


Script:

#!/bin/bash
# Get more context around the approve call to understand the flow
rg -A 5 -B 5 "approve\(args\.gatewayEvm" "packages/client/src/evmDeposit.ts"

Length of output: 466


const revertOptions = {
abortAddress: "0x0000000000000000000000000000000000000000", // not used
Expand Down
9 changes: 7 additions & 2 deletions packages/client/src/evmDepositAndCall.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ export const evmDepositAndCall = async function (
args: {
amount: string;
erc20: string;
gatewayEvm: string;
gatewayEvm?: string;
receiver: string;
revertOptions: revertOptions;
txOptions: txOptions;
Expand All @@ -39,7 +39,12 @@ export const evmDepositAndCall = async function (
) {
const signer = this.signer;
const { utils } = ethers;
const gateway = new ethers.Contract(args.gatewayEvm, GatewayABI.abi, signer);
const gatewayEvmAddress = args.gatewayEvm || this.getGatewayAddress();
const gateway = new ethers.Contract(
gatewayEvmAddress,
GatewayABI.abi,
signer
);

const revertOptions = {
abortAddress: "0x0000000000000000000000000000000000000000", // not used
Expand Down
7 changes: 4 additions & 3 deletions packages/client/src/zetachainCall.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ export const zetachainCall = async function (
args: {
callOptions: any;
function: string;
gatewayZetaChain: string;
gatewayZetaChain?: string;
receiver: string;
revertOptions: revertOptions;
txOptions: txOptions;
Expand All @@ -43,9 +43,10 @@ export const zetachainCall = async function (
) {
const signer = this.signer;
const { utils } = ethers;

const gatewayZetaChainAddress =
args.gatewayZetaChain || this.getGatewayAddress();
const gateway = new ethers.Contract(
args.gatewayZetaChain,
gatewayZetaChainAddress,
GatewayABI.abi,
signer
);
Expand Down
6 changes: 4 additions & 2 deletions packages/client/src/zetachainWithdraw.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ export const zetachainWithdraw = async function (
this: ZetaChainClient,
args: {
amount: string;
gatewayZetaChain: string;
gatewayZetaChain?: string;
receiver: string;
revertOptions: revertOptions;
txOptions: txOptions;
Expand All @@ -38,8 +38,10 @@ export const zetachainWithdraw = async function (
const signer = this.signer;
const { utils } = ethers;

const gatewayZetaChainAddress =
args.gatewayZetaChain || this.getGatewayAddress();
const gateway = new ethers.Contract(
args.gatewayZetaChain,
gatewayZetaChainAddress,
GatewayABI.abi,
signer
);
Expand Down
6 changes: 4 additions & 2 deletions packages/client/src/zetachainWithdrawAndCall.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ export const zetachainWithdrawAndCall = async function (
amount: string;
callOptions: any;
function?: string;
gatewayZetaChain: string;
gatewayZetaChain?: string;
receiver: string;
revertOptions: revertOptions;
txOptions: txOptions;
Expand All @@ -46,8 +46,10 @@ export const zetachainWithdrawAndCall = async function (
const signer = this.signer;
const { utils } = ethers;

const gatewayZetaChainAddress =
args.gatewayZetaChain || this.getGatewayAddress();
const gateway = new ethers.Contract(
args.gatewayZetaChain,
gatewayZetaChainAddress,
Comment on lines +49 to +52
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Use resolved gateway address consistently throughout the function

The function uses args.gatewayZetaChain directly in the approval calls instead of the resolved gatewayZetaChainAddress. This could lead to undefined behavior when the gateway address is not provided.

  const approveGasAndWithdraw = await zrc20.approve(
-   args.gatewayZetaChain,
+   gatewayZetaChainAddress,
    value.add(gasFee),
    args.txOptions
  );
  // ... and similar changes in other approve calls

Also applies to: 100-120


⚠️ Potential issue

Add error handling for gateway address resolution

The fallback mechanism could fail if getGatewayAddress() returns undefined/null or an invalid address. Consider adding validation to ensure the gateway address is valid before proceeding.

  const gatewayZetaChainAddress =
    args.gatewayZetaChain || this.getGatewayAddress();
+ if (!utils.isAddress(gatewayZetaChainAddress)) {
+   throw new Error('Invalid or missing gateway address');
+ }
  const gateway = new ethers.Contract(
    gatewayZetaChainAddress,
    GatewayABI.abi,
    signer
  );
📝 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
const gatewayZetaChainAddress =
args.gatewayZetaChain || this.getGatewayAddress();
const gateway = new ethers.Contract(
args.gatewayZetaChain,
gatewayZetaChainAddress,
const gatewayZetaChainAddress =
args.gatewayZetaChain || this.getGatewayAddress();
if (!utils.isAddress(gatewayZetaChainAddress)) {
throw new Error('Invalid or missing gateway address');
}
const gateway = new ethers.Contract(
gatewayZetaChainAddress,

GatewayABI.abi,
signer
);
Expand Down
9 changes: 3 additions & 6 deletions packages/tasks/src/evmCall.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@ import { ZetaChainClient } from "../../client/src/";
export const evmCall = async (args: any, hre: HardhatRuntimeEnvironment) => {
try {
const [signer] = await hre.ethers.getSigners();
const client = new ZetaChainClient({ network: "testnet", signer });
const network = hre.network.name;
const client = new ZetaChainClient({ network, signer });
const tx = await client.evmCall({
gatewayEvm: args.gatewayEvm,
receiver: args.receiver,
Expand All @@ -32,11 +33,7 @@ export const evmCall = async (args: any, hre: HardhatRuntimeEnvironment) => {

task("evm-call", "Call a universal app", evmCall)
.addParam("receiver", "Receiver address on ZetaChain")
.addOptionalParam(
"gatewayEvm",
"contract address of gateway on EVM",
"0x9fE46736679d2D9a65F0992F2272dE9f3c7fa6e0"
)
.addOptionalParam("gatewayEvm", "contract address of gateway on EVM")
.addFlag("callOnRevert", "Whether to call on revert")
.addOptionalParam(
"revertAddress",
Expand Down
9 changes: 3 additions & 6 deletions packages/tasks/src/evmDeposit.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@ import { ZetaChainClient } from "../../client/src/";
export const evmDeposit = async (args: any, hre: HardhatRuntimeEnvironment) => {
try {
const [signer] = await hre.ethers.getSigners();
const client = new ZetaChainClient({ network: "testnet", signer });
const network = hre.network.name;
const client = new ZetaChainClient({ network, signer });
const tx = await client.evmDeposit({
amount: args.amount,
erc20: args.erc20,
Expand Down Expand Up @@ -34,11 +35,7 @@ export const evmDeposit = async (args: any, hre: HardhatRuntimeEnvironment) => {

task("evm-deposit", "Deposit tokens", evmDeposit)
.addParam("receiver", "Receiver address on ZetaChain")
.addOptionalParam(
"gatewayEvm",
"contract address of gateway on EVM",
"0x9fE46736679d2D9a65F0992F2272dE9f3c7fa6e0"
)
.addOptionalParam("gatewayEvm", "contract address of gateway on EVM")
.addFlag("callOnRevert", "Whether to call on revert")
.addOptionalParam(
"revertAddress",
Expand Down
9 changes: 3 additions & 6 deletions packages/tasks/src/evmDepositAndCall.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@ export const evmDepositAndCall = async (
) => {
try {
const [signer] = await hre.ethers.getSigners();
const client = new ZetaChainClient({ network: "testnet", signer });
const network = hre.network.name;
const client = new ZetaChainClient({ network, signer });
const tx = await client.evmDepositAndCall({
amount: args.amount,
erc20: args.erc20,
Expand Down Expand Up @@ -39,11 +40,7 @@ export const evmDepositAndCall = async (

task("evm-deposit-and-call", "Deposit tokens", evmDepositAndCall)
.addParam("receiver", "Receiver address on ZetaChain")
.addOptionalParam(
"gatewayEvm",
"contract address of gateway on EVM",
"0x9fE46736679d2D9a65F0992F2272dE9f3c7fa6e0"
)
.addOptionalParam("gatewayEvm", "contract address of gateway on EVM")
.addFlag("callOnRevert", "Whether to call on revert")
.addOptionalParam(
"revertAddress",
Expand Down
6 changes: 3 additions & 3 deletions packages/tasks/src/zetachainCall.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@ export const zetachainCall = async (

try {
const [signer] = await hre.ethers.getSigners();
const client = new ZetaChainClient({ network: "testnet", signer });
const network = hre.network.name;
const client = new ZetaChainClient({ network, signer });
const response = await client.zetachainCall({
callOptions,
function: args.function,
Expand Down Expand Up @@ -44,8 +45,7 @@ export const zetachainCall = async (
task("zetachain-call", "Call a contract on a connected chain", zetachainCall)
.addOptionalParam(
"gatewayZetaChain",
"contract address of gateway on ZetaChain",
"0xA51c1fc2f0D1a1b8494Ed1FE312d7C3a78Ed91C0"
"contract address of gateway on ZetaChain"
)
.addParam("zrc20", "The address of ZRC-20 to pay fees")
.addFlag("callOnRevert", "Whether to call on revert")
Expand Down
6 changes: 3 additions & 3 deletions packages/tasks/src/zetachainWithdraw.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@ export const zetachainWithdraw = async (
) => {
try {
const [signer] = await hre.ethers.getSigners();
const client = new ZetaChainClient({ network: "testnet", signer });
const network = hre.network.name;
const client = new ZetaChainClient({ network, signer });
const response = await client.zetachainWithdraw({
amount: args.amount,
gatewayZetaChain: args.gatewayZetaChain,
Expand Down Expand Up @@ -37,8 +38,7 @@ export const zetachainWithdraw = async (
task("zetachain-withdraw", "Withdraw tokens from ZetaChain", zetachainWithdraw)
.addOptionalParam(
"gatewayZetaChain",
"contract address of gateway on ZetaChain",
"0xA51c1fc2f0D1a1b8494Ed1FE312d7C3a78Ed91C0"
"contract address of gateway on ZetaChain"
)
.addOptionalParam("zrc20", "The address of the ZRC20 token")
.addFlag("callOnRevert", "Whether to call on revert")
Expand Down
6 changes: 3 additions & 3 deletions packages/tasks/src/zetachainWithdrawAndCall.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@ export const zetachainWithdrawAndCall = async (
};

const [signer] = await hre.ethers.getSigners();
const client = new ZetaChainClient({ network: "testnet", signer });
const network = hre.network.name;
const client = new ZetaChainClient({ network, signer });
const response = await client.zetachainWithdrawAndCall({
amount: args.amount,
callOptions,
Expand Down Expand Up @@ -50,8 +51,7 @@ task(
)
.addOptionalParam(
"gatewayZetaChain",
"contract address of gateway on ZetaChain",
"0xA51c1fc2f0D1a1b8494Ed1FE312d7C3a78Ed91C0"
"contract address of gateway on ZetaChain"
)
.addOptionalParam("zrc20", "The address of the ZRC20 token")
.addFlag("callOnRevert", "Whether to call on revert")
Expand Down
Loading
Loading