@pendulum-chain/api-solang
Version:
Interface to interact with smart contracts compiled via Solang
136 lines (135 loc) • 5.83 kB
JavaScript
import { BN_ZERO } from "@polkadot/util";
import { extractDispatchErrorDescription } from "./dispatchError.js";
// error explanations taken from https://docs.soliditylang.org/en/v0.8.20/control-structures.html#panic-via-assert-and-error-via-require
export function explainPanicError(errorCode) {
switch (errorCode) {
case 0x00:
return "Used for generic compiler inserted panics.";
case 0x01:
return "Assert called with an argument that evaluated to false.";
case 0x11:
return "Arithmetic operation resulted in underflow or overflow outside of an unchecked { ... } block.";
case 0x12:
return "Division or modulo by zero (e.g. 5 / 0 or 23 % 0).";
case 0x21:
return "Converted a value that is too big or negative into an enum type.";
case 0x22:
return "Accessed a storage byte array that is incorrectly encoded.";
case 0x31:
return "Called .pop() on an empty array.";
case 0x32:
return "Accessed an array, bytesN or an array slice at an out-of-bounds or negative index (i.e. x[i] where i >= x.length or i < 0).";
case 0x41:
return "Allocated too much memory or create an array that is too large.";
case 0x51:
return "Called a zero-initialized variable of internal function type.";
default:
return "Unknown panic error";
}
}
function extractContractExecutionOutput(api, abi, result, returnType) {
const data = result.data.toU8a(true);
const contractReturnFlags = result.flags.bits?.toNumber() ?? 0;
const didRevert = result.flags.isRevert || (contractReturnFlags & 0x01) === 0x01;
if (!didRevert) {
const value = returnType
? abi.registry.createTypeUnsafe(returnType.lookupName || returnType.type, [data], {
isPedantic: true,
})
: undefined;
return { type: "success", value };
}
else {
const dataView = new DataView(data.buffer);
const prefix = data.buffer.byteLength >= 4 ? dataView.getUint32(0) : 0;
switch (prefix) {
case 0x08c379a0:
return {
type: "reverted",
description: api.createType("String", data.slice(4)).toString(),
};
case 0x4e487b71:
try {
const errorCode = data.buffer.byteLength >= 36
? dataView.getBigUint64(4, true) +
2n ** 64n * dataView.getBigUint64(12, true) +
2n ** 128n * dataView.getBigUint64(20, true) +
2n ** 192n * dataView.getBigUint64(28, true)
: -1n;
return {
type: "panic",
errorCode: Number(errorCode),
explanation: explainPanicError(Number(errorCode)),
};
}
catch { }
default:
return { type: "error" };
}
}
}
export async function rpcCall({ api, abi, callerAddress, messageName, contractAddress, limits, messageArguments, }) {
let resolved = false;
return new Promise((resolve) => {
const message = abi.findMessage(messageName);
const observable = api.rx.call.contractsApi.call(callerAddress, api.createType("AccountId", contractAddress), BN_ZERO, api.createType("WeightV2", limits.gas), limits.storageDeposit, message.toU8a(messageArguments));
observable.forEach((event) => {
if (resolved) {
return;
}
resolved = true;
const { result, gasRequired, gasConsumed } = event;
if (result.isOk) {
resolve({
gasRequired,
gasConsumed,
output: extractContractExecutionOutput(api, abi, result.asOk, message.returnType),
});
}
else {
resolve({
gasRequired,
gasConsumed,
output: {
type: "error",
description: extractDispatchErrorDescription(result.asErr),
},
});
}
});
});
}
export async function rpcInstantiate({ api, abi, callerAddress, constructorName, limits, constructorArguments, }) {
let resolved = false;
return new Promise((resolve) => {
const constructor = abi.findConstructor(constructorName);
const data = constructor.toU8a(constructorArguments);
const salt = new Uint8Array();
//const code = api.createType("Code", { Upload: abi.info.source.wasm });
const observable = api.rx.call.contractsApi.instantiate(callerAddress, BN_ZERO, api.createType("WeightV2", limits.gas), limits.storageDeposit, { Upload: abi.info.source.wasm }, data, salt);
observable.forEach((event) => {
if (resolved) {
return;
}
resolved = true;
const { result, gasRequired, gasConsumed } = event;
if (result.isOk) {
resolve({
gasRequired,
gasConsumed,
output: extractContractExecutionOutput(api, abi, result.asOk.result, constructor.returnType),
});
}
else {
resolve({
gasRequired,
gasConsumed,
output: {
type: "error",
description: extractDispatchErrorDescription(result.asErr),
},
});
}
});
});
}