ethers.js/packages/abi/lib/interface.js
2021-06-26 01:55:19 -04:00

648 lines
28 KiB
JavaScript

"use strict";
var __extends = (this && this.__extends) || (function () {
var extendStatics = function (d, b) {
extendStatics = Object.setPrototypeOf ||
({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
function (d, b) { for (var p in b) if (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; };
return extendStatics(d, b);
};
return function (d, b) {
if (typeof b !== "function" && b !== null)
throw new TypeError("Class extends value " + String(b) + " is not a constructor or null");
extendStatics(d, b);
function __() { this.constructor = d; }
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
};
})();
Object.defineProperty(exports, "__esModule", { value: true });
exports.Interface = exports.Indexed = exports.ErrorDescription = exports.TransactionDescription = exports.LogDescription = exports.checkResultErrors = void 0;
var address_1 = require("@ethersproject/address");
var bignumber_1 = require("@ethersproject/bignumber");
var bytes_1 = require("@ethersproject/bytes");
var hash_1 = require("@ethersproject/hash");
var keccak256_1 = require("@ethersproject/keccak256");
var properties_1 = require("@ethersproject/properties");
var abi_coder_1 = require("./abi-coder");
var abstract_coder_1 = require("./coders/abstract-coder");
Object.defineProperty(exports, "checkResultErrors", { enumerable: true, get: function () { return abstract_coder_1.checkResultErrors; } });
var fragments_1 = require("./fragments");
var logger_1 = require("@ethersproject/logger");
var _version_1 = require("./_version");
var logger = new logger_1.Logger(_version_1.version);
var LogDescription = /** @class */ (function (_super) {
__extends(LogDescription, _super);
function LogDescription() {
return _super !== null && _super.apply(this, arguments) || this;
}
return LogDescription;
}(properties_1.Description));
exports.LogDescription = LogDescription;
var TransactionDescription = /** @class */ (function (_super) {
__extends(TransactionDescription, _super);
function TransactionDescription() {
return _super !== null && _super.apply(this, arguments) || this;
}
return TransactionDescription;
}(properties_1.Description));
exports.TransactionDescription = TransactionDescription;
var ErrorDescription = /** @class */ (function (_super) {
__extends(ErrorDescription, _super);
function ErrorDescription() {
return _super !== null && _super.apply(this, arguments) || this;
}
return ErrorDescription;
}(properties_1.Description));
exports.ErrorDescription = ErrorDescription;
var Indexed = /** @class */ (function (_super) {
__extends(Indexed, _super);
function Indexed() {
return _super !== null && _super.apply(this, arguments) || this;
}
Indexed.isIndexed = function (value) {
return !!(value && value._isIndexed);
};
return Indexed;
}(properties_1.Description));
exports.Indexed = Indexed;
var BuiltinErrors = {
"0x08c379a0": { signature: "Error(string)", name: "Error", inputs: ["string"], reason: true },
"0x4e487b71": { signature: "Panic(uint256)", name: "Panic", inputs: ["uint256"] }
};
function wrapAccessError(property, error) {
var wrap = new Error("deferred error during ABI decoding triggered accessing " + property);
wrap.error = error;
return wrap;
}
/*
function checkNames(fragment: Fragment, type: "input" | "output", params: Array<ParamType>): void {
params.reduce((accum, param) => {
if (param.name) {
if (accum[param.name]) {
logger.throwArgumentError(`duplicate ${ type } parameter ${ JSON.stringify(param.name) } in ${ fragment.format("full") }`, "fragment", fragment);
}
accum[param.name] = true;
}
return accum;
}, <{ [ name: string ]: boolean }>{ });
}
*/
var Interface = /** @class */ (function () {
function Interface(fragments) {
var _newTarget = this.constructor;
var _this = this;
logger.checkNew(_newTarget, Interface);
var abi = [];
if (typeof (fragments) === "string") {
abi = JSON.parse(fragments);
}
else {
abi = fragments;
}
properties_1.defineReadOnly(this, "fragments", abi.map(function (fragment) {
return fragments_1.Fragment.from(fragment);
}).filter(function (fragment) { return (fragment != null); }));
properties_1.defineReadOnly(this, "_abiCoder", properties_1.getStatic((_newTarget), "getAbiCoder")());
properties_1.defineReadOnly(this, "functions", {});
properties_1.defineReadOnly(this, "errors", {});
properties_1.defineReadOnly(this, "events", {});
properties_1.defineReadOnly(this, "structs", {});
// Add all fragments by their signature
this.fragments.forEach(function (fragment) {
var bucket = null;
switch (fragment.type) {
case "constructor":
if (_this.deploy) {
logger.warn("duplicate definition - constructor");
return;
}
//checkNames(fragment, "input", fragment.inputs);
properties_1.defineReadOnly(_this, "deploy", fragment);
return;
case "function":
//checkNames(fragment, "input", fragment.inputs);
//checkNames(fragment, "output", (<FunctionFragment>fragment).outputs);
bucket = _this.functions;
break;
case "event":
//checkNames(fragment, "input", fragment.inputs);
bucket = _this.events;
break;
case "error":
bucket = _this.errors;
break;
default:
return;
}
var signature = fragment.format();
if (bucket[signature]) {
logger.warn("duplicate definition - " + signature);
return;
}
bucket[signature] = fragment;
});
// If we do not have a constructor add a default
if (!this.deploy) {
properties_1.defineReadOnly(this, "deploy", fragments_1.ConstructorFragment.from({
payable: false,
type: "constructor"
}));
}
properties_1.defineReadOnly(this, "_isInterface", true);
}
Interface.prototype.format = function (format) {
if (!format) {
format = fragments_1.FormatTypes.full;
}
if (format === fragments_1.FormatTypes.sighash) {
logger.throwArgumentError("interface does not support formatting sighash", "format", format);
}
var abi = this.fragments.map(function (fragment) { return fragment.format(format); });
// We need to re-bundle the JSON fragments a bit
if (format === fragments_1.FormatTypes.json) {
return JSON.stringify(abi.map(function (j) { return JSON.parse(j); }));
}
return abi;
};
// Sub-classes can override these to handle other blockchains
Interface.getAbiCoder = function () {
return abi_coder_1.defaultAbiCoder;
};
Interface.getAddress = function (address) {
return address_1.getAddress(address);
};
Interface.getSighash = function (fragment) {
return bytes_1.hexDataSlice(hash_1.id(fragment.format()), 0, 4);
};
Interface.getEventTopic = function (eventFragment) {
return hash_1.id(eventFragment.format());
};
// Find a function definition by any means necessary (unless it is ambiguous)
Interface.prototype.getFunction = function (nameOrSignatureOrSighash) {
if (bytes_1.isHexString(nameOrSignatureOrSighash)) {
for (var name_1 in this.functions) {
if (nameOrSignatureOrSighash === this.getSighash(name_1)) {
return this.functions[name_1];
}
}
logger.throwArgumentError("no matching function", "sighash", nameOrSignatureOrSighash);
}
// It is a bare name, look up the function (will return null if ambiguous)
if (nameOrSignatureOrSighash.indexOf("(") === -1) {
var name_2 = nameOrSignatureOrSighash.trim();
var matching = Object.keys(this.functions).filter(function (f) { return (f.split("(" /* fix:) */)[0] === name_2); });
if (matching.length === 0) {
logger.throwArgumentError("no matching function", "name", name_2);
}
else if (matching.length > 1) {
logger.throwArgumentError("multiple matching functions", "name", name_2);
}
return this.functions[matching[0]];
}
// Normlize the signature and lookup the function
var result = this.functions[fragments_1.FunctionFragment.fromString(nameOrSignatureOrSighash).format()];
if (!result) {
logger.throwArgumentError("no matching function", "signature", nameOrSignatureOrSighash);
}
return result;
};
// Find an event definition by any means necessary (unless it is ambiguous)
Interface.prototype.getEvent = function (nameOrSignatureOrTopic) {
if (bytes_1.isHexString(nameOrSignatureOrTopic)) {
var topichash = nameOrSignatureOrTopic.toLowerCase();
for (var name_3 in this.events) {
if (topichash === this.getEventTopic(name_3)) {
return this.events[name_3];
}
}
logger.throwArgumentError("no matching event", "topichash", topichash);
}
// It is a bare name, look up the function (will return null if ambiguous)
if (nameOrSignatureOrTopic.indexOf("(") === -1) {
var name_4 = nameOrSignatureOrTopic.trim();
var matching = Object.keys(this.events).filter(function (f) { return (f.split("(" /* fix:) */)[0] === name_4); });
if (matching.length === 0) {
logger.throwArgumentError("no matching event", "name", name_4);
}
else if (matching.length > 1) {
logger.throwArgumentError("multiple matching events", "name", name_4);
}
return this.events[matching[0]];
}
// Normlize the signature and lookup the function
var result = this.events[fragments_1.EventFragment.fromString(nameOrSignatureOrTopic).format()];
if (!result) {
logger.throwArgumentError("no matching event", "signature", nameOrSignatureOrTopic);
}
return result;
};
// Find a function definition by any means necessary (unless it is ambiguous)
Interface.prototype.getError = function (nameOrSignatureOrSighash) {
if (bytes_1.isHexString(nameOrSignatureOrSighash)) {
var getSighash = properties_1.getStatic(this.constructor, "getSighash");
for (var name_5 in this.errors) {
var error = this.errors[name_5];
if (nameOrSignatureOrSighash === getSighash(error)) {
return this.errors[name_5];
}
}
logger.throwArgumentError("no matching error", "sighash", nameOrSignatureOrSighash);
}
// It is a bare name, look up the function (will return null if ambiguous)
if (nameOrSignatureOrSighash.indexOf("(") === -1) {
var name_6 = nameOrSignatureOrSighash.trim();
var matching = Object.keys(this.errors).filter(function (f) { return (f.split("(" /* fix:) */)[0] === name_6); });
if (matching.length === 0) {
logger.throwArgumentError("no matching error", "name", name_6);
}
else if (matching.length > 1) {
logger.throwArgumentError("multiple matching errors", "name", name_6);
}
return this.errors[matching[0]];
}
// Normlize the signature and lookup the function
var result = this.errors[fragments_1.FunctionFragment.fromString(nameOrSignatureOrSighash).format()];
if (!result) {
logger.throwArgumentError("no matching error", "signature", nameOrSignatureOrSighash);
}
return result;
};
// Get the sighash (the bytes4 selector) used by Solidity to identify a function
Interface.prototype.getSighash = function (fragment) {
if (typeof (fragment) === "string") {
try {
fragment = this.getFunction(fragment);
}
catch (error) {
try {
fragment = this.getError(fragment);
}
catch (_) {
throw error;
}
}
}
return properties_1.getStatic(this.constructor, "getSighash")(fragment);
};
// Get the topic (the bytes32 hash) used by Solidity to identify an event
Interface.prototype.getEventTopic = function (eventFragment) {
if (typeof (eventFragment) === "string") {
eventFragment = this.getEvent(eventFragment);
}
return properties_1.getStatic(this.constructor, "getEventTopic")(eventFragment);
};
Interface.prototype._decodeParams = function (params, data) {
return this._abiCoder.decode(params, data);
};
Interface.prototype._encodeParams = function (params, values) {
return this._abiCoder.encode(params, values);
};
Interface.prototype.encodeDeploy = function (values) {
return this._encodeParams(this.deploy.inputs, values || []);
};
Interface.prototype.decodeErrorResult = function (fragment, data) {
if (typeof (fragment) === "string") {
fragment = this.getError(fragment);
}
var bytes = bytes_1.arrayify(data);
if (bytes_1.hexlify(bytes.slice(0, 4)) !== this.getSighash(fragment)) {
logger.throwArgumentError("data signature does not match error " + fragment.name + ".", "data", bytes_1.hexlify(bytes));
}
return this._decodeParams(fragment.inputs, bytes.slice(4));
};
Interface.prototype.encodeErrorResult = function (fragment, values) {
if (typeof (fragment) === "string") {
fragment = this.getError(fragment);
}
return bytes_1.hexlify(bytes_1.concat([
this.getSighash(fragment),
this._encodeParams(fragment.inputs, values || [])
]));
};
// Decode the data for a function call (e.g. tx.data)
Interface.prototype.decodeFunctionData = function (functionFragment, data) {
if (typeof (functionFragment) === "string") {
functionFragment = this.getFunction(functionFragment);
}
var bytes = bytes_1.arrayify(data);
if (bytes_1.hexlify(bytes.slice(0, 4)) !== this.getSighash(functionFragment)) {
logger.throwArgumentError("data signature does not match function " + functionFragment.name + ".", "data", bytes_1.hexlify(bytes));
}
return this._decodeParams(functionFragment.inputs, bytes.slice(4));
};
// Encode the data for a function call (e.g. tx.data)
Interface.prototype.encodeFunctionData = function (functionFragment, values) {
if (typeof (functionFragment) === "string") {
functionFragment = this.getFunction(functionFragment);
}
return bytes_1.hexlify(bytes_1.concat([
this.getSighash(functionFragment),
this._encodeParams(functionFragment.inputs, values || [])
]));
};
// Decode the result from a function call (e.g. from eth_call)
Interface.prototype.decodeFunctionResult = function (functionFragment, data) {
if (typeof (functionFragment) === "string") {
functionFragment = this.getFunction(functionFragment);
}
var bytes = bytes_1.arrayify(data);
var reason = null;
var errorArgs = null;
var errorName = null;
var errorSignature = null;
switch (bytes.length % this._abiCoder._getWordSize()) {
case 0:
try {
return this._abiCoder.decode(functionFragment.outputs, bytes);
}
catch (error) { }
break;
case 4: {
var selector = bytes_1.hexlify(bytes.slice(0, 4));
var builtin = BuiltinErrors[selector];
if (builtin) {
errorArgs = this._abiCoder.decode(builtin.inputs, bytes.slice(4));
errorName = builtin.name;
errorSignature = builtin.signature;
if (builtin.reason) {
reason = errorArgs[0];
}
}
else {
try {
var error = this.getError(selector);
errorArgs = this._abiCoder.decode(error.inputs, bytes.slice(4));
errorName = error.name;
errorSignature = error.format();
}
catch (error) {
console.log(error);
}
}
break;
}
}
return logger.throwError("call revert exception", logger_1.Logger.errors.CALL_EXCEPTION, {
method: functionFragment.format(),
errorArgs: errorArgs, errorName: errorName, errorSignature: errorSignature, reason: reason
});
};
// Encode the result for a function call (e.g. for eth_call)
Interface.prototype.encodeFunctionResult = function (functionFragment, values) {
if (typeof (functionFragment) === "string") {
functionFragment = this.getFunction(functionFragment);
}
return bytes_1.hexlify(this._abiCoder.encode(functionFragment.outputs, values || []));
};
// Create the filter for the event with search criteria (e.g. for eth_filterLog)
Interface.prototype.encodeFilterTopics = function (eventFragment, values) {
var _this = this;
if (typeof (eventFragment) === "string") {
eventFragment = this.getEvent(eventFragment);
}
if (values.length > eventFragment.inputs.length) {
logger.throwError("too many arguments for " + eventFragment.format(), logger_1.Logger.errors.UNEXPECTED_ARGUMENT, {
argument: "values",
value: values
});
}
var topics = [];
if (!eventFragment.anonymous) {
topics.push(this.getEventTopic(eventFragment));
}
var encodeTopic = function (param, value) {
if (param.type === "string") {
return hash_1.id(value);
}
else if (param.type === "bytes") {
return keccak256_1.keccak256(bytes_1.hexlify(value));
}
// Check addresses are valid
if (param.type === "address") {
_this._abiCoder.encode(["address"], [value]);
}
return bytes_1.hexZeroPad(bytes_1.hexlify(value), 32);
};
values.forEach(function (value, index) {
var param = eventFragment.inputs[index];
if (!param.indexed) {
if (value != null) {
logger.throwArgumentError("cannot filter non-indexed parameters; must be null", ("contract." + param.name), value);
}
return;
}
if (value == null) {
topics.push(null);
}
else if (param.baseType === "array" || param.baseType === "tuple") {
logger.throwArgumentError("filtering with tuples or arrays not supported", ("contract." + param.name), value);
}
else if (Array.isArray(value)) {
topics.push(value.map(function (value) { return encodeTopic(param, value); }));
}
else {
topics.push(encodeTopic(param, value));
}
});
// Trim off trailing nulls
while (topics.length && topics[topics.length - 1] === null) {
topics.pop();
}
return topics;
};
Interface.prototype.encodeEventLog = function (eventFragment, values) {
var _this = this;
if (typeof (eventFragment) === "string") {
eventFragment = this.getEvent(eventFragment);
}
var topics = [];
var dataTypes = [];
var dataValues = [];
if (!eventFragment.anonymous) {
topics.push(this.getEventTopic(eventFragment));
}
if (values.length !== eventFragment.inputs.length) {
logger.throwArgumentError("event arguments/values mismatch", "values", values);
}
eventFragment.inputs.forEach(function (param, index) {
var value = values[index];
if (param.indexed) {
if (param.type === "string") {
topics.push(hash_1.id(value));
}
else if (param.type === "bytes") {
topics.push(keccak256_1.keccak256(value));
}
else if (param.baseType === "tuple" || param.baseType === "array") {
// @TOOD
throw new Error("not implemented");
}
else {
topics.push(_this._abiCoder.encode([param.type], [value]));
}
}
else {
dataTypes.push(param);
dataValues.push(value);
}
});
return {
data: this._abiCoder.encode(dataTypes, dataValues),
topics: topics
};
};
// Decode a filter for the event and the search criteria
Interface.prototype.decodeEventLog = function (eventFragment, data, topics) {
if (typeof (eventFragment) === "string") {
eventFragment = this.getEvent(eventFragment);
}
if (topics != null && !eventFragment.anonymous) {
var topicHash = this.getEventTopic(eventFragment);
if (!bytes_1.isHexString(topics[0], 32) || topics[0].toLowerCase() !== topicHash) {
logger.throwError("fragment/topic mismatch", logger_1.Logger.errors.INVALID_ARGUMENT, { argument: "topics[0]", expected: topicHash, value: topics[0] });
}
topics = topics.slice(1);
}
var indexed = [];
var nonIndexed = [];
var dynamic = [];
eventFragment.inputs.forEach(function (param, index) {
if (param.indexed) {
if (param.type === "string" || param.type === "bytes" || param.baseType === "tuple" || param.baseType === "array") {
indexed.push(fragments_1.ParamType.fromObject({ type: "bytes32", name: param.name }));
dynamic.push(true);
}
else {
indexed.push(param);
dynamic.push(false);
}
}
else {
nonIndexed.push(param);
dynamic.push(false);
}
});
var resultIndexed = (topics != null) ? this._abiCoder.decode(indexed, bytes_1.concat(topics)) : null;
var resultNonIndexed = this._abiCoder.decode(nonIndexed, data, true);
var result = [];
var nonIndexedIndex = 0, indexedIndex = 0;
eventFragment.inputs.forEach(function (param, index) {
if (param.indexed) {
if (resultIndexed == null) {
result[index] = new Indexed({ _isIndexed: true, hash: null });
}
else if (dynamic[index]) {
result[index] = new Indexed({ _isIndexed: true, hash: resultIndexed[indexedIndex++] });
}
else {
try {
result[index] = resultIndexed[indexedIndex++];
}
catch (error) {
result[index] = error;
}
}
}
else {
try {
result[index] = resultNonIndexed[nonIndexedIndex++];
}
catch (error) {
result[index] = error;
}
}
// Add the keyword argument if named and safe
if (param.name && result[param.name] == null) {
var value_1 = result[index];
// Make error named values throw on access
if (value_1 instanceof Error) {
Object.defineProperty(result, param.name, {
get: function () { throw wrapAccessError("property " + JSON.stringify(param.name), value_1); }
});
}
else {
result[param.name] = value_1;
}
}
});
var _loop_1 = function (i) {
var value = result[i];
if (value instanceof Error) {
Object.defineProperty(result, i, {
get: function () { throw wrapAccessError("index " + i, value); }
});
}
};
// Make all error indexed values throw on access
for (var i = 0; i < result.length; i++) {
_loop_1(i);
}
return Object.freeze(result);
};
// Given a transaction, find the matching function fragment (if any) and
// determine all its properties and call parameters
Interface.prototype.parseTransaction = function (tx) {
var fragment = this.getFunction(tx.data.substring(0, 10).toLowerCase());
if (!fragment) {
return null;
}
return new TransactionDescription({
args: this._abiCoder.decode(fragment.inputs, "0x" + tx.data.substring(10)),
functionFragment: fragment,
name: fragment.name,
signature: fragment.format(),
sighash: this.getSighash(fragment),
value: bignumber_1.BigNumber.from(tx.value || "0"),
});
};
// @TODO
//parseCallResult(data: BytesLike): ??
// Given an event log, find the matching event fragment (if any) and
// determine all its properties and values
Interface.prototype.parseLog = function (log) {
var fragment = this.getEvent(log.topics[0]);
if (!fragment || fragment.anonymous) {
return null;
}
// @TODO: If anonymous, and the only method, and the input count matches, should we parse?
// Probably not, because just because it is the only event in the ABI does
// not mean we have the full ABI; maybe jsut a fragment?
return new LogDescription({
eventFragment: fragment,
name: fragment.name,
signature: fragment.format(),
topic: this.getEventTopic(fragment),
args: this.decodeEventLog(fragment, log.data, log.topics)
});
};
Interface.prototype.parseError = function (data) {
var hexData = bytes_1.hexlify(data);
var fragment = this.getError(hexData.substring(0, 10).toLowerCase());
if (!fragment) {
return null;
}
return new ErrorDescription({
args: this._abiCoder.decode(fragment.inputs, "0x" + hexData.substring(10)),
errorFragment: fragment,
name: fragment.name,
signature: fragment.format(),
sighash: this.getSighash(fragment),
});
};
/*
static from(value: Array<Fragment | string | JsonAbi> | string | Interface) {
if (Interface.isInterface(value)) {
return value;
}
if (typeof(value) === "string") {
return new Interface(JSON.parse(value));
}
return new Interface(value);
}
*/
Interface.isInterface = function (value) {
return !!(value && value._isInterface);
};
return Interface;
}());
exports.Interface = Interface;
//# sourceMappingURL=interface.js.map