Added ABI v2 coder to Interface (still experimental in Solidity though).

This commit is contained in:
Richard Moore 2017-11-06 19:35:18 -05:00
parent 54c19dfb5a
commit fe3ac0e94d
No known key found for this signature in database
GPG Key ID: 525F70A6FCABC295
5 changed files with 947 additions and 284 deletions

View File

@ -58,8 +58,27 @@ function getKeys(params, key, allowEmpty) {
return result;
}
var coderNull = {
name: 'null',
type: '',
encode: function(value) {
return utils.arrayify([]);
},
decode: function(data, offset) {
if (offset > data.length) { throw new Error('invalid null'); }
return {
consumed: 0,
value: undefined
}
},
dynamic: false
};
function coderNumber(size, signed) {
var name = ((signed ? 'int': 'uint') + size);
return {
name: name,
type: name,
encode: function(value) {
value = utils.bigNumberify(value).toTwos(size * 8).maskn(size * 8);
//value = value.toTwos(size * 8).maskn(size * 8);
@ -89,6 +108,8 @@ function coderNumber(size, signed) {
var uint256Coder = coderNumber(32, false);
var coderBoolean = {
name: 'boolean',
type: 'boolean',
encode: function(value) {
return uint256Coder.encode(value ? 1: 0);
},
@ -102,7 +123,10 @@ var coderBoolean = {
}
function coderFixedBytes(length) {
var name = ('bytes' + length);
return {
name: name,
type: name,
encode: function(value) {
value = utils.arrayify(value);
if (length === 32) { return value; }
@ -123,6 +147,8 @@ function coderFixedBytes(length) {
}
var coderAddress = {
name: 'address',
type: 'address',
encode: function(value) {
value = utils.arrayify(utils.getAddress(value));
var result = new Uint8Array(32);
@ -163,6 +189,8 @@ function _decodeDynamicBytes(data, offset) {
}
var coderDynamicBytes = {
name: 'bytes',
type: 'bytes',
encode: function(value) {
return _encodeDynamicBytes(utils.arrayify(value));
},
@ -175,6 +203,8 @@ var coderDynamicBytes = {
};
var coderString = {
name: 'string',
type: 'string',
encode: function(value) {
return _encodeDynamicBytes(utils.toUtf8Bytes(value));
},
@ -186,24 +216,106 @@ var coderString = {
dynamic: true
};
function coderArray(coder, length) {
function alignSize(size) {
return parseInt(32 * Math.ceil(size / 32));
}
function pack(coders, 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, index) {
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, index) {
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;
});
return {
value: value,
consumed: consumed
}
return result;
}
function coderArray(coder, length) {
var type = (coder.type + '[' + (length >= 0 ? length: '') + ']');
return {
coder: coder,
length: length,
name: 'array',
type: type,
encode: function(value) {
if (!Array.isArray(value)) { throwError('invalid array'); }
var count = length;
var result = new Uint8Array(0);
if (length === -1) {
length = value.length;
result = uint256Coder.encode(length);
if (count === -1) {
count = value.length;
result = uint256Coder.encode(count);
}
if (length !== value.length) { throwError('size mismatch'); }
if (count !== value.length) { throwError('size mismatch'); }
value.forEach(function(value) {
result = utils.concat([result, coder.encode(value)]);
});
var coders = [];
value.forEach(function(value) { coders.push(coder); });
return result;
return utils.concat([result, pack(coders, value)]);
},
decode: function(data, offset) {
// @TODO:
@ -211,98 +323,142 @@ function coderArray(coder, length) {
var consumed = 0;
var result;
if (length === -1) {
result = uint256Coder.decode(data, offset);
length = result.value.toNumber();
consumed += result.consumed;
offset += result.consumed;
var count = length;
if (count === -1) {
var decodedLength = uint256Coder.decode(data, offset);
count = decodedLength.value.toNumber();
consumed += decodedLength.consumed;
offset += decodedLength.consumed;
}
var value = [];
var coders = [];
for (var i = 0; i < count; i++) { coders.push(coder); }
for (var i = 0; i < length; i++) {
var result = coder.decode(data, offset);
consumed += result.consumed;
offset += result.consumed;
value.push(result.value);
}
return {
consumed: consumed,
value: value,
}
var result = unpack(coders, data, offset);
result.consumed += consumed;
return result;
},
dynamic: (length === -1)
dynamic: (length === -1 || coder.dynamic)
}
}
// Break the type up into [staticType][staticArray]*[dynamicArray]? | [dynamicType] and
// build the coder up from its parts
var paramTypePart = new RegExp(/^((u?int|bytes)([0-9]*)|(address|bool|string)|(\[([0-9]*)\]))/);
function getParamCoder(type) {
var coder = null;
while (type) {
var part = type.match(paramTypePart);
if (!part) { throwError('invalid type', { type: type }); }
type = type.substring(part[0].length);
var prefix = (part[2] || part[4] || part[5]);
switch (prefix) {
case 'int': case 'uint':
if (coder) { throwError('invalid type', { type: type }); }
var size = parseInt(part[3] || 256);
if (size === 0 || size > 256 || (size % 8) !== 0) {
throwError('invalid type', { type: type });
function coderTuple(coders) {
var dynamic = false;
var types = [];
coders.forEach(function(coder) {
if (coder.dynamic) { dynamic = true; }
types.push(coder.type);
});
var type = ('tuple(' + types.join(',') + ')');
return {
coders: coders,
name: 'tuple',
type: type,
encode: function(value) {
if (coders.length !== coders.length) {
throwError('types/values mismatch', { type: type, values: values });
}
return pack(coders, value);
},
decode: function(data, offset) {
return unpack(coders, data, offset);
},
dynamic: dynamic
};
}
function getTypes(coders) {
var type = coderTuple(coders).type;
return type.substring(6, type.length - 1);
}
function splitNesting(value) {
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');
}
coder = coderNumber(size / 8, (prefix === 'int'));
break;
case 'bool':
if (coder) { throwError('invalid type', { type: type }); }
coder = coderBoolean;
break;
case 'string':
if (coder) { throwError('invalid type', { type: type }); }
coder = coderString;
break;
case 'bytes':
if (coder) { throwError('invalid type', { type: type }); }
if (part[3]) {
var size = parseInt(part[3]);
if (size === 0 || size > 32) {
throwError('invalid type ' + type);
}
coder = coderFixedBytes(size);
} else {
coder = coderDynamicBytes;
}
break;
case 'address':
if (coder) { throwError('invalid type', { type: type }); }
coder = coderAddress;
break;
case '[]':
if (!coder || coder.dynamic) { throwError('invalid type', { type: type }); }
coder = coderArray(coder, -1);
break;
// "[0-9+]"
default:
if (!coder || coder.dynamic) { throwError('invalid type', { type: type }); }
var size = parseInt(part[6]);
coder = coderArray(coder, size);
}
}
}
result.push(accum);
if (!coder) { throwError('invalid type'); }
return coder;
return result;
}
var paramTypeBytes = new RegExp(/^bytes([0-9]*)$/);
var paramTypeNumber = new RegExp(/^(u?int)([0-9]*)$/);
var paramTypeArray = new RegExp(/^(.*)\[([0-9]*)\]$/);
var paramTypeSimple = {
address: coderAddress,
bool: coderBoolean,
string: coderString,
bytes: coderDynamicBytes,
};
function getParamCoder(type) {
var coder = paramTypeSimple[type];
if (coder) { return coder; }
var match = type.match(paramTypeNumber);
if (match) {
var size = parseInt(match[2] || 256);
if (size === 0 || size > 256 || (size % 8) !== 0) {
throwError('invalid type', { type: type });
}
return coderNumber(size / 8, (match[1] === 'int'));
}
var match = type.match(paramTypeBytes);
if (match) {
var size = parseInt(match[1]);
if (size === 0 || size > 32) {
throwError('invalid type ' + type);
}
return coderFixedBytes(size);
}
var match = type.match(paramTypeArray);
if (match) {
var size = parseInt(match[2] || -1);
return coderArray(getParamCoder(match[1]), size);
}
if (type.substring(0, 6) === 'tuple(' && type.substring(type.length - 1) === ')') {
var coders = [];
splitNesting(type.substring(6, type.length - 1)).forEach(function(type) {
coders.push(getParamCoder(type));
});
return coderTuple(coders);
}
if (type === '') {
return coderNull;
}
throwError('invalid type', { type: type });
}
function populateDescription(object, items) {
for (var key in items) {
utils.defineProperty(object, key, items[key]);
@ -525,46 +681,12 @@ function Interface(abi) {
utils.defineProperty(Interface, 'encodeParams', function(types, values) {
if (types.length !== values.length) { throwError('types/values mismatch', {types: types, values: values}); }
var parts = [];
types.forEach(function(type, index) {
var coder = getParamCoder(type);
parts.push({dynamic: coder.dynamic, value: coder.encode(values[index])});
})
function alignSize(size) {
return parseInt(32 * Math.ceil(size / 32));
}
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 coders = [];
types.forEach(function(type) {
coders.push(getParamCoder(type));
});
var offset = 0, dynamicOffset = staticSize;
var data = new Uint8Array(staticSize + dynamicSize);
parts.forEach(function(part, index) {
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 utils.hexlify(data);
return utils.hexlify(coderTuple(coders).encode(values));
});
@ -580,47 +702,35 @@ utils.defineProperty(Interface, 'decodeParams', function(names, types, data) {
}
data = utils.arrayify(data);
var coders = [];
types.forEach(function(type) {
coders.push(getParamCoder(type));
});
var result = coderTuple(coders).decode(data, 0);
// @TODO: Move this into coderTuple
var values = new Result();
var offset = 0;
types.forEach(function(type, index) {
var coder = getParamCoder(type);
if (coder.dynamic) {
var dynamicOffset = uint256Coder.decode(data, offset);
var result = coder.decode(data, dynamicOffset.value.toNumber());
offset += dynamicOffset.consumed;
} else {
var result = coder.decode(data, offset);
offset += result.consumed;
}
// Add indexed parameter
values[index] = result.value;
// Add named parameters
if (names[index]) {
coders.forEach(function(coder, index) {
values[index] = result.value[index];
if (names && names[index]) {
var name = names[index];
// We reserve length to make the Result object arrayish
if (name === 'length') {
console.log('WARNING: result length renamed to _length');
name = '_length';
}
if (values[name] == null) {
values[name] = result.value;
values[name] = values[index];
} else {
console.log('WARNING: duplicate value - ' + name);
}
}
});
})
values.length = types.length;
return values;
});
//utils.defineProperty(Interface, 'getDeployTransaction', function(bytecode) {
//});
module.exports = Interface;

View File

@ -1,6 +1,6 @@
{
"name": "ethers-contracts",
"version": "2.1.3",
"version": "2.1.4",
"description": "Contract and Interface (ABI) library for Ethereum.",
"bugs": {
"url": "http://github.com/ethers-io/ethers.js/issues",

View File

@ -1,19 +1,45 @@
'use strict';
// See: https://github.com/ethereum/wiki/wiki/Ethereum-Contract-ABI
var crypto = require('crypto');
var fs = require('fs');
var BN = require('bn.js');
var solc = require('solc');
var ethereumVm = require('ethereumjs-vm');
var ethereumUtil = require('ethereumjs-util');
var promiseRationing = require('promise-rationing');
var Web3 = require('web3');
var contracts = require('../../contracts/index.js');
var bigNumber = require('../../utils/bignumber.js');
var convert = require('../../utils/convert.js');
var getAddress = require('../../utils/address.js').getAddress;
var arrayify = require('../../utils/convert').arrayify;
var utils = require('./utils.js');
var utils = require('../utils.js');
function addLog(message) {
fs.appendFileSync('make-contract-interface.log', message + '\n');
}
function id(text) {
return crypto.createHash('sha256').update(text).digest().toString('hex').substring(0, 10).toUpperCase();
}
process.on('unhandledRejection', function(reason, p){
console.log('Error: Unhandled promise rejection');
console.log(reason);
});
var compile = (function() {
var soljson = require('../soljson.js');
var _compile = soljson.cwrap("compileJSONCallback", "string", ["string", "number", "number"]);
function compile(source) {
return JSON.parse(_compile(JSON.stringify({sources: { "demo.sol": source }}), 0));
}
compile.version = JSON.parse(compile('contract Foo { }').contracts['demo.sol:Foo'].metadata).compiler.version
return compile;
})();
// Create the indent given a tabstop
function indent(tabs) {
@ -23,6 +49,18 @@ function indent(tabs) {
}
function recursiveHexlify(object) {
if (object.type === 'tuple') {
var result = [];
object.value.forEach(function(object) {
result.push(recursiveHexlify(object));
});
return {type: 'tuple', value: result};
}
if (object.type && object.value != null) {
object = object.value;
}
if (typeof(object) === 'number') {
object = new BN(object);
}
@ -45,12 +83,13 @@ function recursiveHexlify(object) {
} else if (Buffer.isBuffer(object)) {
return {type: 'buffer', value: utils.hexlify(object)};
}
throw new Error('unsupported type - ' + object + ' ' + typeof(object));
}
var web3 = new Web3(new Web3.providers.HttpProvider('http://127.0.0.1:8545'));
var web3 = new Web3(new Web3.providers.HttpProvider('http://127.0.0.1:8549'));
/**
*
@ -62,108 +101,210 @@ function getValue(value) {
} else if (BN.isBN(value)) {
value = value.toString(10);
} else if (typeof(value) !== 'string' && typeof(value) !== 'number' && typeof(value) !== 'boolean') {
console.dir(value, { depth: null });
throw new Error('invalid type - ' + value + ' ' + typeof(value));
}
return value;
}
function createContractOutput(types, values) {
var source = 'contract Test {\n';
source += ' function test() constant returns (' + types.join(', ') + ') {\n';
var returns = [];
for (var i = 0; i < types.length; i++) {
var name = String.fromCharCode(97 + i);
function getName(depth) {
return String.fromCharCode(97 + depth);
}
function getStructName(types) {
return 'Struct' + id('struct(' + types.join(',') + ')');
}
function getStructSource(types) {
var source = '';
types.forEach(function(type, index) {
var name = getName(index);
source += indent(2) + type + ' ' + name + ';\n';
});
return (indent(1) + 'struct ' + getStructName(types) + ' {\n' + source + indent(1) + '}\n');
}
function populate(name, value, depth, info) {
value.localName = name;
if (value.type === 'tuple') {
info.pragmas['experimental ABIEncoderV2'] = true;
var source = '';
var types = [];
value.value.forEach(function(value, index) {
var localName = getName(index);
populate(name + '.' + localName, value, depth + 1, info);
types.push(value.name);
});
if (!value.struct) {
value.struct = getStructSource(types);
}
info.structs[value.struct] = true;
} else if (Array.isArray(value.value)) {
if (value.type.substring(value.type.length - 2) === '[]') {
info.inits.push(indent(2) + value.localName + ' = new ' + value.name + '(' + value.value.length + ');\n');
value.dynamic = true;
}
value.value.forEach(function(value, index) {
populate(name + '[' + String(index) + ']', value, depth + 1, info);
if (value.dynamic) {
info.pragmas['experimental ABIEncoderV2'] = true;
}
});
} else {
if (value.type === 'string' || value.type === 'bytes') {
value.dynamic = true;
}
}
}
function createContractSource(values, info, comments) {
var pragmas = { 'solidity ^0.4.18': true };
var _getName = -1;
var getName = function() {
_getName++;
return String.fromCharCode(97 + parseInt(_getName / 26)) + String.fromCharCode(97 + (_getName % 26));
}
var source = '';
var returnTypes = [];
values.forEach(function(value, index) {
returnTypes.push(value.name + ' ' + value.localName);
});
var temp = false;
function dumpValue(value) {
// Tuple
if (value.type === 'tuple') {
value.value.forEach(function(value) {
dumpValue(value);
});
// Array type; do a deep copy
if (types[i].indexOf('[') >= 0) {
// Each count (or optionally empty) array type
var arrays = types[i].match(/\[[0-9]*\]/g);
// Allocate the space (only dynamic arrays require new)
source += indent(2) + types[i] + ' memory ' + name;
if (arrays[arrays.length - 1] === '[]') {
source += ' = new ' + types[i] + '(' + values[i].length+ ')';
}
source +=';\n';
var baseType = types[i].substring(0, types[i].indexOf('['));
function recursiveSet(item, indices) {
if (Array.isArray(item)) {
item.forEach(function(item, index) {
var i = indices.slice();
i.unshift(index);
recursiveSet(item, i);
});
} else {
var loc = '';
indices.forEach(function(index) {
loc = '[' + index + ']' + loc;
})
item = getValue(item);
//if (item instanceof BN) { item = item.toString(10); }
source += indent(2) + name + loc + ' = ' + baseType + '(' + item + ');\n';
}
}
recursiveSet(values[i], []);
} else if (value.type.indexOf('[') >= 0) {
value.value.forEach(function(value) {
dumpValue(value);
});
// Dynamic type: bytes
} else if (types[i] === 'bytes') {
source += indent(2) + 'bytes memory ' + name + ' = new bytes(' + values[i].length + ');\n';
} else if (value.type === 'bytes') {
if (!temp) {
source += indent(2) + 'bytes memory temp ';
temp = true;
} else {
source += indent(2) + 'temp ';
}
source += '= new bytes(' + value.value.length + ');\n';
source += indent(2) + value.localName + ' = temp;\n';
source += indent(2) + 'assembly {\n'
source += indent(3) + 'mstore(' + name + ', ' + values[i].length + ')\n';
for (var j = 0; j < values[i].length; j++) {
source += indent(3) + 'mstore8(add(' + name + ', ' + (32 + j) + '), ' + values[i][j] + ')\n';
source += indent(3) + 'mstore(temp, ' + value.value.length + ')\n';
for (var i = 0; i < value.value.length; i++) {
source += indent(3) + 'mstore8(add(temp, ' + (32 + i) + '), ' + value.value[i] + ')\n';
}
source += indent(2) + '}\n'
/*
var value = '';
for (var j = 0; j < values[i].length; j++) {
value += '\\' + 'x' + values[i].slice(j, j + 1).toString('hex');
}
source += ' bytes memory ' + name + ' = "' + value + '";\n';
*/
// Dynamic type: string
} else if (types[i] === 'string') {
source += ' string memory ' + name + ' = "' + values[i] + '";\n';
} else if (value.type === 'string') {
source += indent(2) + value.localName + ' = "' + value.value + '";\n';
// Static type; just use the stack
} else {
var value = getValue(values[i]);
source += ' ' + types[i] + ' ' + name + ' = ' + types[i] + '(' + value + ');\n';
var v = value.value;
if (Buffer.isBuffer(v)) { v = '0x' + v.toString('hex'); }
source += indent(2) + value.localName + ' = ' + value.type + '(' + v + ');\n';
}
// Track the name to return
returns.push(name);
}
// Return the values
source += ' return (' + returns.join(', ') + ');\n';
// Recursively (if necessary) set the parameter value
values.forEach(function(value) {
dumpValue(value);
});
source += ' }\n';
source += '}\n';
// Pragmas
var sourcePragma = '';
Object.keys(info.pragmas).forEach(function(pragma) {
sourcePragma += 'pragma ' + pragma + ';\n';
});
if (sourcePragma.length) { sourcePragma += '\n'; }
// Structs
var sourceStructs = '';
Object.keys(info.structs).forEach(function(struct) {
sourceStructs += struct + '\n';
});
// Initialization code
var sourceInit = '';
info.inits.forEach(function(init) {
sourceInit += init;
});
if (sourceInit.length) { sourceInit += '\n'; }
var sourceComments = '';
comments.forEach(function(comment) { sourceComments += '// ' + comment + '\n'; });
if (sourceComments.length) { sourceComments += ' \n'; }
return [
sourceComments,
sourcePragma,
'contract Test {\n',
sourceStructs,
(indent(1) + 'function test() pure returns (' + returnTypes.join(', ') + ') {\n'),
sourceInit,
source,
(indent(1) + '}\n'),
'}\n',
].join('');
}
function compileContract(source, ignoreErrors) {
try {
var contract = solc.compile(source, 0);
contract = contract.contracts.Test;
var contracts = compile(source);
contracts.errors.forEach(function(error) {
console.log(error);
});
var contract = contracts.contracts['demo.sol:Test'];
if (!contract && ignoreErrors) {
addLog(source);
contracts.errors.forEach(function(error) {
addLog(error);
});
addLog('======');
return null;
}
contract.sourceCode = source;
contract.version = JSON.parse(contract.metadata).compiler.version;
return contract;
} catch (error) {
console.log(error);
console.log('Failed to compile ========');
console.log({types: types, values: values, contract: contract});
//console.log({types: types, values: values, contract: contract});
console.log(source);
console.log('========');
process.exit();
}
}
var Address = '0xbe764deeec446f1c6e9d4c891b0f87148a2f9a00';
//var Address = '0xbe764deeec446f1c6e9d4c891b0f87148a2f9a00';
var Output = [];
//var Output = [];
function web3Promise(method, params) {
return new Promise(function(resolve, reject) {
@ -196,63 +337,114 @@ function sendTransaction(transaction) {
});
}
function makeTests() {
function _check(name, types, values, normalizedValues) {
if (!normalizedValues) { normalizedValues = values; }
function _check(name, values, info) {
var test = JSON.stringify(values);
var contract = createContractOutput(types, values);
var transaction = { data: '0x' + contract.bytecode };
// Recursively augment the values
if (!info.inits) { info.inits = []; }
if (!info.structs) { info.structs = { }; }
if (!info.pragmas) { info.pragmas = { }; }
info.pragmas[ 'solidity ^0.4.18'] = true;
return sendTransaction(transaction).then(function(hash) {
console.log('Transaction', hash);
values.forEach(function(value, index) {
populate('r' + index, value, 0, info)
});
return new Promise(function(resolve, reject) {
function check() {
web3Promise('getTransaction', [hash]).then(function(transaction) {
if (transaction.blockHash) {
console.log('Done', hash);
resolve(transaction);
return;
}
console.log('Waiting', hash);
setTimeout(check, 1000);
}, function(error) {
reject(error);
})
}
check();
});
}).then(function(transaction) {
return new web3Promise('call', [{
to: transaction.creates,
data: '0xf8a8fd6d',
}]);
}).then(function(result) {
console.log('Result', result);
var output = {
bytecode: '0x' + contract.bytecode,
result: result,
interface: contract.interface,
name: name,
runtimeBytecode: '0x' + contract.runtimeBytecode,
source: contract.sourceCode,
types: JSON.stringify(types),
values: JSON.stringify(recursiveHexlify(values)),
normalizedValues: JSON.stringify(recursiveHexlify(normalizedValues)),
};
return output;
function getTypes(result, value) {
value.forEach(function(value) {
if (value.type === 'tuple') {
result.push('tuple(' + getTypes([], value.value).join(',') + ')');
} else {
result.push(value.type);
}
});
return result;
}
var types = getTypes([], values);
var source = createContractSource(values, info, [
('Test: ' + name),
('Comnpiler: ' + compile.version),
test
]);
// MOO
//console.log(source);
//return Promise.resolve();
var contract = compileContract(source, true);
if (!contract) {
console.log('Skipping:', test)
return Promise.resolve();
}
if (!contract) { throw new Error('invalid version'); }
var transaction = { data: '0x' + contract.bytecode };
return sendTransaction(transaction).then(function(hash) {
console.log('Transaction', hash);
return new Promise(function(resolve, reject) {
function check() {
web3Promise('getTransaction', [hash]).then(function(transaction) {
if (transaction.blockHash) {
console.log('Done', hash);
resolve(transaction);
return;
}
console.log('Waiting', hash);
setTimeout(check, 1000);
}, function(error) {
reject(error);
})
}
check();
});
}).then(function(transaction) {
return new web3Promise('call', [{
to: transaction.creates,
data: '0xf8a8fd6d',
}]);
}).then(function(result) {
console.log('Result', result);
var output = {
bytecode: '0x' + contract.bytecode,
result: result,
interface: contract.interface,
name: name,
runtimeBytecode: '0x' + contract.runtimeBytecode,
source: contract.sourceCode,
types: JSON.stringify(types),
values: JSON.stringify(recursiveHexlify(values)),
version: contract.version,
// normalizedValues: JSON.stringify(recursiveHexlify(normalizedValues)),
};
return output;
});
}
function makeTests() {
var promiseFuncs = [];
function check(name, types, values, normalizedValues) {
if (normalizedValues == null) { normalizedValues = values; }
promiseFuncs.push(function(resolve, reject) {
_check(name, types, values, normalizedValues).then(function(result) {
var test = [ ];
types.forEach(function(type, index) {
test.push({
type: type,
normalizedValue: normalizedValues[index],
value: values[index]
});
});
_check(name, test).then(function(result) {
resolve(result);
}, function(error) {
reject(error);
@ -260,6 +452,7 @@ function makeTests() {
});
};
// Test cases: https://github.com/ethereum/solidity.js/blob/master/test/coder.decodeParam.js
check('sol-1', ['int'], [new BN(1)]);
check('sol-2', ['int'], [new BN(16)]);
@ -517,7 +710,7 @@ function makeTests() {
for (var j = 0; j < count; j++) {
var type = randomTypeValue('type-' + i + '-' + j);
types.push(type.type);
var value = type.value();
var value = type.value('test-' + j);
values.push(value.value);
normalized.push(value.normalized);
}
@ -528,10 +721,323 @@ function makeTests() {
// check('', ['uint8[4][]'], [ [] ]);
promiseRationing.all(promiseFuncs, 100).then(function(result) {
utils.saveTestcase('contract-interface', result);
//utils.saveTestcase('contract-interface', result);
}, function(error) {
console.log('ERROR', error);
});
}
makeTests();
function makeTestsAbi2() {
var promiseFuncs = [];
function check(name, values, info) {
promiseFuncs.push(function(resolve, reject) {
_check(name, values, info).then(function(result) {
resolve(result);
}, function(error) {
reject(error);
});
});
};
var address = '0x0123456789012345678901234567890123456789';
var longText = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.";
/*
// Some hand-coded (white-box test cases)
check('abi2-basic-test', [
{ type: 'address', value: '0x1234567890123456789012345678901234567890' }
]);
check('abi2-empty', [
{ type: 'tuple', value: [ ] },
]);
check('abi2-deeper', [
{ type: 'tuple', value: [
{ type: 'tuple', value: [
{ type: 'uint256', value: 0x22222222222 }
] }
] }
]);
check('abi2-same-struct', [
{ type: 'tuple', value: [
{ type: 'uint256', value: 18 },
{ type: 'int256', value: -18 },
] },
{ type: 'tuple', value: [
{ type: 'uint256', value: 18 },
{ type: 'int256', value: -18 },
] },
{ type: 'tuple', value: [
{ type: 'tuple', value: [
{ type: 'tuple', value: [
{ type: 'uint256', value: 18 },
{ type: 'int256', value: -18 },
] },
] }
] },
]);
check('abi2-dynamic', [
{ type: 'uint256[]', value: [
{ type: 'uint256', value: 0x123456 },
{ type: 'uint256', value: 0x789abc },
] }
]);
check('abi2-nested-dynamic', [
{ type: 'uint256[][]', value: [
{ type: 'uint256[]', value: [
{ type: 'uint256', value: 0x123456 },
{ type: 'uint256', value: 0x789abc },
{ type: 'uint256', value: 0xdef123 },
] },
{ type: 'uint256[]', value: [
{ type: 'uint256', value: 0x666666 },
] },
] }
]);
check('abi2-string-array', [
{ type: 'string[]', value: [
{ type: 'string', value: "Hello" },
{ type: 'string', value: "World" },
] }
]);
check('abi2-single', [
{ name: 'StructA', type: 'tuple', value: [
{ type: 'uint256', value: 0x11111111111 }
] },
]);
check('abi2-pair', [
{ name: 'StructA', type: 'tuple', value: [
{ type: 'address', value: address },
{ type: 'uint256', value: 0x22222222222 }
] },
]);
check('abi2-deeper', [
{ name: 'StructA', type: 'tuple', value: [
{ name: 'StructB', type: 'tuple', value: [
{ type: 'uint256', value: 0x22222222222 }
] }
] }
]);
check('abi2-very-deep', [
{ name: 'StructA', type: 'tuple', value: [
{ type: 'address', value: address },
{ name: 'StructB', type: 'tuple', value: [
{ type: 'uint32', value: 45 },
{ type: 'uint32', value: 46 },
{ name: 'StructC', type: 'tuple', value: [
{ type: 'uint32', value: 45 },
{ type: 'uint256', value: 0x22222222222 },
{ type: 'tuple', name: 'StructD', value: [
{ type: 'bool', value: true }
] }
] }
] },
{ type: 'uint256', value: 0x55559876 },
] }
]);
check('abi2-string', [
{ type: 'tuple', name: 'StructA', value: [
{ type: 'string', value: "Hello World" }
] }
]);
check('abi2-empty-string', [
{ type: 'tuple', name: 'StructA', value: [
{ type: 'string', value: "" }
] }
]);
check('abi2-long-string', [
{ type: 'tuple', name: 'StructA', value: [
{ type: 'string', value: longText }
] }
]);
*/
// Procedurally generated test cases (handles some black-box testing)
function randomTestPart(seed, info) {
switch (utils.randomNumber(seed + '-type', 0, 7)) {
case 0:
return {
type: 'address',
name: 'address',
value: function(extra) {
return {
type: 'address',
name: 'address',
value: getAddress(utils.randomHexString(seed + '-address-' + extra, 20, 20))
}
}
};
case 1:
var sign = (utils.randomNumber(seed + '-number-sign', 0, 2) == 0);
var type = ((sign ? '': 'u') + 'int');
var size = utils.randomNumber(seed + '-number-size', 0, 33) * 8;
if (size !== 0) {
type += String(size);
} else {
size = 256;
}
return {
type: type,
name: type,
value: function(extra) {
var value = new BN(utils.randomHexString(seed + '-number-value-' + extra, 1, size / 8).substring(2), 16);
if (sign) {
var signBit = (new BN(1)).shln(size - 1);
if (!signBit.and(value).isZero()) {
value = value.maskn(size - 1).mul(new BN(-1));
}
}
return {
type: type,
name: type,
value: value
}
}
}
case 2:
return {
type: 'bytes',
name: 'bytes',
value: function(extra) {
return {
type: 'bytes',
name: 'bytes',
value: new Buffer(utils.randomBytes(seed + '-bytes-' + extra, 0, 64))
}
}
};
case 3:
return {
type: 'string',
name: 'string',
value: function(extra) {
return {
type: 'string',
name: 'string',
value: longText.substring(0, utils.randomNumber(seed + '-string-' + extra, 0, longText.length))
}
}
};
case 4:
var count = utils.randomNumber(seed + '-bytes-count', 1, 33);
return {
type: 'bytes' + String(count),
name: 'bytes' + String(count),
value: function(extra) {
return {
type: 'bytes' + String(count),
name: 'bytes' + String(count),
value: new Buffer(utils.randomBytes(seed + '-bytes-value-' + extra, count, count))
};
}
};
case 5:
var subtype = randomTestPart(seed + '-array-subtype', info);
var count = utils.randomNumber(seed + '-array-count', 0, 4);
var size = String(count);
if (count === 0) {
count = utils.randomNumber(seed + '-array-size', 0, 4);
size = '';
}
var type = subtype.type + '[' + size + ']';
var name = (subtype.name + '[' + size + ']');
return {
type: type,
name: name,
value: function() {
var result = [];
for (var i = 0; i < count; i++) {
result.push(subtype.value('-array-value-' + i));
}
return {
type: type,
name: name,
value: result
};
}
};
case 6:
var subtypes = [];
var subtypeTypes = [];
var subtypeNames = [];
var count = utils.randomNumber(seed + '-tuple-size', 1, 4);
for (var i = 0; i < count; i++) {
var subtype = randomTestPart(seed + '-tuple-subtype-' + i, info);
subtypes.push(subtype);
subtypeTypes.push(subtype.type);
subtypeNames.push(subtype.name);
}
var type = 'tuple(' + subtypeTypes.join(',') + ')';
var name = getStructName(subtypeNames);
var struct = getStructSource(subtypeNames);
info.structs[struct] = true;
return {
type: type,
name: name,
struct: struct,
value: function(extra) {
var result = [];
subtypes.forEach(function(subtype) {
result.push(subtype.value(seed + '-tuple-subvalue-' + i));
});
return {
type: 'tuple',
name: name,
struct: struct,
value: result
};
}
};
default:
throw new Error('invalid case');
}
}
for (var i = 0; i < 2000; i++) {
//i = 917
var test = [];
var info = { pragmas: { 'experimental ABIEncoderV2': true }, structs: {} };
var count = utils.randomNumber('count-' + i, 1, 5);
for (var j = 0; j < count; j++) {
var part = randomTestPart('test-' + i + '-' + j, info)
test.push(part.value('part-' + j));
}
console.dir(test, { depth: null });
check('random-' + i, test, info);
//break;
}
promiseRationing.all(promiseFuncs, 20).then(function(result) {
result = result.filter(function(item) { return !!item; } );
utils.saveTests('contract-interface-abi2', result);
}, function(error) {
console.log('ERROR', error);
});
}
//makeTests();
makeTestsAbi2();

View File

@ -75,7 +75,7 @@ function equals(a, b) {
return true;
}
if (testWeb3) {
if (testWeb3 || true) {
if (a.match && a.match(/^0x[0-9A-Fa-f]{40}$/)) { a = a.toLowerCase(); }
if (b.match && b.match(/^0x[0-9A-Fa-f]{40}$/)) { b = b.toLowerCase(); }
}
@ -126,6 +126,9 @@ function getValues(object, format) {
}
return utils.arrayify(object.value);
case 'tuple':
return getValues(object.value, format);
default:
throw new Error('invalid type - ' + object.type);
}
@ -207,14 +210,9 @@ describe('Contract Interface ABI Decoding', function() {
assert.ok(false, 'This testcase seems to fail');
} else {
//console.log(result);
var resultBuffer = new Buffer(result.substring(2), 'hex');
var valuesEthereumLib = getValues(JSON.parse(test.normalizedValues), FORMAT_ETHEREUM_LIB);
//console.log('V', valuesEthereumLib);
var ethereumLibDecoded = ethereumLibCoder.rawDecode(types, resultBuffer);
//console.log('R', types, ethereumLibDecoded);
//console.log('E', valuesEthereumLib, ethereumLibDecoded, equals(valuesEthereumLib, ethereumLibDecoded));
//console.log(ethereumLibDecoded);
assert.ok(equals(valuesEthereumLib, ethereumLibDecoded),
'ethereum-lib decoded data - ' + title);
}
@ -223,3 +221,52 @@ describe('Contract Interface ABI Decoding', function() {
});
});
describe('Contract Interface ABI v2 Decoding', function() {
var Interface = require('../contracts/index.js').Interface;
var tests = utils.loadTests('contract-interface-abi2');
tests.forEach(function(test) {
var values = getValues(JSON.parse(test.values));
var types = JSON.parse(test.types);
var result = test.result;
var title = test.name + ' => (' + test.types + ') = (' + test.values + ')';
it(('decodes parameters - ' + test.name + ' - ' + test.types), function() {
var decoded = Interface.decodeParams(types, result);
var decodedArray = Array.prototype.slice.call(decoded);
assert.ok(equals(values, decodedArray), 'decoded parameters - ' + title);
});
});
});
describe('Contract Interface ABI v2 Encoding', function() {
var Interface = require('../contracts/index.js').Interface;
var tests = utils.loadTests('contract-interface-abi2');
tests.forEach(function(test) {
var values = getValues(JSON.parse(test.values));
var types = JSON.parse(test.types);
var expected = test.result;
var title = test.name + ' => (' + test.types + ') = (' + test.value + ')';
it(('encodes parameters - ' + test.name + ' - ' + test.types), function() {
var encoded = Interface.encodeParams(types, values);
/*
console.log('Actual:');
for (var i = 2; i < encoded.length; i += 64) {
console.log(' ', encoded.substring(i, i + 64));
}
console.log('Expected:');
for (var i = 2; i < expected.length; i += 64) {
console.log(' ', expected.substring(i, i + 64));
}
*/
assert.equal(encoded, expected, 'decoded parameters - ' + title);
});
});
});

Binary file not shown.