@pendulum-chain/api-solang
Version:
Interface to interact with smart contracts compiled via Solang
165 lines (164 loc) • 6.26 kB
JavaScript
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,
};
}
}