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: support zkSyncEra and zkSyndTestnet chains #1259

Merged
merged 17 commits into from
Nov 22, 2023
Merged
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: 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',
Copy link
Contributor

Choose a reason for hiding this comment

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

Any reason why we don't keep the usual format (zk-sync-era-testnet) ?

},
],
[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' },
Copy link
Contributor

Choose a reason for hiding this comment

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

?

{ 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
Copy link
Contributor

Choose a reason for hiding this comment

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

Seems weird, no? Don't we just have 1 PK for deployment/

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I just copied the logic from hardhat.config.ts file, we get the accounts the same way

Copy link
Contributor

Choose a reason for hiding this comment

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

I think it can be simplified with the only key you need for deployment.

For example the DEPLOYER_MASTER_KEY is never used in this context.
I think you can also remove DEPLOYMENT_PRIVATE_KEY, and only use ADMIN_PRIVATE_KEY. Both of them exists in the original hardhat config, as we needed a distinction when we deployed the contract with the scripts not using CREATE2.

? [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';
benjlevesque marked this conversation as resolved.
Show resolved Hide resolved
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