ethers.js/contracts/interface.js

380 lines
15 KiB
JavaScript
Raw Normal View History

2018-06-13 22:39:39 +03:00
'use strict';
var __extends = (this && this.__extends) || (function () {
var extendStatics = Object.setPrototypeOf ||
({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; };
return function (d, b) {
extendStatics(d, b);
function __() { this.constructor = d; }
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
};
})();
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k];
result["default"] = mod;
return result;
};
Object.defineProperty(exports, "__esModule", { value: true });
// See: https://github.com/ethereum/wiki/wiki/Ethereum-Contract-ABI
var abi_coder_1 = require("../utils/abi-coder");
var bignumber_1 = require("../utils/bignumber");
2018-06-17 23:47:28 +03:00
var bytes_1 = require("../utils/bytes");
2018-06-13 22:39:39 +03:00
var keccak256_1 = require("../utils/keccak256");
var utf8_1 = require("../utils/utf8");
var properties_1 = require("../utils/properties");
var errors = __importStar(require("../utils/errors"));
// @TODO: Replace with a new abiCode.formatSignature method
2018-06-13 22:39:39 +03:00
function parseParams(params) {
var names = [];
var types = [];
params.forEach(function (param) {
if (param.components != null) {
if (param.type.substring(0, 5) !== 'tuple') {
throw new Error('internal error; report on GitHub');
}
var suffix = '';
var arrayBracket = param.type.indexOf('[');
if (arrayBracket >= 0) {
suffix = param.type.substring(arrayBracket);
}
var result = parseParams(param.components);
names.push({ name: (param.name || null), names: result.names });
types.push('tuple(' + result.types.join(',') + ')' + suffix);
}
else {
names.push(param.name || null);
types.push(param.type);
}
});
return {
names: names,
types: types
};
}
var Description = /** @class */ (function () {
function Description(info) {
for (var key in info) {
var value = info[key];
if (value != null && typeof (value) === 'object') {
properties_1.defineFrozen(this, key, info[key]);
}
else {
properties_1.defineReadOnly(this, key, info[key]);
}
}
}
return Description;
}());
exports.Description = Description;
// @TOOD: Make this a description
2018-06-18 12:42:41 +03:00
var Indexed = /** @class */ (function (_super) {
__extends(Indexed, _super);
function Indexed() {
return _super !== null && _super.apply(this, arguments) || this;
}
return Indexed;
2018-06-18 12:42:41 +03:00
}(Description));
exports.Indexed = Indexed;
2018-06-13 22:39:39 +03:00
var DeployDescription = /** @class */ (function (_super) {
__extends(DeployDescription, _super);
function DeployDescription() {
return _super !== null && _super.apply(this, arguments) || this;
}
DeployDescription.prototype.encode = function (bytecode, params) {
2018-06-17 23:47:28 +03:00
if (!bytes_1.isHexString(bytecode)) {
2018-06-13 22:39:39 +03:00
errors.throwError('invalid contract bytecode', errors.INVALID_ARGUMENT, {
arg: 'bytecode',
value: bytecode
});
}
errors.checkArgumentCount(params.length, this.inputs.length, 'in Interface constructor');
2018-06-13 22:39:39 +03:00
try {
return (bytecode + abi_coder_1.defaultAbiCoder.encode(this.inputs, params).substring(2));
}
catch (error) {
errors.throwError('invalid constructor argument', errors.INVALID_ARGUMENT, {
arg: error.arg,
reason: error.reason,
value: error.value
});
}
return null;
};
return DeployDescription;
}(Description));
exports.DeployDescription = DeployDescription;
var FunctionDescription = /** @class */ (function (_super) {
__extends(FunctionDescription, _super);
function FunctionDescription() {
return _super !== null && _super.apply(this, arguments) || this;
}
FunctionDescription.prototype.encode = function (params) {
errors.checkArgumentCount(params.length, this.inputs.length, 'in interface function ' + this.name);
2018-06-13 22:39:39 +03:00
try {
return this.sighash + abi_coder_1.defaultAbiCoder.encode(this.inputs, params).substring(2);
}
catch (error) {
errors.throwError('invalid input argument', errors.INVALID_ARGUMENT, {
arg: error.arg,
reason: error.reason,
value: error.value
});
}
return null;
};
FunctionDescription.prototype.decode = function (data) {
try {
2018-06-17 23:47:28 +03:00
return abi_coder_1.defaultAbiCoder.decode(this.outputs, bytes_1.arrayify(data));
2018-06-13 22:39:39 +03:00
}
catch (error) {
errors.throwError('invalid data for function output', errors.INVALID_ARGUMENT, {
arg: 'data',
errorArg: error.arg,
errorValue: error.value,
value: data,
reason: error.reason
});
}
};
return FunctionDescription;
}(Description));
exports.FunctionDescription = FunctionDescription;
2018-06-18 12:42:41 +03:00
var Result = /** @class */ (function (_super) {
__extends(Result, _super);
function Result() {
return _super !== null && _super.apply(this, arguments) || this;
}
return Result;
}(Description));
2018-06-13 22:39:39 +03:00
var EventDescription = /** @class */ (function (_super) {
__extends(EventDescription, _super);
function EventDescription() {
return _super !== null && _super.apply(this, arguments) || this;
}
EventDescription.prototype.decode = function (data, topics) {
// Strip the signature off of non-anonymous topics
if (topics != null && !this.anonymous) {
topics = topics.slice(1);
}
var inputIndexed = [];
var inputNonIndexed = [];
2018-06-13 22:39:39 +03:00
var inputDynamic = [];
this.inputs.forEach(function (param, index) {
if (param.indexed) {
if (param.type === 'string' || param.type === 'bytes' || param.type.indexOf('[') >= 0 || param.type.substring(0, 5) === 'tuple') {
inputIndexed.push({ type: 'bytes32', name: (param.name || '') });
inputDynamic.push(true);
}
else {
inputIndexed.push(param);
inputDynamic.push(false);
}
}
else {
inputNonIndexed.push(param);
inputDynamic.push(false);
}
});
if (topics != null) {
2018-06-17 23:47:28 +03:00
var resultIndexed = abi_coder_1.defaultAbiCoder.decode(inputIndexed, bytes_1.concat(topics));
2018-06-13 22:39:39 +03:00
}
2018-06-17 23:47:28 +03:00
var resultNonIndexed = abi_coder_1.defaultAbiCoder.decode(inputNonIndexed, bytes_1.arrayify(data));
2018-06-18 12:42:41 +03:00
var result = new Result({});
2018-06-13 22:39:39 +03:00
var nonIndexedIndex = 0, indexedIndex = 0;
this.inputs.forEach(function (input, index) {
if (input.indexed) {
if (topics == null) {
2018-06-18 12:42:41 +03:00
result[index] = new Indexed({ type: 'indexed', hash: null });
2018-06-13 22:39:39 +03:00
}
else if (inputDynamic[index]) {
2018-06-18 12:42:41 +03:00
result[index] = new Indexed({ type: 'indexed', hash: resultIndexed[indexedIndex++] });
2018-06-13 22:39:39 +03:00
}
else {
result[index] = resultIndexed[indexedIndex++];
}
}
else {
result[index] = resultNonIndexed[nonIndexedIndex++];
}
if (input.name) {
result[input.name] = result[index];
}
});
result.length = this.inputs.length;
return result;
};
return EventDescription;
}(Description));
exports.EventDescription = EventDescription;
2018-06-18 12:42:41 +03:00
var TransactionDescription = /** @class */ (function (_super) {
__extends(TransactionDescription, _super);
function TransactionDescription() {
return _super !== null && _super.apply(this, arguments) || this;
}
return TransactionDescription;
}(Description));
var LogDescription = /** @class */ (function (_super) {
__extends(LogDescription, _super);
function LogDescription() {
return _super !== null && _super.apply(this, arguments) || this;
}
return LogDescription;
}(Description));
2018-06-13 22:39:39 +03:00
function addMethod(method) {
switch (method.type) {
case 'constructor': {
var description = new DeployDescription({
inputs: method.inputs,
payable: (method.payable == null || !!method.payable),
type: 'deploy'
});
if (!this.deployFunction) {
this.deployFunction = description;
}
break;
}
case 'function': {
// @TODO: See event
var signature = '(' + parseParams(method.inputs).types.join(',') + ')';
signature = signature.replace(/tuple/g, '');
signature = method.name + signature;
var sighash = keccak256_1.keccak256(utf8_1.toUtf8Bytes(signature)).substring(0, 10);
var description = new FunctionDescription({
inputs: method.inputs,
outputs: method.outputs,
payable: (method.payable == null || !!method.payable),
type: ((method.constant) ? 'call' : 'transaction'),
signature: signature,
sighash: sighash,
});
// Expose the first (and hopefully unique named function
if (method.name && this.functions[method.name] == null) {
properties_1.defineReadOnly(this.functions, method.name, description);
}
// Expose all methods by their signature, for overloaded functions
if (this.functions[description.signature] == null) {
properties_1.defineReadOnly(this.functions, description.signature, description);
}
break;
}
case 'event': {
// @TODO: method.params instead? As well? Different fomrat?
//let inputParams = parseParams(method.inputs);
// @TODO: Don't use parseParams (create new function in ABI, formatSignature)
var signature = '(' + parseParams(method.inputs).types.join(',') + ')';
signature = signature.replace(/tuple/g, '');
signature = method.name + signature;
var description = new EventDescription({
name: method.name,
signature: signature,
inputs: method.inputs,
2018-06-18 12:42:41 +03:00
topic: keccak256_1.keccak256(utf8_1.toUtf8Bytes(signature)),
2018-06-13 22:39:39 +03:00
anonymous: (!!method.anonymous),
type: 'event'
});
// Expose the first (and hopefully unique) event name
if (method.name && this.events[method.name] == null) {
properties_1.defineReadOnly(this.events, method.name, description);
}
// Expose all events by their signature, for overloaded functions
if (this.events[description.signature] == null) {
properties_1.defineReadOnly(this.events, description.signature, description);
}
break;
}
case 'fallback':
// Nothing to do for fallback
break;
default:
console.log('WARNING: unsupported ABI type - ' + method.type);
break;
}
}
var Interface = /** @class */ (function () {
function Interface(abi) {
errors.checkNew(this, Interface);
2018-06-13 22:39:39 +03:00
if (typeof (abi) === 'string') {
try {
abi = JSON.parse(abi);
}
catch (error) {
errors.throwError('could not parse ABI JSON', errors.INVALID_ARGUMENT, {
arg: 'abi',
errorMessage: error.message,
value: abi
});
}
if (!Array.isArray(abi)) {
errors.throwError('invalid abi', errors.INVALID_ARGUMENT, { arg: 'abi', value: abi });
return null;
}
}
properties_1.defineReadOnly(this, 'functions', {});
properties_1.defineReadOnly(this, 'events', {});
// Convert any supported ABI format into a standard ABI format
2018-06-15 11:18:17 +03:00
var _abi = [];
2018-06-13 22:39:39 +03:00
abi.forEach(function (fragment) {
if (typeof (fragment) === 'string') {
fragment = abi_coder_1.parseSignature(fragment);
}
// @TODO: We should probable do some validation; create abiCoder.formatSignature for checking
2018-06-15 11:18:17 +03:00
_abi.push(fragment);
2018-06-13 22:39:39 +03:00
});
2018-06-15 11:18:17 +03:00
properties_1.defineFrozen(this, 'abi', _abi);
_abi.forEach(addMethod, this);
2018-06-13 22:39:39 +03:00
// If there wasn't a constructor, create the default constructor
if (!this.deployFunction) {
addMethod.call(this, { type: 'constructor', inputs: [] });
}
}
Interface.prototype.parseTransaction = function (tx) {
var sighash = tx.data.substring(0, 10).toLowerCase();
for (var name in this.functions) {
if (name.indexOf('(') === -1) {
continue;
}
var func = this.functions[name];
if (func.sighash === sighash) {
var result = abi_coder_1.defaultAbiCoder.decode(func.inputs, '0x' + tx.data.substring(10));
2018-06-18 12:42:41 +03:00
return new TransactionDescription({
2018-06-13 22:39:39 +03:00
args: result,
2018-06-18 12:42:41 +03:00
decode: func.decode,
name: name,
2018-06-13 22:39:39 +03:00
signature: func.signature,
sighash: func.sighash,
2018-06-18 12:42:41 +03:00
type: 'transaction',
2018-06-13 22:39:39 +03:00
value: bignumber_1.bigNumberify(tx.value || 0),
2018-06-18 12:42:41 +03:00
});
2018-06-13 22:39:39 +03:00
}
}
return null;
};
2018-06-18 12:42:41 +03:00
Interface.prototype.parseLog = function (log) {
for (var name in this.events) {
if (name.indexOf('(') === -1) {
continue;
}
var event = this.events[name];
if (event.anonymous) {
continue;
}
if (event.topic !== log.topics[0]) {
continue;
}
// @TODO: If anonymous, and the only method, and the input count matches, should we parse and return it?
return new LogDescription({
name: event.name,
signature: event.signature,
topic: event.topic,
type: 'log',
values: event.decode(log.data, log.topics)
});
}
return null;
};
2018-06-13 22:39:39 +03:00
return Interface;
}());
exports.Interface = Interface;