ethers.js/contracts/interface.js

424 lines
16 KiB
JavaScript
Raw Normal View History

2017-02-24 23:10:28 +03:00
'use strict';
// See: https://github.com/ethereum/wiki/wiki/Ethereum-Contract-ABI
2017-02-24 23:10:28 +03:00
var utils = (function() {
2018-06-04 03:50:21 +03:00
var AbiCoder = require('../utils/abi-coder');
var convert = require('../utils/convert');
var properties = require('../utils/properties');
var utf8 = require('../utils/utf8');
2017-02-24 23:10:28 +03:00
return {
defineFrozen: properties.defineFrozen,
defineProperty: properties.defineProperty,
2017-02-24 23:10:28 +03:00
2018-06-04 03:50:21 +03:00
coder: AbiCoder.defaultCoder,
parseSignature: AbiCoder.parseSignature,
arrayify: convert.arrayify,
2017-02-24 23:10:28 +03:00
concat: convert.concat,
isHexString: convert.isHexString,
2017-02-24 23:10:28 +03:00
toUtf8Bytes: utf8.toUtf8Bytes,
keccak256: require('../utils/keccak256'),
2017-02-24 23:10:28 +03:00
};
})();
2018-04-17 04:42:17 +03:00
var errors = require('../utils/errors');
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
}
}
2017-02-24 23:10:28 +03:00
function populateDescription(object, items) {
for (var key in items) {
utils.defineProperty(object, key, items[key]);
}
return object;
}
/**
* - bytecode (optional; only for deploy)
* - type ("deploy")
*/
function DeployDescription() { }
2017-02-24 23:10:28 +03:00
/**
* - name
* - signature
* - sighash
* -
* -
* -
* -
* - type: ("call" | "transaction")
*/
function FunctionDescription() { }
/**
* - anonymous
* - name
* - signature
* - parse
* - topics
* - inputs
* - type ("event")
*/
2017-02-24 23:10:28 +03:00
function EventDescription() { }
function Indexed(value) {
utils.defineProperty(this, 'indexed', true);
utils.defineProperty(this, 'hash', value);
}
function Result() {}
2017-02-24 23:10:28 +03:00
function Interface(abi) {
if (!(this instanceof Interface)) { throw new Error('missing new'); }
if (typeof(abi) === 'string') {
try {
abi = JSON.parse(abi);
} catch (error) {
2018-04-17 04:42:17 +03:00
errors.throwError('could not parse ABI JSON', errors.INVALID_ARGUMENT, {
arg: 'abi',
errorMessage: error.message,
value: abi
});
}
}
2018-06-04 03:50:21 +03:00
var _abi = [];
abi.forEach(function(fragment) {
if (typeof(fragment) === 'string') {
fragment = utils.parseSignature(fragment);
}
_abi.push(fragment);
});
utils.defineFrozen(this, 'abi', _abi);
2017-02-24 23:10:28 +03:00
var methods = {}, events = {}, deploy = null;
utils.defineProperty(this, 'functions', methods);
utils.defineProperty(this, 'events', events);
function addMethod(method) {
2017-02-24 23:10:28 +03:00
switch (method.type) {
case 'constructor':
var func = (function() {
var inputParams = parseParams(method.inputs);
var func = function(bytecode) {
if (!utils.isHexString(bytecode)) {
2018-04-17 04:42:17 +03:00
errors.throwError('invalid contract bytecode', errors.INVALID_ARGUMENT, {
arg: 'bytecode',
type: typeof(bytecode),
value: bytecode
});
}
var params = Array.prototype.slice.call(arguments, 1);
if (params.length < inputParams.types.length) {
2018-04-17 04:42:17 +03:00
errors.throwError('missing constructor argument', errors.MISSING_ARGUMENT, {
arg: (inputParams.names[params.length] || 'unknown'),
count: params.length,
expectedCount: inputParams.types.length
});
} else if (params.length > inputParams.types.length) {
2018-04-17 04:42:17 +03:00
errors.throwError('too many constructor arguments', errors.UNEXPECTED_ARGUMENT, {
count: params.length,
expectedCount: inputParams.types.length
});
}
try {
var encodedParams = utils.coder.encode(inputParams.names, inputParams.types, params)
} catch (error) {
errors.throwError('invalid constructor argument', errors.INVALID_ARGUMENT, {
arg: error.arg,
reason: error.reason,
value: error.value
});
}
var result = {
2018-04-17 04:42:17 +03:00
bytecode: bytecode + encodedParams.substring(2),
type: 'deploy'
}
return populateDescription(new DeployDescription(), result);
}
utils.defineFrozen(func, 'inputs', inputParams);
utils.defineProperty(func, 'payable', (method.payable == null || !!method.payable))
return func;
})();
if (!deploy) { deploy = func; }
break;
2017-02-24 23:10:28 +03:00
case 'function':
var func = (function() {
var inputParams = parseParams(method.inputs);
var outputParams = parseParams(method.outputs);
var signature = '(' + inputParams.types.join(',') + ')';
signature = signature.replace(/tuple/g, '');
signature = method.name + signature;
var parse = function(data) {
2018-04-17 04:42:17 +03:00
try {
return utils.coder.decode(
outputParams.names,
outputParams.types,
utils.arrayify(data)
);
} 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
});
}
};
var sighash = utils.keccak256(utils.toUtf8Bytes(signature)).substring(0, 10);
2017-02-24 23:10:28 +03:00
var func = function() {
var result = {
name: method.name,
signature: signature,
sighash: sighash,
type: ((method.constant) ? 'call': 'transaction')
2017-02-24 23:10:28 +03:00
};
var params = Array.prototype.slice.call(arguments, 0);
if (params.length < inputParams.types.length) {
2018-04-17 04:42:17 +03:00
errors.throwError('missing input argument', errors.MISSING_ARGUMENT, {
arg: (inputParams.names[params.length] || 'unknown'),
count: params.length,
expectedCount: inputParams.types.length,
name: method.name
});
} else if (params.length > inputParams.types.length) {
2018-04-17 04:42:17 +03:00
errors.throwError('too many input arguments', errors.UNEXPECTED_ARGUMENT, {
count: params.length,
expectedCount: inputParams.types.length
});
}
try {
var encodedParams = utils.coder.encode(inputParams.names, inputParams.types, params);
} catch (error) {
errors.throwError('invalid input argument', errors.INVALID_ARGUMENT, {
arg: error.arg,
reason: error.reason,
value: error.value
});
2017-02-24 23:10:28 +03:00
}
2018-04-17 04:42:17 +03:00
result.data = sighash + encodedParams.substring(2);
result.parse = parse;
2017-02-24 23:10:28 +03:00
return populateDescription(new FunctionDescription(), result);
2017-02-24 23:10:28 +03:00
}
utils.defineFrozen(func, 'inputs', inputParams);
utils.defineFrozen(func, 'outputs', outputParams);
utils.defineProperty(func, 'payable', (method.payable == null || !!method.payable))
utils.defineProperty(func, 'parseResult', parse);
utils.defineProperty(func, 'signature', signature);
utils.defineProperty(func, 'sighash', sighash);
2017-02-24 23:10:28 +03:00
return func;
})();
// Expose the first (and hopefully unique named function
if (method.name && methods[method.name] == null) {
utils.defineProperty(methods, method.name, func);
}
// Expose all methods by their signature, for overloaded functions
if (methods[func.signature] == null) {
utils.defineProperty(methods, func.signature, func);
}
2017-02-24 23:10:28 +03:00
break;
case 'event':
var func = (function() {
var inputParams = parseParams(method.inputs);
var signature = '(' + inputParams.types.join(',') + ')';
signature = signature.replace(/tuple/g, '');
signature = method.name + signature;
var result = {
anonymous: (!!method.anonymous),
name: method.name,
signature: signature,
type: 'event'
};
result.parse = function(topics, data) {
if (data == null) {
data = topics;
topics = null;
}
// Strip the signature off of non-anonymous topics
if (topics != null && !method.anonymous) { topics = topics.slice(1); }
var inputNamesIndexed = [], inputNamesNonIndexed = [];
var inputTypesIndexed = [], inputTypesNonIndexed = [];
var inputDynamic = [];
method.inputs.forEach(function(input, index) {
var type = inputParams.types[index];
var name = inputParams.names[index];
if (input.indexed) {
if (type === 'string' || type === 'bytes' || type.indexOf('[') >= 0 || type.substring(0, 5) === 'tuple') {
inputTypesIndexed.push('bytes32');
inputDynamic.push(true);
} else {
inputTypesIndexed.push(type);
inputDynamic.push(false);
}
inputNamesIndexed.push(name);
} else {
inputNamesNonIndexed.push(name);
inputTypesNonIndexed.push(type);
inputDynamic.push(false);
}
});
if (topics != null) {
var resultIndexed = utils.coder.decode(
inputNamesIndexed,
inputTypesIndexed,
utils.concat(topics)
2017-02-24 23:10:28 +03:00
);
}
var resultNonIndexed = utils.coder.decode(
inputNamesNonIndexed,
inputTypesNonIndexed,
utils.arrayify(data)
);
var result = new Result();
var nonIndexedIndex = 0, indexedIndex = 0;
method.inputs.forEach(function(input, index) {
if (input.indexed) {
if (topics == null) {
result[index] = new Indexed(null);
} else if (inputDynamic[index]) {
result[index] = new Indexed(resultIndexed[indexedIndex++]);
} else {
result[index] = resultIndexed[indexedIndex++];
}
} else {
result[index] = resultNonIndexed[nonIndexedIndex++];
}
if (input.name) { result[input.name] = result[index]; }
});
result.length = method.inputs.length;
return result;
};
var func = populateDescription(new EventDescription(), result)
utils.defineFrozen(func, 'topics', [ utils.keccak256(utils.toUtf8Bytes(signature)) ]);
utils.defineFrozen(func, 'inputs', inputParams);
2017-02-24 23:10:28 +03:00
return func;
})();
// Expose the first (and hopefully unique) event name
if (method.name && events[method.name] == null) {
utils.defineProperty(events, method.name, func);
}
// Expose all events by their signature, for overloaded functions
if (methods[func.signature] == null) {
utils.defineProperty(methods, func.signature, func);
}
2017-02-24 23:10:28 +03:00
break;
case 'fallback':
// Nothing to do for fallback
break;
2017-02-24 23:10:28 +03:00
default:
console.log('WARNING: unsupported ABI type - ' + method.type);
2017-02-24 23:10:28 +03:00
break;
}
};
2017-02-24 23:10:28 +03:00
2018-06-04 03:50:21 +03:00
_abi.forEach(addMethod, this);
2017-02-24 23:10:28 +03:00
// If there wasn't a constructor, create the default constructor
if (!deploy) {
addMethod({type: 'constructor', inputs: []});
}
utils.defineProperty(this, 'deployFunction', deploy);
2017-02-24 23:10:28 +03:00
}
utils.defineProperty(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 = utils.coder.decode(func.inputs.types, '0x' + tx.data.substring(10));
return {
args: result,
signature: func.signature,
sighash: func.sighash,
parse: func.parseResult,
value: tx.value,
};
}
}
return null;
});
2017-02-24 23:10:28 +03:00
module.exports = Interface;