@pendulum-chain/api-solang
Version:
Interface to interact with smart contracts compiled via Solang
175 lines (174 loc) • 7.14 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.signAndSubmitExtrinsic = exports.submitExtrinsic = exports.signExtrinsic = void 0;
exports.deployContract = deployContract;
exports.executeMessage = executeMessage;
exports.createExecuteMessageExtrinsic = createExecuteMessageExtrinsic;
exports.submitExecuteMessageExtrinsic = submitExecuteMessageExtrinsic;
exports.readMessage = readMessage;
const util_1 = require("@polkadot/util");
const api_contract_1 = require("@polkadot/api-contract");
const contractRpc_js_1 = require("./contractRpc.js");
const submitExtrinsic_js_1 = require("./submitExtrinsic.js");
Object.defineProperty(exports, "signAndSubmitExtrinsic", { enumerable: true, get: function () { return submitExtrinsic_js_1.signAndSubmitExtrinsic; } });
Object.defineProperty(exports, "submitExtrinsic", { enumerable: true, get: function () { return submitExtrinsic_js_1.submitExtrinsic; } });
Object.defineProperty(exports, "signExtrinsic", { enumerable: true, get: function () { return submitExtrinsic_js_1.signExtrinsic; } });
const util_crypto_1 = require("@polkadot/util-crypto");
const deployContract_js_1 = require("./deployContract.js");
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,
},
};
});
}
async function deployContract({ signer, api, abi, constructorArguments, constructorName, limits, skipDryRunning, modifyExtrinsic, lookupAbi, }) {
const result = await (0, deployContract_js_1.basicDeployContract)({
api,
abi,
constructorArguments,
constructorName,
limits,
signer,
skipDryRunning,
modifyExtrinsic,
});
switch (result.type) {
case "panic":
case "reverted":
case "error":
return result;
}
const extendedLookupAbi = (contractAddress) => {
if ((0, util_crypto_1.addressEq)(contractAddress, result.deploymentAddress)) {
return abi;
}
return lookupAbi?.(contractAddress);
};
return {
...result,
events: decodeContractEvents(result.eventRecords, extendedLookupAbi),
};
}
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 (0, submitExtrinsic_js_1.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 },
};
}
async function createExecuteMessageExtrinsic({ abi, api, contractDeploymentAddress, messageArguments, messageName, limits, callerAddress, gasLimitTolerancePercentage = 10, skipDryRunning, }) {
const contract = new api_contract_1.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, util_1.BN_ZERO, gasRequired, limits.storageDeposit, contract.abi.findMessage(messageName).toU8a(messageArguments));
return {
execution: { type: "extrinsic", extrinsic },
result: readMessageResult,
};
}
async function submitExecuteMessageExtrinsic(extrinsic, lookupAbi) {
const { eventRecords, status, transactionFee } = await (0, submitExtrinsic_js_1.submitExtrinsic)(extrinsic);
return {
contractEvents: decodeContractEvents(eventRecords, lookupAbi),
transactionFee,
status,
};
}
async function readMessage({ abi, api, contractDeploymentAddress, callerAddress, messageName, messageArguments, limits, }) {
const { gasRequired, gasConsumed, output } = await (0, contractRpc_js_1.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,
};
}
}