ethers.js/utils/abi-coder.js

962 lines
33 KiB
JavaScript
Raw Permalink Normal View History

2018-06-13 15:39:39 -04: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
2018-09-24 16:07:14 -04:00
var constants_1 = require("../constants");
var errors = __importStar(require("../errors"));
2018-06-13 15:39:39 -04:00
var address_1 = require("./address");
var bignumber_1 = require("./bignumber");
2018-06-17 16:47:28 -04:00
var bytes_1 = require("./bytes");
2018-06-13 15:39:39 -04:00
var utf8_1 = require("./utf8");
2018-06-15 04:18:17 -04:00
var properties_1 = require("./properties");
///////////////////////////////
2018-06-13 15:39:39 -04:00
var paramTypeBytes = new RegExp(/^bytes([0-9]*)$/);
var paramTypeNumber = new RegExp(/^(u?int)([0-9]*)$/);
var paramTypeArray = new RegExp(/^(.*)\[([0-9]*)\]$/);
2018-06-15 04:18:17 -04:00
exports.defaultCoerceFunc = function (type, value) {
2018-06-13 15:39:39 -04:00
var match = type.match(paramTypeNumber);
if (match && parseInt(match[2]) <= 48) {
return value.toNumber();
}
return value;
2018-06-15 04:18:17 -04:00
};
2018-06-13 15:39:39 -04:00
///////////////////////////////////
// Parsing for Solidity Signatures
var regexParen = new RegExp("^([^)(]*)\\((.*)\\)([^)(]*)$");
var regexIdentifier = new RegExp("^[A-Za-z_][A-Za-z0-9_]*$");
function verifyType(type) {
// These need to be transformed to their full description
if (type.match(/^uint($|[^1-9])/)) {
type = 'uint256' + type.substring(4);
}
else if (type.match(/^int($|[^1-9])/)) {
type = 'int256' + type.substring(3);
}
return type;
}
function parseParam(param, allowIndexed) {
2018-12-04 17:16:42 -05:00
var originalParam = param;
2018-06-13 15:39:39 -04:00
function throwError(i) {
2018-12-04 17:16:42 -05:00
throw new Error('unexpected character "' + originalParam[i] + '" at position ' + i + ' in "' + originalParam + '"');
2018-06-13 15:39:39 -04:00
}
2018-12-04 17:16:42 -05:00
param = param.replace(/\s/g, ' ');
2018-06-13 15:39:39 -04:00
var parent = { type: '', name: '', state: { allowType: true } };
var node = parent;
for (var i = 0; i < param.length; i++) {
var c = param[i];
switch (c) {
case '(':
if (!node.state.allowParams) {
throwError(i);
}
node.state.allowType = false;
node.type = verifyType(node.type);
node.components = [{ type: '', name: '', parent: node, state: { allowType: true } }];
node = node.components[0];
break;
case ')':
delete node.state;
if (allowIndexed && node.name === 'indexed') {
node.indexed = true;
node.name = '';
}
2018-06-13 15:39:39 -04:00
node.type = verifyType(node.type);
var child = node;
node = node.parent;
if (!node) {
throwError(i);
}
delete child.parent;
node.state.allowParams = false;
node.state.allowName = true;
node.state.allowArray = true;
break;
case ',':
delete node.state;
if (allowIndexed && node.name === 'indexed') {
node.indexed = true;
node.name = '';
}
2018-06-13 15:39:39 -04:00
node.type = verifyType(node.type);
var sibling = { type: '', name: '', parent: node.parent, state: { allowType: true } };
node.parent.components.push(sibling);
delete node.parent;
node = sibling;
break;
// Hit a space...
case ' ':
// If reading type, the type is done and may read a param or name
if (node.state.allowType) {
if (node.type !== '') {
node.type = verifyType(node.type);
delete node.state.allowType;
node.state.allowName = true;
node.state.allowParams = true;
}
}
// If reading name, the name is done
if (node.state.allowName) {
if (node.name !== '') {
if (allowIndexed && node.name === 'indexed') {
node.indexed = true;
node.name = '';
}
else {
node.state.allowName = false;
}
}
}
break;
case '[':
if (!node.state.allowArray) {
throwError(i);
}
node.type += c;
node.state.allowArray = false;
node.state.allowName = false;
node.state.readArray = true;
break;
case ']':
if (!node.state.readArray) {
throwError(i);
}
node.type += c;
node.state.readArray = false;
node.state.allowArray = true;
node.state.allowName = true;
break;
default:
if (node.state.allowType) {
node.type += c;
node.state.allowParams = true;
node.state.allowArray = true;
}
else if (node.state.allowName) {
node.name += c;
delete node.state.allowArray;
}
else if (node.state.readArray) {
node.type += c;
}
else {
throwError(i);
}
}
}
if (node.parent) {
throw new Error("unexpected eof");
}
delete parent.state;
if (allowIndexed && node.name === 'indexed') {
node.indexed = true;
node.name = '';
}
2018-06-13 15:39:39 -04:00
parent.type = verifyType(parent.type);
return parent;
}
// @TODO: Better return type
function parseSignatureEvent(fragment) {
var abi = {
anonymous: false,
inputs: [],
name: '',
type: 'event'
};
var match = fragment.match(regexParen);
if (!match) {
throw new Error('invalid event: ' + fragment);
}
abi.name = match[1].trim();
splitNesting(match[2]).forEach(function (param) {
param = parseParam(param, true);
param.indexed = !!param.indexed;
abi.inputs.push(param);
});
match[3].split(' ').forEach(function (modifier) {
switch (modifier) {
case 'anonymous':
abi.anonymous = true;
break;
case '':
break;
default:
2018-12-27 15:53:00 -05:00
errors.info('unknown modifier: ' + modifier);
2018-06-13 15:39:39 -04:00
}
});
if (abi.name && !abi.name.match(regexIdentifier)) {
throw new Error('invalid identifier: "' + abi.name + '"');
}
return abi;
}
function parseSignatureFunction(fragment) {
var abi = {
constant: false,
2018-08-01 18:05:19 -04:00
gas: null,
2018-06-13 15:39:39 -04:00
inputs: [],
name: '',
outputs: [],
payable: false,
stateMutability: null,
type: 'function'
};
2018-08-01 18:05:19 -04:00
var comps = fragment.split('@');
if (comps.length !== 1) {
if (comps.length > 2) {
throw new Error('invalid signature');
}
if (!comps[1].match(/^[0-9]+$/)) {
throw new Error('invalid signature gas');
}
abi.gas = bignumber_1.bigNumberify(comps[1]);
fragment = comps[0];
}
comps = fragment.split(' returns ');
2018-06-13 15:39:39 -04:00
var left = comps[0].match(regexParen);
if (!left) {
throw new Error('invalid signature');
}
abi.name = left[1].trim();
if (!abi.name.match(regexIdentifier)) {
throw new Error('invalid identifier: "' + left[1] + '"');
}
splitNesting(left[2]).forEach(function (param) {
abi.inputs.push(parseParam(param));
});
left[3].split(' ').forEach(function (modifier) {
switch (modifier) {
case 'constant':
abi.constant = true;
break;
case 'payable':
abi.payable = true;
2018-07-26 17:02:32 -04:00
abi.stateMutability = 'payable';
2018-06-13 15:39:39 -04:00
break;
case 'pure':
abi.constant = true;
abi.stateMutability = 'pure';
break;
case 'view':
abi.constant = true;
abi.stateMutability = 'view';
break;
2018-08-27 13:42:26 +02:00
case 'external':
2018-07-26 17:02:32 -04:00
case 'public':
2018-06-13 15:39:39 -04:00
case '':
break;
default:
2018-12-27 15:53:00 -05:00
errors.info('unknown modifier: ' + modifier);
2018-06-13 15:39:39 -04:00
}
});
// We have outputs
if (comps.length > 1) {
var right = comps[1].match(regexParen);
if (right[1].trim() != '' || right[3].trim() != '') {
throw new Error('unexpected tokens');
}
splitNesting(right[2]).forEach(function (param) {
abi.outputs.push(parseParam(param));
});
}
2018-09-24 16:07:14 -04:00
if (abi.name === 'constructor') {
abi.type = "constructor";
if (abi.outputs.length) {
throw new Error('constructor may not have outputs');
}
delete abi.name;
delete abi.outputs;
}
2018-06-13 15:39:39 -04:00
return abi;
}
function parseParamType(type) {
return parseParam(type, true);
}
exports.parseParamType = parseParamType;
// @TODO: Allow a second boolean to expose names
function formatParamType(paramType) {
return getParamCoder(exports.defaultCoerceFunc, paramType).type;
}
exports.formatParamType = formatParamType;
// @TODO: Allow a second boolean to expose names and modifiers
function formatSignature(fragment) {
return fragment.name + '(' + fragment.inputs.map(function (i) { return formatParamType(i); }).join(',') + ')';
}
exports.formatSignature = formatSignature;
2018-06-13 15:39:39 -04:00
function parseSignature(fragment) {
if (typeof (fragment) === 'string') {
// Make sure the "returns" is surrounded by a space and all whitespace is exactly one space
2018-12-04 17:16:42 -05:00
fragment = fragment.replace(/\s/g, ' ');
2018-06-13 15:39:39 -04:00
fragment = fragment.replace(/\(/g, ' (').replace(/\)/g, ') ').replace(/\s+/g, ' ');
fragment = fragment.trim();
if (fragment.substring(0, 6) === 'event ') {
return parseSignatureEvent(fragment.substring(6).trim());
}
else {
if (fragment.substring(0, 9) === 'function ') {
fragment = fragment.substring(9);
}
return parseSignatureFunction(fragment.trim());
}
}
2018-06-15 04:18:17 -04:00
throw new Error('unknown signature');
2018-06-13 15:39:39 -04:00
}
exports.parseSignature = parseSignature;
var Coder = /** @class */ (function () {
function Coder(coerceFunc, name, type, localName, dynamic) {
this.coerceFunc = coerceFunc;
this.name = name;
this.type = type;
this.localName = localName;
this.dynamic = dynamic;
}
return Coder;
}());
// Clones the functionality of an existing Coder, but without a localName
var CoderAnonymous = /** @class */ (function (_super) {
__extends(CoderAnonymous, _super);
function CoderAnonymous(coder) {
var _this = _super.call(this, coder.coerceFunc, coder.name, coder.type, undefined, coder.dynamic) || this;
properties_1.defineReadOnly(_this, 'coder', coder);
return _this;
}
CoderAnonymous.prototype.encode = function (value) { return this.coder.encode(value); };
CoderAnonymous.prototype.decode = function (data, offset) { return this.coder.decode(data, offset); };
return CoderAnonymous;
}(Coder));
2018-06-13 15:39:39 -04:00
var CoderNull = /** @class */ (function (_super) {
__extends(CoderNull, _super);
function CoderNull(coerceFunc, localName) {
return _super.call(this, coerceFunc, 'null', '', localName, false) || this;
}
CoderNull.prototype.encode = function (value) {
2018-06-17 16:47:28 -04:00
return bytes_1.arrayify([]);
2018-06-13 15:39:39 -04:00
};
CoderNull.prototype.decode = function (data, offset) {
if (offset > data.length) {
throw new Error('invalid null');
}
return {
consumed: 0,
value: this.coerceFunc('null', undefined)
};
};
return CoderNull;
}(Coder));
var CoderNumber = /** @class */ (function (_super) {
__extends(CoderNumber, _super);
function CoderNumber(coerceFunc, size, signed, localName) {
var _this = this;
var name = ((signed ? 'int' : 'uint') + (size * 8));
_this = _super.call(this, coerceFunc, name, name, localName, false) || this;
_this.size = size;
_this.signed = signed;
return _this;
}
CoderNumber.prototype.encode = function (value) {
try {
var v = bignumber_1.bigNumberify(value);
2018-07-25 21:20:21 -04:00
if (this.signed) {
2018-08-02 21:35:39 -04:00
var bounds = constants_1.MaxUint256.maskn(this.size * 8 - 1);
2018-07-25 21:20:21 -04:00
if (v.gt(bounds)) {
throw new Error('out-of-bounds');
}
2018-08-02 21:35:39 -04:00
bounds = bounds.add(constants_1.One).mul(constants_1.NegativeOne);
2018-07-25 21:20:21 -04:00
if (v.lt(bounds)) {
throw new Error('out-of-bounds');
}
}
2018-08-02 21:35:39 -04:00
else if (v.lt(constants_1.Zero) || v.gt(constants_1.MaxUint256.maskn(this.size * 8))) {
2018-07-25 21:20:21 -04:00
throw new Error('out-of-bounds');
}
2018-06-13 15:39:39 -04:00
v = v.toTwos(this.size * 8).maskn(this.size * 8);
if (this.signed) {
v = v.fromTwos(this.size * 8).toTwos(256);
}
2018-06-17 16:47:28 -04:00
return bytes_1.padZeros(bytes_1.arrayify(v), 32);
2018-06-13 15:39:39 -04:00
}
catch (error) {
errors.throwError('invalid number value', errors.INVALID_ARGUMENT, {
arg: this.localName,
coderType: this.name,
2018-06-13 15:39:39 -04:00
value: value
});
}
return null;
};
CoderNumber.prototype.decode = function (data, offset) {
if (data.length < offset + 32) {
errors.throwError('insufficient data for ' + this.name + ' type', errors.INVALID_ARGUMENT, {
arg: this.localName,
coderType: this.name,
2018-06-17 16:47:28 -04:00
value: bytes_1.hexlify(data.slice(offset, offset + 32))
2018-06-13 15:39:39 -04:00
});
}
var junkLength = 32 - this.size;
var value = bignumber_1.bigNumberify(data.slice(offset + junkLength, offset + 32));
if (this.signed) {
value = value.fromTwos(this.size * 8);
}
else {
value = value.maskn(this.size * 8);
}
return {
consumed: 32,
value: this.coerceFunc(this.name, value),
};
};
return CoderNumber;
}(Coder));
var uint256Coder = new CoderNumber(function (type, value) { return value; }, 32, false, 'none');
var CoderBoolean = /** @class */ (function (_super) {
__extends(CoderBoolean, _super);
function CoderBoolean(coerceFunc, localName) {
return _super.call(this, coerceFunc, 'bool', 'bool', localName, false) || this;
}
CoderBoolean.prototype.encode = function (value) {
return uint256Coder.encode(!!value ? 1 : 0);
};
CoderBoolean.prototype.decode = function (data, offset) {
try {
var result = uint256Coder.decode(data, offset);
}
catch (error) {
if (error.reason === 'insufficient data for uint256 type') {
errors.throwError('insufficient data for boolean type', errors.INVALID_ARGUMENT, {
arg: this.localName,
coderType: 'boolean',
value: error.value
});
}
throw error;
}
return {
consumed: result.consumed,
value: this.coerceFunc('bool', !result.value.isZero())
};
};
return CoderBoolean;
}(Coder));
var CoderFixedBytes = /** @class */ (function (_super) {
__extends(CoderFixedBytes, _super);
function CoderFixedBytes(coerceFunc, length, localName) {
var _this = this;
var name = ('bytes' + length);
_this = _super.call(this, coerceFunc, name, name, localName, false) || this;
_this.length = length;
return _this;
}
CoderFixedBytes.prototype.encode = function (value) {
var result = new Uint8Array(32);
try {
var data = bytes_1.arrayify(value);
2018-07-25 21:20:21 -04:00
if (data.length !== this.length) {
throw new Error('incorrect data length');
2018-06-13 15:39:39 -04:00
}
result.set(data);
2018-06-13 15:39:39 -04:00
}
catch (error) {
errors.throwError('invalid ' + this.name + ' value', errors.INVALID_ARGUMENT, {
arg: this.localName,
coderType: this.name,
2018-06-13 15:39:39 -04:00
value: (error.value || value)
});
}
return result;
};
CoderFixedBytes.prototype.decode = function (data, offset) {
if (data.length < offset + 32) {
2019-06-10 01:59:51 -04:00
errors.throwError('insufficient data for ' + this.name + ' type', errors.INVALID_ARGUMENT, {
2018-06-13 15:39:39 -04:00
arg: this.localName,
coderType: this.name,
2018-06-17 16:47:28 -04:00
value: bytes_1.hexlify(data.slice(offset, offset + 32))
2018-06-13 15:39:39 -04:00
});
}
return {
consumed: 32,
2018-06-17 16:47:28 -04:00
value: this.coerceFunc(this.name, bytes_1.hexlify(data.slice(offset, offset + this.length)))
2018-06-13 15:39:39 -04:00
};
};
return CoderFixedBytes;
}(Coder));
var CoderAddress = /** @class */ (function (_super) {
__extends(CoderAddress, _super);
function CoderAddress(coerceFunc, localName) {
return _super.call(this, coerceFunc, 'address', 'address', localName, false) || this;
}
CoderAddress.prototype.encode = function (value) {
var result = new Uint8Array(32);
try {
2018-06-17 16:47:28 -04:00
result.set(bytes_1.arrayify(address_1.getAddress(value)), 12);
2018-06-13 15:39:39 -04:00
}
catch (error) {
errors.throwError('invalid address', errors.INVALID_ARGUMENT, {
arg: this.localName,
coderType: 'address',
2018-06-13 15:39:39 -04:00
value: value
});
}
return result;
};
CoderAddress.prototype.decode = function (data, offset) {
if (data.length < offset + 32) {
2020-04-21 23:17:53 -04:00
errors.throwError('insufficient data for address type', errors.INVALID_ARGUMENT, {
2018-06-13 15:39:39 -04:00
arg: this.localName,
coderType: 'address',
2018-06-17 16:47:28 -04:00
value: bytes_1.hexlify(data.slice(offset, offset + 32))
2018-06-13 15:39:39 -04:00
});
}
return {
consumed: 32,
2018-06-17 16:47:28 -04:00
value: this.coerceFunc('address', address_1.getAddress(bytes_1.hexlify(data.slice(offset + 12, offset + 32))))
2018-06-13 15:39:39 -04:00
};
};
return CoderAddress;
}(Coder));
function _encodeDynamicBytes(value) {
var dataLength = 32 * Math.ceil(value.length / 32);
2018-06-13 15:39:39 -04:00
var padding = new Uint8Array(dataLength - value.length);
2018-06-17 16:47:28 -04:00
return bytes_1.concat([
2018-06-13 15:39:39 -04:00
uint256Coder.encode(value.length),
value,
padding
]);
}
function _decodeDynamicBytes(data, offset, localName) {
if (data.length < offset + 32) {
errors.throwError('insufficient data for dynamicBytes length', errors.INVALID_ARGUMENT, {
arg: localName,
coderType: 'dynamicBytes',
2018-06-17 16:47:28 -04:00
value: bytes_1.hexlify(data.slice(offset, offset + 32))
2018-06-13 15:39:39 -04:00
});
}
var length = uint256Coder.decode(data, offset).value;
try {
length = length.toNumber();
}
catch (error) {
errors.throwError('dynamic bytes count too large', errors.INVALID_ARGUMENT, {
arg: localName,
coderType: 'dynamicBytes',
value: length.toString()
});
}
if (data.length < offset + 32 + length) {
errors.throwError('insufficient data for dynamicBytes type', errors.INVALID_ARGUMENT, {
arg: localName,
coderType: 'dynamicBytes',
2018-06-17 16:47:28 -04:00
value: bytes_1.hexlify(data.slice(offset, offset + 32 + length))
2018-06-13 15:39:39 -04:00
});
}
return {
consumed: 32 + 32 * Math.ceil(length / 32),
2018-06-13 15:39:39 -04:00
value: data.slice(offset + 32, offset + 32 + length),
};
}
var CoderDynamicBytes = /** @class */ (function (_super) {
__extends(CoderDynamicBytes, _super);
function CoderDynamicBytes(coerceFunc, localName) {
return _super.call(this, coerceFunc, 'bytes', 'bytes', localName, true) || this;
}
CoderDynamicBytes.prototype.encode = function (value) {
try {
2018-06-17 16:47:28 -04:00
return _encodeDynamicBytes(bytes_1.arrayify(value));
2018-06-13 15:39:39 -04:00
}
catch (error) {
errors.throwError('invalid bytes value', errors.INVALID_ARGUMENT, {
arg: this.localName,
coderType: 'bytes',
2018-06-13 15:39:39 -04:00
value: error.value
});
}
return null;
};
CoderDynamicBytes.prototype.decode = function (data, offset) {
var result = _decodeDynamicBytes(data, offset, this.localName);
2018-06-17 16:47:28 -04:00
result.value = this.coerceFunc('bytes', bytes_1.hexlify(result.value));
2018-06-13 15:39:39 -04:00
return result;
};
return CoderDynamicBytes;
}(Coder));
var CoderString = /** @class */ (function (_super) {
__extends(CoderString, _super);
function CoderString(coerceFunc, localName) {
return _super.call(this, coerceFunc, 'string', 'string', localName, true) || this;
}
CoderString.prototype.encode = function (value) {
if (typeof (value) !== 'string') {
errors.throwError('invalid string value', errors.INVALID_ARGUMENT, {
arg: this.localName,
coderType: 'string',
2018-06-13 15:39:39 -04:00
value: value
});
}
return _encodeDynamicBytes(utf8_1.toUtf8Bytes(value));
};
CoderString.prototype.decode = function (data, offset) {
var result = _decodeDynamicBytes(data, offset, this.localName);
result.value = this.coerceFunc('string', utf8_1.toUtf8String(result.value));
return result;
};
return CoderString;
}(Coder));
function alignSize(size) {
return 32 * Math.ceil(size / 32);
2018-06-13 15:39:39 -04:00
}
function pack(coders, values) {
if (Array.isArray(values)) {
// do nothing
}
else if (values && typeof (values) === 'object') {
var arrayValues = [];
coders.forEach(function (coder) {
arrayValues.push(values[coder.localName]);
});
values = arrayValues;
}
else {
errors.throwError('invalid tuple value', errors.INVALID_ARGUMENT, {
coderType: 'tuple',
value: values
});
}
if (coders.length !== values.length) {
errors.throwError('types/value length mismatch', errors.INVALID_ARGUMENT, {
coderType: 'tuple',
value: values
});
}
var parts = [];
coders.forEach(function (coder, index) {
parts.push({ dynamic: coder.dynamic, value: coder.encode(values[index]) });
});
var staticSize = 0, dynamicSize = 0;
parts.forEach(function (part) {
if (part.dynamic) {
staticSize += 32;
dynamicSize += alignSize(part.value.length);
}
else {
staticSize += alignSize(part.value.length);
}
});
var offset = 0, dynamicOffset = staticSize;
var data = new Uint8Array(staticSize + dynamicSize);
parts.forEach(function (part) {
if (part.dynamic) {
//uint256Coder.encode(dynamicOffset).copy(data, offset);
data.set(uint256Coder.encode(dynamicOffset), offset);
offset += 32;
//part.value.copy(data, dynamicOffset); @TODO
data.set(part.value, dynamicOffset);
dynamicOffset += alignSize(part.value.length);
}
else {
//part.value.copy(data, offset); @TODO
data.set(part.value, offset);
offset += alignSize(part.value.length);
}
});
return data;
}
function unpack(coders, data, offset) {
var baseOffset = offset;
var consumed = 0;
var value = [];
coders.forEach(function (coder) {
if (coder.dynamic) {
var dynamicOffset = uint256Coder.decode(data, offset);
var result = coder.decode(data, baseOffset + dynamicOffset.value.toNumber());
// The dynamic part is leap-frogged somewhere else; doesn't count towards size
result.consumed = dynamicOffset.consumed;
}
else {
var result = coder.decode(data, offset);
}
if (result.value != undefined) {
value.push(result.value);
}
offset += result.consumed;
consumed += result.consumed;
});
coders.forEach(function (coder, index) {
var name = coder.localName;
if (!name) {
return;
}
if (name === 'length') {
name = '_length';
}
if (value[name] != null) {
return;
}
value[name] = value[index];
});
return {
value: value,
consumed: consumed
};
}
var CoderArray = /** @class */ (function (_super) {
__extends(CoderArray, _super);
function CoderArray(coerceFunc, coder, length, localName) {
var _this = this;
var type = (coder.type + '[' + (length >= 0 ? length : '') + ']');
var dynamic = (length === -1 || coder.dynamic);
_this = _super.call(this, coerceFunc, 'array', type, localName, dynamic) || this;
_this.coder = coder;
_this.length = length;
return _this;
}
CoderArray.prototype.encode = function (value) {
if (!Array.isArray(value)) {
errors.throwError('expected array value', errors.INVALID_ARGUMENT, {
arg: this.localName,
coderType: 'array',
value: value
});
}
var count = this.length;
var result = new Uint8Array(0);
if (count === -1) {
count = value.length;
result = uint256Coder.encode(count);
}
2018-11-12 17:27:47 -05:00
errors.checkArgumentCount(count, value.length, ' in coder array' + (this.localName ? (" " + this.localName) : ""));
2018-06-13 15:39:39 -04:00
var coders = [];
for (var i = 0; i < value.length; i++) {
coders.push(this.coder);
}
2018-06-17 16:47:28 -04:00
return bytes_1.concat([result, pack(coders, value)]);
2018-06-13 15:39:39 -04:00
};
CoderArray.prototype.decode = function (data, offset) {
// @TODO:
//if (data.length < offset + length * 32) { throw new Error('invalid array'); }
var consumed = 0;
var count = this.length;
if (count === -1) {
try {
var decodedLength = uint256Coder.decode(data, offset);
}
catch (error) {
errors.throwError('insufficient data for dynamic array length', errors.INVALID_ARGUMENT, {
arg: this.localName,
coderType: 'array',
value: error.value
});
}
try {
count = decodedLength.value.toNumber();
}
catch (error) {
errors.throwError('array count too large', errors.INVALID_ARGUMENT, {
arg: this.localName,
coderType: 'array',
value: decodedLength.value.toString()
});
}
consumed += decodedLength.consumed;
offset += decodedLength.consumed;
}
var coders = [];
for (var i = 0; i < count; i++) {
coders.push(new CoderAnonymous(this.coder));
2018-06-13 15:39:39 -04:00
}
var result = unpack(coders, data, offset);
result.consumed += consumed;
result.value = this.coerceFunc(this.type, result.value);
return result;
};
return CoderArray;
}(Coder));
var CoderTuple = /** @class */ (function (_super) {
__extends(CoderTuple, _super);
function CoderTuple(coerceFunc, coders, localName) {
var _this = this;
var dynamic = false;
var types = [];
coders.forEach(function (coder) {
if (coder.dynamic) {
dynamic = true;
}
types.push(coder.type);
});
var type = ('tuple(' + types.join(',') + ')');
_this = _super.call(this, coerceFunc, 'tuple', type, localName, dynamic) || this;
_this.coders = coders;
return _this;
}
CoderTuple.prototype.encode = function (value) {
return pack(this.coders, value);
};
CoderTuple.prototype.decode = function (data, offset) {
var result = unpack(this.coders, data, offset);
result.value = this.coerceFunc(this.type, result.value);
return result;
};
return CoderTuple;
}(Coder));
/*
function getTypes(coders) {
var type = coderTuple(coders).type;
return type.substring(6, type.length - 1);
}
*/
function splitNesting(value) {
2018-08-04 19:40:06 -04:00
value = value.trim();
2018-06-13 15:39:39 -04:00
var result = [];
var accum = '';
var depth = 0;
for (var offset = 0; offset < value.length; offset++) {
var c = value[offset];
if (c === ',' && depth === 0) {
result.push(accum);
accum = '';
}
else {
accum += c;
if (c === '(') {
depth++;
}
else if (c === ')') {
depth--;
if (depth === -1) {
throw new Error('unbalanced parenthsis');
}
}
}
}
2018-08-04 19:40:06 -04:00
if (accum) {
result.push(accum);
}
2018-06-13 15:39:39 -04:00
return result;
}
// @TODO: Is there a way to return "class"?
2018-06-13 15:39:39 -04:00
var paramTypeSimple = {
address: CoderAddress,
bool: CoderBoolean,
string: CoderString,
bytes: CoderDynamicBytes,
};
function getTupleParamCoder(coerceFunc, components, localName) {
if (!components) {
components = [];
}
var coders = [];
components.forEach(function (component) {
coders.push(getParamCoder(coerceFunc, component));
});
return new CoderTuple(coerceFunc, coders, localName);
}
function getParamCoder(coerceFunc, param) {
var coder = paramTypeSimple[param.type];
if (coder) {
return new coder(coerceFunc, param.name);
}
var match = param.type.match(paramTypeNumber);
if (match) {
var size = parseInt(match[2] || "256");
if (size === 0 || size > 256 || (size % 8) !== 0) {
errors.throwError('invalid ' + match[1] + ' bit length', errors.INVALID_ARGUMENT, {
arg: 'param',
value: param
});
}
return new CoderNumber(coerceFunc, size / 8, (match[1] === 'int'), param.name);
}
var match = param.type.match(paramTypeBytes);
if (match) {
var size = parseInt(match[1]);
if (size === 0 || size > 32) {
errors.throwError('invalid bytes length', errors.INVALID_ARGUMENT, {
arg: 'param',
value: param
});
}
return new CoderFixedBytes(coerceFunc, size, param.name);
}
var match = param.type.match(paramTypeArray);
if (match) {
var size = parseInt(match[2] || "-1");
2018-08-21 13:13:52 +02:00
param = properties_1.shallowCopy(param);
2018-06-13 15:39:39 -04:00
param.type = match[1];
2018-08-21 13:13:52 +02:00
param = properties_1.deepCopy(param);
2018-06-13 15:39:39 -04:00
return new CoderArray(coerceFunc, getParamCoder(coerceFunc, param), size, param.name);
}
if (param.type.substring(0, 5) === 'tuple') {
return getTupleParamCoder(coerceFunc, param.components, param.name);
}
if (param.type === '') {
return new CoderNull(coerceFunc, param.name);
}
errors.throwError('invalid type', errors.INVALID_ARGUMENT, {
arg: 'type',
value: param.type
});
return null;
}
var AbiCoder = /** @class */ (function () {
function AbiCoder(coerceFunc) {
2018-06-15 04:18:17 -04:00
errors.checkNew(this, AbiCoder);
2018-06-13 15:39:39 -04:00
if (!coerceFunc) {
2018-06-15 04:18:17 -04:00
coerceFunc = exports.defaultCoerceFunc;
2018-06-13 15:39:39 -04:00
}
2018-06-15 04:18:17 -04:00
properties_1.defineReadOnly(this, 'coerceFunc', coerceFunc);
2018-06-13 15:39:39 -04:00
}
AbiCoder.prototype.encode = function (types, values) {
if (types.length !== values.length) {
errors.throwError('types/values length mismatch', errors.INVALID_ARGUMENT, {
count: { types: types.length, values: values.length },
value: { types: types, values: values }
});
}
var coders = [];
types.forEach(function (type) {
// Convert types to type objects
// - "uint foo" => { type: "uint", name: "foo" }
// - "tuple(uint, uint)" => { type: "tuple", components: [ { type: "uint" }, { type: "uint" }, ] }
2018-06-15 04:18:17 -04:00
var typeObject = null;
2018-06-13 15:39:39 -04:00
if (typeof (type) === 'string') {
2018-06-15 04:18:17 -04:00
typeObject = parseParam(type);
2018-06-13 15:39:39 -04:00
}
2018-06-15 04:18:17 -04:00
else {
typeObject = type;
2018-06-15 04:18:17 -04:00
}
coders.push(getParamCoder(this.coerceFunc, typeObject));
2018-06-13 15:39:39 -04:00
}, this);
2018-06-17 16:47:28 -04:00
return bytes_1.hexlify(new CoderTuple(this.coerceFunc, coders, '_').encode(values));
2018-06-13 15:39:39 -04:00
};
AbiCoder.prototype.decode = function (types, data) {
var coders = [];
types.forEach(function (type) {
// See encode for details
2018-06-15 04:18:17 -04:00
var typeObject = null;
2018-06-13 15:39:39 -04:00
if (typeof (type) === 'string') {
2018-06-15 04:18:17 -04:00
typeObject = parseParam(type);
}
else {
2018-08-21 13:13:52 +02:00
typeObject = properties_1.deepCopy(type);
2018-06-13 15:39:39 -04:00
}
2018-06-15 04:18:17 -04:00
coders.push(getParamCoder(this.coerceFunc, typeObject));
2018-06-13 15:39:39 -04:00
}, this);
2018-06-17 16:47:28 -04:00
return new CoderTuple(this.coerceFunc, coders, '_').decode(bytes_1.arrayify(data), 0).value;
2018-06-13 15:39:39 -04:00
};
return AbiCoder;
}());
exports.AbiCoder = AbiCoder;
exports.defaultAbiCoder = new AbiCoder();