UNPKG

hardhat-scilla-plugin

Version:
307 lines (265 loc) 8.12 kB
// This is necessary so that tsc can resolve some of the indirect types for // sc_call, otherwise it errors out - richard@zilliqa.com 2023-03-09 import { Account, Transaction } from "@zilliqa-js/account"; import { Contract, Init } from "@zilliqa-js/contract"; import { BN, bytes, Long, units } from "@zilliqa-js/util"; import { Zilliqa } from "@zilliqa-js/zilliqa"; import fs from "fs"; import { HardhatPluginError } from "hardhat/plugins"; import { HardhatRuntimeEnvironment } from "hardhat/types"; import * as ScillaContractProxy from "../parser/ScillaContractProxy"; import { ContractInfo } from "../parser/ScillaContractsInfoUpdater"; import { Field, Fields, isNumeric } from "../parser/ScillaParser"; export interface Value { vname: string; type: string; value: string; } export interface Setup { zilliqa: Zilliqa; readonly attempts: number; readonly timeout: number; readonly version: number; readonly gasPrice: BN; readonly gasLimit: Long; accounts: Account[]; } export let setup: Setup | null = null; // The optional params are listed in popularity order. export const initZilliqa = ( zilliqaNetworkUrl: string, chainId: number, privateKeys: string[], attempts: number = 10, timeoutMs: number = 1000, gasPriceQa: number = 2000, gasLimit: number = 50000 ): Setup => { const zilliqaObject = new Zilliqa(zilliqaNetworkUrl); const accounts: Account[] = []; privateKeys.forEach((pk) => { zilliqaObject.wallet.addByPrivateKey(pk); accounts.push(new Account(pk)); }); setup = { zilliqa: zilliqaObject, version: bytes.pack(chainId, 1), gasPrice: units.toQa(gasPriceQa.toString(), units.Units.Li), gasLimit: Long.fromNumber(gasLimit), attempts, timeout: timeoutMs, accounts, }; return setup; }; function read(f: string) { const t = fs.readFileSync(f, "utf8"); return t; } /// Allows you to change setup parameters. Available params: /// gasPrice, gasLimit, attempts, timeout. export function updateSetup(args: any) { if (setup === null) { throw new HardhatPluginError( "hardhat-scilla-plugin", "Please call the initZilliqa function." ); } const overrides: any = {}; if (args.gasPrice) { overrides.gasPrice = units.toQa(args.gasPrice.toString(), units.Units.Li); } if (args.gasLimit) { overrides.gasLimit = Long.fromNumber(args.gasLimit); } if (args.timeout) { overrides.timeout = args.timeout; } if (args.attempts) { overrides.attempts = args.attempts; } const newSetup: Setup = { ...setup, ...overrides }; setup = newSetup; } export function setAccount(account: number | Account) { if (setup === null) { throw new HardhatPluginError( "hardhat-scilla-plugin", "Please call initZilliqa function." ); } if (account instanceof Account) { setup.zilliqa.wallet.defaultAccount = account; } else { setup.zilliqa.wallet.defaultAccount = setup.accounts[account]; } } export type ContractFunction<T = any> = (...args: any[]) => Promise<T>; declare module "@zilliqa-js/contract" { interface Contract { executer?: Account; [key: string]: ContractFunction | any; connect: (signer: Account) => Contract; } } export type ScillaContract = Contract; export interface UserDefinedLibrary { name: string; address: string; } export type OptionalUserDefinedLibraryList = UserDefinedLibrary[] | null; export async function deploy( hre: HardhatRuntimeEnvironment, contractName: string, compress: boolean, userDefinedLibraries: OptionalUserDefinedLibraryList, ...args: any[] ): Promise<ScillaContract> { const contractInfo: ContractInfo = hre.scillaContracts[contractName]; if (contractInfo === undefined) { throw new Error(`Scilla contract ${contractName} doesn't exist.`); } let txParamsForContractDeployment = {}; const constructorParamsLength = contractInfo.parsedContract.constructorParams?.length || 0; if (args.length === constructorParamsLength + 1) { // The last param is Tx info such as amount, nonce, gasPrice txParamsForContractDeployment = args.pop(); } const init: Init = fillInit( contractName, userDefinedLibraries, contractInfo.parsedContract.constructorParams, ...args ); const [tx, sc] = await deployFromFile( contractInfo.path, init, compress, txParamsForContractDeployment ); sc.deployed_by = tx; ScillaContractProxy.injectProxies(setup!, contractInfo, sc); return sc; } export const deployLibrary = async ( hre: HardhatRuntimeEnvironment, libraryName: string, compress: boolean ): Promise<ScillaContract> => { const contractInfo: ContractInfo = hre.scillaContracts[libraryName]; if (contractInfo === undefined) { throw new Error(`Scilla contract ${libraryName} doesn't exist.`); } const init: Init = fillLibraryInit(); const [tx, sc] = await deployFromFile(contractInfo.path, init, compress, {}); // FIXME: In #45 sc.deployed_by = tx; return sc; }; const fillLibraryInit = (): Init => { const init = [ { vname: "_scilla_version", type: "Uint32", value: "0", }, { vname: "_library", type: "Bool", value: { constructor: "True", argtypes: [], arguments: [], }, }, ]; return init; }; const fillInit = ( contractName: string, userDefinedLibraries: OptionalUserDefinedLibraryList, contractParams: Fields | null, ...userSpecifiedArgs: any[] ): Init => { const init: Init = [{ vname: "_scilla_version", type: "Uint32", value: "0" }]; if (userDefinedLibraries) { // Underlying zilliqa-js doesn't support push such an object to Init (init as any).push({ vname: "_extlibs", type: "List(Pair String ByStr20)", value: userDefinedLibraries.map((lib) => ({ constructor: "Pair", argtypes: ["String", "ByStr20"], arguments: [lib.name, lib.address], })), }); } if (contractParams) { if (userSpecifiedArgs.length !== contractParams.length) { throw new Error( `Expected to receive ${contractParams.length} parameters for ${contractName} deployment but got ${userSpecifiedArgs.length}` ); } contractParams.forEach((param: Field, index: number) => { if (isNumeric(param.type)) { init.push({ vname: param.name, type: param.type, value: userSpecifiedArgs[index].toString(), }); } else { // It's an ADT or string init.push({ vname: param.name, type: param.type, value: userSpecifiedArgs[index] as any, }); } }); } else { if (userSpecifiedArgs.length > 0) { throw new Error( `Expected to receive 0 parameters for ${contractName} deployment but got ${userSpecifiedArgs.length}` ); } } return init; }; // deploy a smart contract whose code is in a file with given init arguments export async function deployFromFile( path: string, init: Init, compress: boolean, txParamsForContractDeployment: any ): Promise<[Transaction, ScillaContract]> { if (setup === null) { throw new HardhatPluginError( "hardhat-scilla-plugin", "Please call initZilliqa function." ); } const deployer = setup.zilliqa.wallet.defaultAccount ?? setup.accounts[0]; let code = read(path); if (compress) { code = compressContract(code); } const contract = setup.zilliqa.contracts.new(code, init); const [tx, sc] = await contract.deploy( { ...setup, pubKey: deployer.publicKey, ...txParamsForContractDeployment }, setup.attempts, setup.timeout, false ); // Let's add this function for further signer/executer changes. ScillaContractProxy.injectConnectors(setup, sc); sc.connect(deployer); return [tx, sc]; } export function compressContract(code: string): string { // Remove comments code = code.replace(/(\(\*.*?\*\))/gms, ""); // Remove empty lines code = code.replace(/(^[ \t]*\n)/gm, ""); // Remove extra whitespace at the end of the lines return code.replace(/[ \t]+$/gm, ""); }