Skip to content

Commit

Permalink
feat: support zkSyncEra and zkSyndTestnet chains (#1259)
Browse files Browse the repository at this point in the history
  • Loading branch information
KolevDarko committed Nov 22, 2023
1 parent 0ccd5cc commit 624a636
Show file tree
Hide file tree
Showing 21 changed files with 738 additions and 24 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -40,3 +40,5 @@ tsconfig.build.tsbuildinfo
/packages/smart-contracts/cache/
/packages/smart-contracts/types/
/packages/smart-contracts/src/types/
/packages/smart-contracts/build-zk/
/packages/smart-contracts/cache-zk/
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export const chainId = 280;
1 change: 1 addition & 0 deletions packages/currency/src/chains/evm/data/zksync-era.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export const chainId = 324;
4 changes: 4 additions & 0 deletions packages/currency/src/chains/evm/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ import * as RoninDefinition from './data/ronin';
import * as SokolDefinition from './data/sokol';
import * as TombchainDefinition from './data/tombchain';
import * as XDaiDefinition from './data/xdai';
import * as ZkSyncEraTestnetDefinition from './data/zksync-era-testnet';
import * as ZkSyncEraDefinition from './data/zksync-era';

export type EvmChain = Chain & {
chainId: number;
Expand Down Expand Up @@ -55,4 +57,6 @@ export const chains: Record<CurrencyTypes.EvmChainName, EvmChain> = {
sokol: SokolDefinition,
tombchain: TombchainDefinition,
xdai: XDaiDefinition,
zkSyncEraTestnet: ZkSyncEraTestnetDefinition,
zkSyncEra: ZkSyncEraDefinition,
};
12 changes: 12 additions & 0 deletions packages/currency/src/native.ts
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,18 @@ export const nativeCurrencies: Record<RequestLogicTypes.CURRENCY.ETH, NativeEthC
name: 'Core',
network: 'core',
},
{
symbol: 'ETH-zksync',
decimals: 18,
name: 'Ether',
network: 'zkSyncEra',
},
{
symbol: 'ETH-zksync-testnet',
decimals: 18,
name: 'Ether',
network: 'zkSyncEraTestnet',
},
],
[RequestLogicTypes.CURRENCY.BTC]: [
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ const networks: Record<string, ethers.providers.Network> = {
mantle: { chainId: 5000, name: 'mantle' },
'mantle-testnet': { chainId: 5001, name: 'mantle-testnet' },
core: { chainId: 1116, name: 'core' },
zkSyncEraTestnet: { chainId: 280, name: 'zkSyncEraTestnet' },
zkSyncEra: { chainId: 324, name: 'zkSyncEra' },
};

/**
Expand Down Expand Up @@ -66,6 +68,10 @@ export class MultichainExplorerApiProvider extends ethers.providers.EtherscanPro
return 'https://explorer.testnet.mantle.xyz/api';
case 'core':
return 'https://openapi.coredao.org/';
case 'zkSyncEraTestnet':
return 'https://goerli.explorer.zksync.io/';
case 'zkSyncEra':
return 'https://explorer.zksync.io/';
default:
return super.getBaseUrl();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ describe('erc777-stream', () => {
it.each([
{ network: 'goerli' },
{ network: 'matic' },
{ network: 'xdai' },
// { network: 'xdai' },
{ network: 'optimism' },
{ network: 'avalanche' },
{ network: 'arbitrum-one' },
Expand Down
34 changes: 34 additions & 0 deletions packages/smart-contracts/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -215,6 +215,40 @@ yarn hardhat deploy-live-payments --network private --force
yarn hardhat deploy-live-payments --network private --force --dry-run
```

## ZkSyncEra support

### Compilation

To compile the contracts with the zkSync compiler, we use the same compile task but with the zkSync network specified.

```bash
yarn hardhat compile --network zkSyncEra
```

The compiled results go in separate directories build-zk and cache-zk.
In order for the zkSync compiler to be activated, this networks has the `zksync: true` flag in the hardhat.config.ts file.

### Deployment

We have deployment scripts in the /deploy directory for contracts ERC20FeeProxy, EthereumFeeProxy and BatchPayments.
These are different deploy scripts than regular EVM ones because they use the zkSync deploy package.

We deploy with the following commands:

First deploy the Proxy contracts:

```bash
yarn hardhat deploy-zksync --script deploy-zk-proxy-contracts --network zkSyncEra
```

Then deploy the Batch contract:

```bash
yarn hardhat deploy-zksync --script deploy-zk-batch-contracts --network zkSyncEra
```

We don't have deploy scripts for our Conversion proxy because there is no Chainlink feed yet on this chain.

## Administrate the contracts

The contracts to be updated are listed in the array `create2ContractDeploymentList` in [Utils](scripts-create2/utils.ts).
Expand Down
23 changes: 23 additions & 0 deletions packages/smart-contracts/deploy/deploy-zk-batch-contracts.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { erc20FeeProxyArtifact, ethereumFeeProxyArtifact } from '../src/lib';
import { deployContract } from './utils-zk';
import * as hre from 'hardhat';
import { CurrencyTypes } from '@requestnetwork/types';

/**
* Deploys Batch payments contracts to zkSync network.
* This script is supposed to be run with the deploy-zksync plugin
* check zkSync section in smart-contracts/README file
*/
export default async function () {
const [deployer] = await hre.ethers.getSigners();
const constructorArguments = [
erc20FeeProxyArtifact.getAddress(hre.network.name as CurrencyTypes.EvmChainName),
ethereumFeeProxyArtifact.getAddress(hre.network.name as CurrencyTypes.EvmChainName),
hre.ethers.constants.AddressZero,
hre.ethers.constants.AddressZero,
hre.ethers.constants.AddressZero,
deployer.address,
];
console.log(`Deploying BatchConversionPayments to zkSync ...`);
await deployContract('BatchConversionPayments', constructorArguments);
}
16 changes: 16 additions & 0 deletions packages/smart-contracts/deploy/deploy-zk-proxy-contracts.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { deployContract } from './utils-zk';

/**
* Deploys Proxy contracts to zkSync network.
* This script is supposed to be run with the deploy-zksync plugin
* check zkSync section in smart-contracts/README file
*/
export default async function () {
const deployList: string[] = ['ERC20FeeProxy', 'EthereumFeeProxy'];

for (let index = 0; index < deployList.length; index++) {
const contractName = deployList[index];
console.log(`Deploying ${contractName} to zkSync ...`);
await deployContract(contractName, []);
}
}
172 changes: 172 additions & 0 deletions packages/smart-contracts/deploy/utils-zk.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@
import { Provider, Wallet, Contract } from 'zksync-web3';
import * as hre from 'hardhat';
import { Deployer } from '@matterlabs/hardhat-zksync-deploy';
import { formatEther } from 'ethers/lib/utils';
import { BigNumberish } from 'ethers';

import { config } from 'dotenv';
import { networkRpcs } from '@requestnetwork/utils/dist/providers';

config();

const accounts = process.env.DEPLOYMENT_PRIVATE_KEY
? [process.env.DEPLOYMENT_PRIVATE_KEY]
: process.env.DEPLOYER_MASTER_KEY
? [process.env.DEPLOYER_MASTER_KEY]
: process.env.ADMIN_PRIVATE_KEY
? [process.env.ADMIN_PRIVATE_KEY]
: undefined;

const WALLET_PRIVATE_KEY = (accounts || [])[0];

export const getProvider = () => {
const rpcUrl = networkRpcs[hre.network.name];
if (!rpcUrl)
throw `⛔️ RPC URL wasn't found in "${hre.network.name}"! Please add a "url" field to the network config in hardhat.config.ts`;

// Initialize zkSync Provider
const provider = new Provider(rpcUrl);

return provider;
};

export const getWallet = (privateKey?: string): Wallet => {
if (!privateKey) {
// Get wallet private key from .env file
if (!WALLET_PRIVATE_KEY) throw "⛔️ Wallet private key wasn't found in .env file!";
}

const provider = getProvider();

// Initialize zkSync Wallet
const wallet = new Wallet(privateKey ?? WALLET_PRIVATE_KEY!, provider);

return wallet;
};

export const verifyEnoughBalance = async (wallet: Wallet, amount: BigNumberish) => {
// Check if the wallet has enough balance
const balance = await wallet.getBalance();
if (balance.lt(amount))
throw `⛔️ Wallet balance is too low! Required ${formatEther(amount)} ETH, but current ${
wallet.address
} balance is ${formatEther(balance)} ETH`;
};

/**
* @param {string} data.contract The contract's path and name. E.g., "contracts/Greeter.sol:Greeter"
*/
export const verifyContract = async (data: {
address: string;
contract: string;
constructorArguments: string | [];
bytecode: string;
}) => {
const verificationRequestId: number = await hre.run('verify:verify', {
...data,
noCompile: true,
});
return verificationRequestId;
};

type DeployContractOptions = {
/**
* If true, the deployment process will not print any logs
*/
silent?: boolean;
/**
* If true, the contract will not be verified on Block Explorer
*/
noVerify?: boolean;
/**
* If specified, the contract will be deployed using this wallet
*/
wallet?: Wallet;
};

export const verifyContractByName = async (
contractArtifactName: string,
contractAddress: string,
) => {
const wallet = getWallet();
const deployer = new Deployer(hre, wallet);

const artifact = await deployer.loadArtifact(contractArtifactName).catch((error) => {
if (error?.message?.includes(`Artifact for contract "${contractArtifactName}" not found.`)) {
console.error(error.message);
throw `⛔️ Please make sure you have compiled your contracts or specified the correct contract name!`;
} else {
throw error;
}
});

const fullContractSource = `${artifact.sourceName}:${artifact.contractName}`;

// Display contract deployment info
console.log(`\n"${artifact.contractName}" was successfully deployed:`);
console.log(` - Contract address: ${contractAddress}`);
console.log(` - Contract source: ${fullContractSource}`);

console.log(`Requesting contract verification...`);
await verifyContract({
address: contractAddress,
contract: fullContractSource,
constructorArguments: [],
bytecode: artifact.bytecode,
});
};

export const deployContract = async (
contractArtifactName: string,
constructorArguments?: any[],
options?: DeployContractOptions,
): Promise<Contract> => {
const log = (message: string) => {
if (!options?.silent) console.log(message);
};

log(`\nStarting deployment process of "${contractArtifactName}"...`);

const wallet = options?.wallet ?? getWallet();
const deployer = new Deployer(hre, wallet);

const artifact = await deployer.loadArtifact(contractArtifactName).catch((error) => {
if (error?.message?.includes(`Artifact for contract "${contractArtifactName}" not found.`)) {
console.error(error.message);
throw `⛔️ Please make sure you have compiled your contracts or specified the correct contract name!`;
} else {
throw error;
}
});

// Estimate contract deployment fee
const deploymentFee = await deployer.estimateDeployFee(artifact, constructorArguments || []);
log(`Estimated deployment cost: ${formatEther(deploymentFee)} ETH`);

// Check if the wallet has enough balance
await verifyEnoughBalance(wallet, deploymentFee);

// Deploy the contract to zkSync
const contract = await deployer.deploy(artifact, constructorArguments);

const constructorArgs = contract.interface.encodeDeploy(constructorArguments);
const fullContractSource = `${artifact.sourceName}:${artifact.contractName}`;

// Display contract deployment info
log(`\n"${artifact.contractName}" was successfully deployed:`);
log(` - Contract address: ${contract.address}`);
log(` - Contract source: ${fullContractSource}`);
log(` - Encoded constructor arguments: ${constructorArgs}\n`);

if (!options?.noVerify && hre.network.config.verifyURL) {
log(`Requesting contract verification...`);
await verifyContract({
address: contract.address,
contract: fullContractSource,
constructorArguments: constructorArgs,
bytecode: artifact.bytecode,
});
}

return contract;
};
24 changes: 23 additions & 1 deletion packages/smart-contracts/hardhat.config.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
import '@typechain/hardhat';
import '@nomiclabs/hardhat-waffle';
import '@nomicfoundation/hardhat-verify';
import '@nomiclabs/hardhat-ethers';

import '@matterlabs/hardhat-zksync-node';
import '@matterlabs/hardhat-zksync-deploy';
import '@matterlabs/hardhat-zksync-solc';
import '@matterlabs/hardhat-zksync-verify';

import { subtask, task } from 'hardhat/config';
import { config } from 'dotenv';
import deployAllContracts from './scripts/test-deploy-all';
Expand Down Expand Up @@ -161,6 +166,23 @@ export default {
chainId: 1116,
accounts,
},
zkSyncEraTestnet: {
url: url('zkSyncEraTestnet'),
ethNetwork: 'goerli',
zksync: true,
verifyURL: 'https://zksync2-testnet-explorer.zksync.dev/contract_verification',
accounts,
},
zkSyncEra: {
url: url('zkSyncEra'),
ethNetwork: 'mainnet',
zksync: true,
verifyURL: 'https://zksync2-mainnet-explorer.zksync.io/contract_verification',
accounts,
},
},
zksolc: {
version: '1.3.16',
},
etherscan: {
apiKey: {
Expand Down
8 changes: 7 additions & 1 deletion packages/smart-contracts/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,11 @@
"tslib": "2.5.0"
},
"devDependencies": {
"@matterlabs/hardhat-zksync-deploy": "0.6.5",
"@matterlabs/hardhat-zksync-node": "0.0.1-beta.6",
"@matterlabs/hardhat-zksync-solc": "0.4.2",
"@matterlabs/hardhat-zksync-verify": "0.2.1",
"@matterlabs/zksync-contracts": "0.6.1",
"@nomicfoundation/hardhat-verify": "2.0.0",
"@nomiclabs/hardhat-ethers": "2.0.2",
"@nomiclabs/hardhat-waffle": "2.0.1",
Expand All @@ -79,6 +84,7 @@
"shx": "0.3.2",
"solhint": "3.3.6",
"typechain": "5.1.1",
"web3": "1.7.3"
"web3": "1.7.3",
"zksync-web3": "0.14.3"
}
}
Loading

0 comments on commit 624a636

Please sign in to comment.