Skip to content

Commit

Permalink
enhance plugin contracts init and add a test
Browse files Browse the repository at this point in the history
  • Loading branch information
Muhammad-Altabba committed Jul 8, 2024
1 parent 058cfc2 commit 043a132
Show file tree
Hide file tree
Showing 6 changed files with 206 additions and 128 deletions.
283 changes: 158 additions & 125 deletions src/plugin.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import type { Web3 } from 'web3';
import type { Web3Context, Web3ContextInitOptions, Web3RequestManager } from 'web3-core';
import type * as web3Types from 'web3-types';
import type { Address } from 'web3-types';
Expand All @@ -21,196 +20,230 @@ import { EIP712Transaction } from './Eip712';
import { ZKSyncWallet } from './zksync-wallet';
import { Web3ZkSyncL2 } from './web3zksync-l2';
import { Web3ZkSyncL1 } from './web3zksync-l1';
import { ContractsAddresses } from './types';

interface ZKSyncWalletConstructor {
new (privateKey: string): ZKSyncWallet;
}

export type ZKSyncContractsCollection = {
Generic: {
/**
* The web3.js Contract instance for the `IERC20` interface, which is utilized for interacting with ERC20 tokens.
*/
IERC20Contract: Contract<typeof IERC20ABI>;
/**
* The web3.js Contract instance for the `IERC1271` interface, which is utilized for signature validation by contracts.
*/
IERC1271Contract: Contract<typeof IERC1271ABI>;
};
L1: {
/**
* The web3.js Contract instance for the `ZkSync` interface.
*/
ZkSyncMainContract: Contract<typeof IZkSyncABI>;
/**
* The ABI of the `Bridgehub` interface.
*/
BridgehubContract: Contract<typeof IBridgehubABI>;
/**
* The web3.js Contract instance for the `IL1Bridge` interface, which is utilized for transferring ERC20 tokens from L1 to L2.
*/
L1BridgeContract: Contract<typeof IL1BridgeABI>;
};
L2: {
/**
* The web3.js Contract instance for the `IContractDeployer` interface, which is utilized for deploying smart contracts.
*/
ContractDeployerContract: Contract<typeof IContractDeployerABI>;
/**
* The web3.js Contract instance for the `IL1Messenger` interface, which is utilized for sending messages from the L2 to L1.
*/
L1MessengerContract: Contract<typeof IL1MessengerABI>;
/**
* The web3.js Contract instance for the `IL2Bridge` interface, which is utilized for transferring ERC20 tokens from L2 to L1.
*/
L2BridgeContract: Contract<typeof IL2BridgeABI>;

/**
* The web3.js Contract instance for the `INonceHolder` interface, which is utilized for managing deployment nonces.
*/
NonceHolderContract: Contract<typeof INonceHolderABI>;
};
};

export class ZkSyncPlugin extends Web3PluginBase {
private web3: Web3;
private providerL2: Web3ZkSyncL2;
public L1: Web3ZkSyncL1 | undefined;
public L2: Web3ZkSyncL2;
public pluginNamespace = 'zkSync';
public erc20BridgeL1: string;
public erc20BridgeL2: string;
public wethBridgeL1: string;
public wethBridgeL2: string;
public _rpc?: RpcMethods;
public _l2BridgeContracts: Record<Address, Contract<typeof IL2BridgeABI>>;
public _erc20Contracts: Record<Address, Contract<typeof IERC20ABI>>;
public Contracts: {
Generic: {
/**
* The web3.js Contract instance for the `IERC20` interface, which is utilized for interacting with ERC20 tokens.
*/
IERC20Contract: Contract<typeof IERC20ABI>;
/**
* The web3.js Contract instance for the `IERC1271` interface, which is utilized for signature validation by contracts.
*/
IERC1271Contract: Contract<typeof IERC1271ABI>;
};
L1: {
/**
* The web3.js Contract instance for the `ZkSync` interface.
*/
ZkSyncMainContract: Contract<typeof IZkSyncABI>;
/**
* The ABI of the `Bridgehub` interface.
*/
BridgehubContract: Contract<typeof IBridgehubABI>;
/**
* The web3.js Contract instance for the `IL1Bridge` interface, which is utilized for transferring ERC20 tokens from L1 to L2.
*/
L1BridgeContract: Contract<typeof IL1BridgeABI>;
};
L2: {
/**
* The web3.js Contract instance for the `IContractDeployer` interface, which is utilized for deploying smart contracts.
*/
ContractDeployerContract: Contract<typeof IContractDeployerABI>;
/**
* The web3.js Contract instance for the `IL1Messenger` interface, which is utilized for sending messages from the L2 to L1.
*/
L1MessengerContract: Contract<typeof IL1MessengerABI>;
/**
* The web3.js Contract instance for the `IL2Bridge` interface, which is utilized for transferring ERC20 tokens from L2 to L1.
*/
L2BridgeContract: Contract<typeof IL2BridgeABI>;

/**
* The web3.js Contract instance for the `INonceHolder` interface, which is utilized for managing deployment nonces.
*/
NonceHolderContract: Contract<typeof INonceHolderABI>;
};
};
ZKSyncWallet: ZKSyncWalletConstructor;

private contracts: ZKSyncContractsCollection | undefined;
public get Contracts(): Promise<ZKSyncContractsCollection> {
if (this.contracts) {
return Promise.resolve(this.contracts);
}
return this.initContracts();
}

contractsAddresses: Promise<ContractsAddresses>;
public get ContractsAddresses(): Promise<ContractsAddresses> {
if (this.contractsAddresses) {
return Promise.resolve(this.contractsAddresses);
}
return this.initContractsAddresses();
}

// the wallet type in this class is different from the parent class. So, they should have different names.
ZkWallet: ZKSyncWalletConstructor;

/**
* Constructor
* @param providerOrContextL2 - The provider or context for the L2 network
*/
constructor(
providerOrContextL2: web3Types.SupportedProviders<any> | Web3ContextInitOptions | string,
) {
super();
if (providerOrContextL2 instanceof Web3ZkSyncL2) {
this.providerL2 = providerOrContextL2;
this.L2 = providerOrContextL2;
} else {
this.providerL2 = new Web3ZkSyncL2(providerOrContextL2);
this.L2 = new Web3ZkSyncL2(providerOrContextL2);
}
// @ts-ignore-next-line
TransactionFactory.registerTransactionType(constants.EIP712_TX_TYPE, EIP712Transaction);

// TODO: Chose between this pattern and using this.Contracts...
this.erc20BridgeL1 = '';
this.erc20BridgeL2 = '';
this.wethBridgeL1 = '';
this.wethBridgeL2 = '';
this._l2BridgeContracts = {};
this._erc20Contracts = {};

// TODO: Rethink the way of initializing contracts, maybe it should be lazy loaded?!
this.Contracts = {
this.contractsAddresses = this.initContractsAddresses();

const self = this;
class ZKSyncWalletWithFullContext extends ZKSyncWallet {
constructor(privateKey: string) {
super(privateKey, self.L2, self.L1);
}
}

this.ZkWallet = ZKSyncWalletWithFullContext;
this.initWallet();
}

public async initContracts() {
if (!this.L1 || !this.L2) {
throw new Error(
'Contracts cannot be initialized because a Web3 instance is not yet linked to ZkSync plugin',
);
}

const {
mainContract,
bridgehubContractAddress,
// l1Erc20DefaultBridge,
// l2Erc20DefaultBridge,
// l1WethBridge,
// l2WethBridge,
l1SharedDefaultBridge,
l2SharedDefaultBridge,
} = await this.contractsAddresses;

const contractsCollection: ZKSyncContractsCollection = {
Generic: {
IERC20Contract: new Contract(IERC20ABI),
IERC1271Contract: new Contract(IERC1271ABI),
},
L1: {
ZkSyncMainContract: new Contract(IZkSyncABI),
BridgehubContract: new Contract(IBridgehubABI),
L1BridgeContract: new Contract(IL1BridgeABI),
ZkSyncMainContract: new Contract(IZkSyncABI, mainContract, this.L1),
BridgehubContract: new Contract(IBridgehubABI, bridgehubContractAddress, this.L1),
L1BridgeContract: new Contract(IL1BridgeABI, l1SharedDefaultBridge, this.L1),
},
L2: {
ContractDeployerContract: new Contract(
IContractDeployerABI,
constants.CONTRACT_DEPLOYER_ADDRESS,
this.providerL2,
),
L1MessengerContract: new Contract(
IL1MessengerABI,
constants.L1_MESSENGER_ADDRESS,
this.providerL2,
this.L2,
),
NonceHolderContract: new Contract(
INonceHolderABI,
constants.NONCE_HOLDER_ADDRESS,
this.providerL2,
),
L2BridgeContract: new Contract(IL2BridgeABI, this.providerL2),
L1MessengerContract: new Contract(IL1MessengerABI, constants.L1_MESSENGER_ADDRESS, this.L2),
NonceHolderContract: new Contract(INonceHolderABI, constants.NONCE_HOLDER_ADDRESS, this.L2),
L2BridgeContract: new Contract(IL2BridgeABI, l2SharedDefaultBridge, this.L2),
},
};

this.ZKSyncWallet = (() => {
throw new Error('Web3 instance is not yet linked to ZkSync plugin');
}) as unknown as ZKSyncWalletConstructor;

this.web3 = (() => {
throw new Error('Web3 instance is not yet linked to ZkSync plugin');
}) as unknown as Web3;

this.tryFillContractsAddresses();
this.contracts = contractsCollection;
return contractsCollection;
}

/**
* Try to fill the contract addresses
* @returns True if the contract addresses were successfully filled, false otherwise
*/
public async tryFillContractsAddresses() {
try {
const [mainContract, bridgehubContractAddress, bridgeContracts] = await Promise.all([
this.rpc.getMainContract(),
this.rpc.getBridgehubContractAddress(),
this.rpc.getBridgeContracts(),
]);

// mainContract looks like in the ZK Era mainnet: '0x32400084C286CF3E17e7B677ea9583e60a000324'
this.Contracts.L1.ZkSyncMainContract.options.address = mainContract;
// mainContract looks like in the ZK Era mainnet: '0x303a465B659cBB0ab36eE643eA362c509EEb5213'
this.Contracts.L1.BridgehubContract.options.address = bridgehubContractAddress;

// bridgeContracts object looks like in the ZK Era mainnet:
// {
// l1Erc20DefaultBridge: '0x57891966931eb4bb6fb81430e6ce0a03aabde063',
// l1WethBridge: '0x0000000000000000000000000000000000000000',
// l2Erc20DefaultBridge: '0x11f943b2c77b743ab90f4a0ae7d5a4e7fca3e102',
// l2WethBridge: '0x0000000000000000000000000000000000000000'
// }
this.Contracts.L1.L1BridgeContract.options.address = bridgeContracts.l1Erc20DefaultBridge;
this.Contracts.L2.L2BridgeContract.options.address = bridgeContracts.l2Erc20DefaultBridge;

return true;
} catch (e) {
return false;
}
public async initContractsAddresses() {
const [mainContract, bridgehubContractAddress, bridgeContracts] = await Promise.all([
this.rpc.getMainContract(),
this.rpc.getBridgehubContractAddress(),
this.rpc.getBridgeContracts(),
]);
this.contractsAddresses = Promise.resolve({
mainContract,
bridgehubContractAddress,
...bridgeContracts,
});

return this.contractsAddresses;
}

public link(parentContext: Web3Context): void {
super.link(parentContext);

this.web3 = parentContext as Web3;
this.L1 = new Web3ZkSyncL1(parentContext);

// Pass the parentContext to the corresponding L1 contracts
this.Contracts.L1.ZkSyncMainContract.link<any>(parentContext);
this.Contracts.L1.BridgehubContract.link<any>(parentContext);
this.Contracts.L1.L1BridgeContract.link<any>(parentContext);
this.initContracts();

this.initWallet();
}

private initWallet() {
const self = this;
class ZKSyncWalletWithFullContext extends ZKSyncWallet {
constructor(privateKey: string) {
super(privateKey, self.providerL2, new Web3ZkSyncL1(self.web3.provider));
super(privateKey, self.L2, self.L1);
}
}

this.ZKSyncWallet = ZKSyncWalletWithFullContext;
this.ZkWallet = ZKSyncWalletWithFullContext;
}

/**
* Get RPC methods instance
*/
get rpc(): RpcMethods {
if (!this._rpc) {
this._rpc = new RpcMethods(
this.providerL2.requestManager as unknown as Web3RequestManager<unknown>,
);
this._rpc = new RpcMethods(this.L2.requestManager as unknown as Web3RequestManager<unknown>);
}
return this._rpc;
}

/**
* Update the providers
* @param contextL1 - The context for the L1 network
* @param contextL2 - The context for the L2 network
*
* @remarks This method is used to update the providers for the L1 and L2 networks.
* It is very important to call it if one of the providers is changed to a different network.
* For example, if the L1 or L2 providers were changed from testnet to mainnet, this method should be called.
*/
public updateProviders(
contextL1: Web3ZkSyncL1 | web3Types.SupportedProviders<any> | Web3ContextInitOptions | string,
contextL2: Web3ZkSyncL2 | web3Types.SupportedProviders<any> | Web3ContextInitOptions | string,
) {
this.L1 = contextL1 instanceof Web3ZkSyncL1 ? contextL1 : new Web3ZkSyncL1(contextL1);
this.L2 = contextL2 instanceof Web3ZkSyncL2 ? contextL2 : new Web3ZkSyncL2(contextL2);
this.initContractsAddresses();
this.initContracts();
}

/**
* Get L2 bridge contract instance
* @param address - Contract address
Expand Down Expand Up @@ -243,7 +276,7 @@ export class ZkSyncPlugin extends Web3PluginBase {
if (token == constants.ETH_ADDRESS) {
return constants.ETH_ADDRESS;
} else {
const bridgeAddresses = await this.providerL2.getDefaultBridgeAddresses();
const bridgeAddresses = await this.L2.getDefaultBridgeAddresses();
if (bridgeAddresses.wethL2 !== constants.ZERO_ADDRESS) {
const l2Bridge = this.getL2BridgeContract(bridgeAddresses.wethL2);
try {
Expand All @@ -269,7 +302,7 @@ export class ZkSyncPlugin extends Web3PluginBase {
if (token == constants.ETH_ADDRESS) {
return constants.ETH_ADDRESS;
} else {
const bridgeAddresses = await this.providerL2.getDefaultBridgeAddresses();
const bridgeAddresses = await this.L2.getDefaultBridgeAddresses();
if (bridgeAddresses.wethL2 !== constants.ZERO_ADDRESS) {
const l2Bridge = this.getL2BridgeContract(bridgeAddresses.wethL2);
try {
Expand Down
5 changes: 5 additions & 0 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -813,6 +813,11 @@ export interface BridgeAddresses {
l2SharedDefaultBridge: Address;
}

export interface ContractsAddresses extends BridgeAddresses {
mainContract: string;
bridgehubContractAddress: string;
}

export interface L2ToL1Proof {
proof: HexString[];
id: Numbers;
Expand Down
2 changes: 2 additions & 0 deletions test/fixtures.ts
Original file line number Diff line number Diff line change
Expand Up @@ -119,8 +119,10 @@ export const getBlockDetailsData = {
export const getBridgeContractsData = {
output: {
l1Erc20DefaultBridge: '0x2ae09702f77a4940621572fbcdae2382d44a2cba',
l1SharedDefaultBridge: '0x3e8b2fe58675126ed30d0d12dea2a9bda72d18ae',
l1WethBridge: '0x0000000000000000000000000000000000000000',
l2Erc20DefaultBridge: '0x681a1afdc2e06776816386500d2d461a6c96cb45',
l2SharedDefaultBridge: '0x681a1afdc2e06776816386500d2d461a6c96cb45',
l2WethBridge: '0x0000000000000000000000000000000000000000',
},
};
Expand Down
Loading

0 comments on commit 043a132

Please sign in to comment.