UNPKG

hardhat-scilla-plugin

Version:
202 lines (184 loc) 6.12 kB
// Routines to construct proxies for Scilla contracts. import { Account } from "@zilliqa-js/account"; import { CallParams, State } from "@zilliqa-js/contract"; import { BN } from "@zilliqa-js/util"; import { HardhatPluginError } from "hardhat/plugins"; import * as ScillaContractDeployer from "../deployer/ScillaContractDeployer"; import { ScillaContract, Value, Setup, } from "../deployer/ScillaContractDeployer"; import { ContractInfo } from "./ScillaContractsInfoUpdater"; import { Field, generateTypeConstructors, isNumeric, TransitionParam, } from "./ScillaParser"; function handleParam(param: Field, arg: any): Value { if (typeof param.typeJSON === "undefined") { throw new HardhatPluginError( "hardhat-scilla-plugin", "Parameters were incorrectly parsed. Try clearing your scilla.cache file." ); } else if (typeof param.typeJSON === "string") { return { vname: param.name, type: param.type, value: arg.toString(), }; } else { const values: Value[] = []; param.typeJSON.argtypes.forEach((p: Field, index: number) => { values.push(handleUnnamedParam(p, arg[index])); }); const argtypes = param.typeJSON.argtypes.map((x) => x.type); /* We use JSON.parse(JSON.strigify()) because we need to create a JSON with a constructor field. Typescript expects this constructor to have the same type as an object constructor which is not possible as it should be a string for our purposes. This trick allows forces the typescript compiler to enforce this. */ const value = JSON.parse( JSON.stringify({ constructor: param.typeJSON.ctor, argtypes, arguments: values, }) ); return { vname: param.name, type: param.type, value, }; } } function handleUnnamedParam(param: Field, arg: any): Value { if (typeof param.typeJSON === "undefined") { throw new HardhatPluginError( "hardhat-scilla-plugin", "Parameters were incorrectly parsed. Try clearing your scilla.cache file." ); } else if (typeof param.typeJSON === "string") { return arg.toString(); } else { const values: Value[] = []; param.typeJSON.argtypes.forEach((f: Field, index: number) => { values.push(handleUnnamedParam(f, arg[index])); }); const argtypes = param.typeJSON.argtypes.map((x) => x.type); /* We use JSON.parse(JSON.strigify()) because we need to create a JSON with a constructor field. Typescript expects this constructor to have the same type as an object constructor which is not possible as it should be a string for our purposes. This trick allows forces the typescript compiler to enforce this. */ return JSON.parse( JSON.stringify({ vname: param.name, type: param.type, constructor: param.typeJSON.ctor, argtypes, arguments: values, }) ); } } export function injectConnectors(setup: Setup, sc: ScillaContract) { sc.connect = (signer: Account) => { sc.executer = signer; // If account is not added already, add it to the list. if ( setup?.accounts.findIndex( (acc) => acc.privateKey === signer.privateKey ) === -1 ) { setup?.zilliqa.wallet.addByPrivateKey(signer.privateKey); setup?.accounts.push(signer); } return sc; }; } // Inject proxy functions into a contract. export function injectProxies( setup: Setup, contractInfo: ContractInfo, sc: ScillaContract ) { contractInfo.parsedContract.transitions.forEach((transition) => { sc[transition.name] = async (...args: any[]) => { let callParams: CallParams = { version: setup!.version, gasPrice: setup!.gasPrice, gasLimit: setup!.gasLimit, amount: new BN(0), }; if (args.length === transition.params.length + 1) { // The last param is Tx info such as amount const txParams = args.pop(); callParams = { ...callParams, ...txParams }; } else if (args.length !== transition.params.length) { throw new Error( `Expected to receive ${transition.params.length} parameters for ${transition.name} but got ${args.length}` ); } const values: Value[] = []; transition.params.forEach((param: TransitionParam, index: number) => { values.push(handleParam(param, args[index])); }); return sc_call(sc, transition.name, values, callParams); }; }); contractInfo.parsedContract.fields.forEach((field) => { sc[field.name] = async () => { const state = await sc.getState(); if (isNumeric(field.type)) { return Number(state[field.name]); } return state[field.name]; }; }); if (contractInfo.parsedContract.constructorParams) { contractInfo.parsedContract.constructorParams.forEach((field) => { sc[field.name] = async () => { const states: State = await sc.getInit(); const state = states.filter( (s: { vname: string }) => s.vname === field.name )[0]; if (isNumeric(field.type)) { return Number(state.value); } return state.value; }; }); } // Will shadow any transition named ctors. But done like this to avoid changing the signature of deploy. const parsedCtors = contractInfo.parsedContract.ctors; sc.ctors = generateTypeConstructors(parsedCtors); } // call a smart contract's transition with given args and an amount to send from a given public key export async function sc_call( sc: ScillaContract, transition: string, args: Value[] = [], callParams: CallParams ) { if (ScillaContractDeployer.setup === null) { throw new HardhatPluginError( "hardhat-scilla-plugin", "Please call initZilliqa function." ); } if (callParams.pubKey === undefined && sc.executer) { callParams.pubKey = sc.executer.publicKey; } return sc.call( transition, args, callParams, ScillaContractDeployer.setup.attempts, ScillaContractDeployer.setup.timeout, true ); }