UNPKG

@pendulum-chain/api-solang

Version:

Interface to interact with smart contracts compiled via Solang

165 lines (164 loc) 6.26 kB
import { BN_ZERO } from "@polkadot/util"; import { ContractPromise } from "@polkadot/api-contract"; import { rpcCall } from "./contractRpc.js"; import { signAndSubmitExtrinsic, submitExtrinsic, signExtrinsic, } from "./submitExtrinsic.js"; import { addressEq } from "@polkadot/util-crypto"; import { basicDeployContract } from "./deployContract.js"; export { signExtrinsic, submitExtrinsic, signAndSubmitExtrinsic, }; function decodeContractEvents(eventRecords, lookupAbi) { return eventRecords .filter(({ event: { section, method } }) => section === "contracts" && method === "ContractEmitted") .map((eventRecord) => { const dataJson = eventRecord.event.data.toHuman(); const emittingContractAddress = dataJson.contract; let dataHexString = dataJson.data; if (dataHexString.startsWith("0x")) dataHexString = dataHexString.slice(2); const data = new Uint8Array(dataHexString.length / 2); for (let i = 0; i * 2 < dataHexString.length; i += 1) { data[i] = parseInt(dataHexString.slice(i * 2, i * 2 + 2), 16); } const abi = lookupAbi?.(emittingContractAddress); if (abi === undefined) { return { emittingContractAddress, data, }; } const decodedEvent = abi.decodeEvent(eventRecord); return { emittingContractAddress, data, decoded: { args: decodedEvent.event.args.map((arg, index) => ({ name: arg.name, value: decodedEvent.args[index].toHuman(), })), eventIdentifier: decodedEvent.event.identifier, }, }; }); } export async function deployContract({ signer, api, abi, constructorArguments, constructorName, limits, skipDryRunning, modifyExtrinsic, lookupAbi, }) { const result = await basicDeployContract({ api, abi, constructorArguments, constructorName, limits, signer, skipDryRunning, modifyExtrinsic, }); switch (result.type) { case "panic": case "reverted": case "error": return result; } const extendedLookupAbi = (contractAddress) => { if (addressEq(contractAddress, result.deploymentAddress)) { return abi; } return lookupAbi?.(contractAddress); }; return { ...result, events: decodeContractEvents(result.eventRecords, extendedLookupAbi), }; } export async function executeMessage(options) { const { getSigner, modifyExtrinsic, lookupAbi } = options; const { execution, result: readMessageResult } = await createExecuteMessageExtrinsic(options); if (execution.type === "onlyRpc") { return { execution, result: readMessageResult, }; } let { extrinsic } = execution; if (modifyExtrinsic) { extrinsic = modifyExtrinsic(extrinsic); } const signer = await getSigner(); const { eventRecords, status, transactionFee, txIndex, txHash } = await signAndSubmitExtrinsic(extrinsic, signer); return { execution: { type: "extrinsic", contractEvents: decodeContractEvents(eventRecords, lookupAbi), transactionFee, txIndex, txHash, }, result: status.type === "success" || readMessageResult === undefined ? readMessageResult : { ...status, gasMetrics: readMessageResult.gasMetrics }, }; } export async function createExecuteMessageExtrinsic({ abi, api, contractDeploymentAddress, messageArguments, messageName, limits, callerAddress, gasLimitTolerancePercentage = 10, skipDryRunning, }) { const contract = new ContractPromise(api, abi, contractDeploymentAddress); let gasRequired; let readMessageResult; if (skipDryRunning === true) { gasRequired = api.createType("WeightV2", limits.gas); } else { readMessageResult = await readMessage({ api, abi, contractDeploymentAddress, callerAddress, messageName, messageArguments, limits, }); if (readMessageResult.type !== "success") { return { execution: { type: "onlyRpc" }, result: readMessageResult }; } gasRequired = readMessageResult.gasMetrics.gasRequired; if (gasLimitTolerancePercentage > 0) { gasRequired = api.createType("WeightV2", { refTime: (gasRequired.refTime.toBigInt() * (100n + BigInt(gasLimitTolerancePercentage))) / 100n, proofSize: (gasRequired.proofSize.toBigInt() * (100n + BigInt(gasLimitTolerancePercentage))) / 100n, }); } } const typesAddress = api.registry.createType("AccountId", contractDeploymentAddress); let extrinsic = api.tx.contracts.call(typesAddress, BN_ZERO, gasRequired, limits.storageDeposit, contract.abi.findMessage(messageName).toU8a(messageArguments)); return { execution: { type: "extrinsic", extrinsic }, result: readMessageResult, }; } export async function submitExecuteMessageExtrinsic(extrinsic, lookupAbi) { const { eventRecords, status, transactionFee } = await submitExtrinsic(extrinsic); return { contractEvents: decodeContractEvents(eventRecords, lookupAbi), transactionFee, status, }; } export async function readMessage({ abi, api, contractDeploymentAddress, callerAddress, messageName, messageArguments, limits, }) { const { gasRequired, gasConsumed, output } = await rpcCall({ api, abi, contractAddress: contractDeploymentAddress, callerAddress, limits, messageName, messageArguments, }); const gasMetrics = { gasRequired, gasConsumed }; switch (output.type) { case "success": case "reverted": case "panic": return { ...output, gasMetrics }; case "error": return { type: "error", error: output.description ?? "unknown", gasMetrics, }; } }