Added ABI v2 coder to Interface (still experimental in Solidity though).
This commit is contained in:
parent
54c19dfb5a
commit
fe3ac0e94d
@ -58,8 +58,27 @@ function getKeys(params, key, allowEmpty) {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
function coderNumber(size, signed) {
|
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 {
|
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) {
|
encode: function(value) {
|
||||||
value = utils.bigNumberify(value).toTwos(size * 8).maskn(size * 8);
|
value = utils.bigNumberify(value).toTwos(size * 8).maskn(size * 8);
|
||||||
//value = 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 uint256Coder = coderNumber(32, false);
|
||||||
|
|
||||||
var coderBoolean = {
|
var coderBoolean = {
|
||||||
|
name: 'boolean',
|
||||||
|
type: 'boolean',
|
||||||
encode: function(value) {
|
encode: function(value) {
|
||||||
return uint256Coder.encode(value ? 1: 0);
|
return uint256Coder.encode(value ? 1: 0);
|
||||||
},
|
},
|
||||||
@ -102,7 +123,10 @@ var coderBoolean = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function coderFixedBytes(length) {
|
function coderFixedBytes(length) {
|
||||||
|
var name = ('bytes' + length);
|
||||||
return {
|
return {
|
||||||
|
name: name,
|
||||||
|
type: name,
|
||||||
encode: function(value) {
|
encode: function(value) {
|
||||||
value = utils.arrayify(value);
|
value = utils.arrayify(value);
|
||||||
if (length === 32) { return value; }
|
if (length === 32) { return value; }
|
||||||
@ -123,6 +147,8 @@ function coderFixedBytes(length) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var coderAddress = {
|
var coderAddress = {
|
||||||
|
name: 'address',
|
||||||
|
type: 'address',
|
||||||
encode: function(value) {
|
encode: function(value) {
|
||||||
value = utils.arrayify(utils.getAddress(value));
|
value = utils.arrayify(utils.getAddress(value));
|
||||||
var result = new Uint8Array(32);
|
var result = new Uint8Array(32);
|
||||||
@ -163,6 +189,8 @@ function _decodeDynamicBytes(data, offset) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var coderDynamicBytes = {
|
var coderDynamicBytes = {
|
||||||
|
name: 'bytes',
|
||||||
|
type: 'bytes',
|
||||||
encode: function(value) {
|
encode: function(value) {
|
||||||
return _encodeDynamicBytes(utils.arrayify(value));
|
return _encodeDynamicBytes(utils.arrayify(value));
|
||||||
},
|
},
|
||||||
@ -175,6 +203,8 @@ var coderDynamicBytes = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
var coderString = {
|
var coderString = {
|
||||||
|
name: 'string',
|
||||||
|
type: 'string',
|
||||||
encode: function(value) {
|
encode: function(value) {
|
||||||
return _encodeDynamicBytes(utils.toUtf8Bytes(value));
|
return _encodeDynamicBytes(utils.toUtf8Bytes(value));
|
||||||
},
|
},
|
||||||
@ -186,24 +216,106 @@ var coderString = {
|
|||||||
dynamic: true
|
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 {
|
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) {
|
encode: function(value) {
|
||||||
if (!Array.isArray(value)) { throwError('invalid array'); }
|
if (!Array.isArray(value)) { throwError('invalid array'); }
|
||||||
|
|
||||||
|
var count = length;
|
||||||
|
|
||||||
var result = new Uint8Array(0);
|
var result = new Uint8Array(0);
|
||||||
if (length === -1) {
|
if (count === -1) {
|
||||||
length = value.length;
|
count = value.length;
|
||||||
result = uint256Coder.encode(length);
|
result = uint256Coder.encode(count);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (length !== value.length) { throwError('size mismatch'); }
|
if (count !== value.length) { throwError('size mismatch'); }
|
||||||
|
|
||||||
value.forEach(function(value) {
|
var coders = [];
|
||||||
result = utils.concat([result, coder.encode(value)]);
|
value.forEach(function(value) { coders.push(coder); });
|
||||||
});
|
|
||||||
|
|
||||||
return result;
|
return utils.concat([result, pack(coders, value)]);
|
||||||
},
|
},
|
||||||
decode: function(data, offset) {
|
decode: function(data, offset) {
|
||||||
// @TODO:
|
// @TODO:
|
||||||
@ -211,98 +323,142 @@ function coderArray(coder, length) {
|
|||||||
|
|
||||||
var consumed = 0;
|
var consumed = 0;
|
||||||
|
|
||||||
var result;
|
var count = length;
|
||||||
if (length === -1) {
|
|
||||||
result = uint256Coder.decode(data, offset);
|
if (count === -1) {
|
||||||
length = result.value.toNumber();
|
var decodedLength = uint256Coder.decode(data, offset);
|
||||||
consumed += result.consumed;
|
count = decodedLength.value.toNumber();
|
||||||
offset += result.consumed;
|
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 = unpack(coders, data, offset);
|
||||||
var result = coder.decode(data, offset);
|
result.consumed += consumed;
|
||||||
consumed += result.consumed;
|
return result;
|
||||||
offset += result.consumed;
|
},
|
||||||
value.push(result.value);
|
dynamic: (length === -1 || coder.dynamic)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
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 {
|
return {
|
||||||
consumed: consumed,
|
coders: coders,
|
||||||
value: value,
|
name: 'tuple',
|
||||||
|
type: type,
|
||||||
|
encode: function(value) {
|
||||||
|
|
||||||
|
if (coders.length !== coders.length) {
|
||||||
|
throwError('types/values mismatch', { type: type, values: values });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return pack(coders, value);
|
||||||
},
|
},
|
||||||
dynamic: (length === -1)
|
decode: function(data, offset) {
|
||||||
}
|
return unpack(coders, data, offset);
|
||||||
|
},
|
||||||
|
dynamic: dynamic
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// Break the type up into [staticType][staticArray]*[dynamicArray]? | [dynamicType] and
|
function getTypes(coders) {
|
||||||
// build the coder up from its parts
|
var type = coderTuple(coders).type;
|
||||||
var paramTypePart = new RegExp(/^((u?int|bytes)([0-9]*)|(address|bool|string)|(\[([0-9]*)\]))/);
|
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');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
result.push(accum);
|
||||||
|
|
||||||
|
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) {
|
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]);
|
var coder = paramTypeSimple[type];
|
||||||
switch (prefix) {
|
if (coder) { return coder; }
|
||||||
case 'int': case 'uint':
|
|
||||||
if (coder) { throwError('invalid type', { type: type }); }
|
var match = type.match(paramTypeNumber);
|
||||||
var size = parseInt(part[3] || 256);
|
if (match) {
|
||||||
|
var size = parseInt(match[2] || 256);
|
||||||
if (size === 0 || size > 256 || (size % 8) !== 0) {
|
if (size === 0 || size > 256 || (size % 8) !== 0) {
|
||||||
throwError('invalid type', { type: type });
|
throwError('invalid type', { type: type });
|
||||||
}
|
}
|
||||||
coder = coderNumber(size / 8, (prefix === 'int'));
|
return coderNumber(size / 8, (match[1] === 'int'));
|
||||||
break;
|
}
|
||||||
|
|
||||||
case 'bool':
|
var match = type.match(paramTypeBytes);
|
||||||
if (coder) { throwError('invalid type', { type: type }); }
|
if (match) {
|
||||||
coder = coderBoolean;
|
var size = parseInt(match[1]);
|
||||||
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) {
|
if (size === 0 || size > 32) {
|
||||||
throwError('invalid type ' + type);
|
throwError('invalid type ' + type);
|
||||||
}
|
}
|
||||||
coder = coderFixedBytes(size);
|
return 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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!coder) { throwError('invalid type'); }
|
var match = type.match(paramTypeArray);
|
||||||
return coder;
|
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) {
|
function populateDescription(object, items) {
|
||||||
for (var key in items) {
|
for (var key in items) {
|
||||||
utils.defineProperty(object, key, items[key]);
|
utils.defineProperty(object, key, items[key]);
|
||||||
@ -525,46 +681,12 @@ function Interface(abi) {
|
|||||||
utils.defineProperty(Interface, 'encodeParams', function(types, values) {
|
utils.defineProperty(Interface, 'encodeParams', function(types, values) {
|
||||||
if (types.length !== values.length) { throwError('types/values mismatch', {types: types, values: values}); }
|
if (types.length !== values.length) { throwError('types/values mismatch', {types: types, values: values}); }
|
||||||
|
|
||||||
var parts = [];
|
var coders = [];
|
||||||
|
types.forEach(function(type) {
|
||||||
types.forEach(function(type, index) {
|
coders.push(getParamCoder(type));
|
||||||
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 offset = 0, dynamicOffset = staticSize;
|
return utils.hexlify(coderTuple(coders).encode(values));
|
||||||
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);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
@ -580,47 +702,35 @@ utils.defineProperty(Interface, 'decodeParams', function(names, types, data) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
data = utils.arrayify(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 values = new Result();
|
||||||
|
coders.forEach(function(coder, index) {
|
||||||
var offset = 0;
|
values[index] = result.value[index];
|
||||||
types.forEach(function(type, index) {
|
if (names && names[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]) {
|
|
||||||
var name = names[index];
|
var name = names[index];
|
||||||
|
|
||||||
// We reserve length to make the Result object arrayish
|
|
||||||
if (name === 'length') {
|
if (name === 'length') {
|
||||||
console.log('WARNING: result length renamed to _length');
|
console.log('WARNING: result length renamed to _length');
|
||||||
name = '_length';
|
name = '_length';
|
||||||
}
|
}
|
||||||
|
|
||||||
if (values[name] == null) {
|
if (values[name] == null) {
|
||||||
values[name] = result.value;
|
values[name] = values[index];
|
||||||
} else {
|
} else {
|
||||||
console.log('WARNING: duplicate value - ' + name);
|
console.log('WARNING: duplicate value - ' + name);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
})
|
||||||
|
|
||||||
values.length = types.length;
|
values.length = types.length;
|
||||||
|
|
||||||
return values;
|
return values;
|
||||||
});
|
});
|
||||||
|
|
||||||
//utils.defineProperty(Interface, 'getDeployTransaction', function(bytecode) {
|
|
||||||
//});
|
|
||||||
|
|
||||||
module.exports = Interface;
|
module.exports = Interface;
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "ethers-contracts",
|
"name": "ethers-contracts",
|
||||||
"version": "2.1.3",
|
"version": "2.1.4",
|
||||||
"description": "Contract and Interface (ABI) library for Ethereum.",
|
"description": "Contract and Interface (ABI) library for Ethereum.",
|
||||||
"bugs": {
|
"bugs": {
|
||||||
"url": "http://github.com/ethers-io/ethers.js/issues",
|
"url": "http://github.com/ethers-io/ethers.js/issues",
|
||||||
|
@ -1,19 +1,45 @@
|
|||||||
'use strict';
|
'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 BN = require('bn.js');
|
||||||
var solc = require('solc');
|
|
||||||
var ethereumVm = require('ethereumjs-vm');
|
|
||||||
var ethereumUtil = require('ethereumjs-util');
|
|
||||||
var promiseRationing = require('promise-rationing');
|
var promiseRationing = require('promise-rationing');
|
||||||
var Web3 = require('web3');
|
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 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
|
// Create the indent given a tabstop
|
||||||
function indent(tabs) {
|
function indent(tabs) {
|
||||||
@ -23,6 +49,18 @@ function indent(tabs) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function recursiveHexlify(object) {
|
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') {
|
if (typeof(object) === 'number') {
|
||||||
object = new BN(object);
|
object = new BN(object);
|
||||||
}
|
}
|
||||||
@ -45,12 +83,13 @@ function recursiveHexlify(object) {
|
|||||||
|
|
||||||
} else if (Buffer.isBuffer(object)) {
|
} else if (Buffer.isBuffer(object)) {
|
||||||
return {type: 'buffer', value: utils.hexlify(object)};
|
return {type: 'buffer', value: utils.hexlify(object)};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
throw new Error('unsupported type - ' + object + ' ' + typeof(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)) {
|
} else if (BN.isBN(value)) {
|
||||||
value = value.toString(10);
|
value = value.toString(10);
|
||||||
} else if (typeof(value) !== 'string' && typeof(value) !== 'number' && typeof(value) !== 'boolean') {
|
} else if (typeof(value) !== 'string' && typeof(value) !== 'number' && typeof(value) !== 'boolean') {
|
||||||
|
console.dir(value, { depth: null });
|
||||||
throw new Error('invalid type - ' + value + ' ' + typeof(value));
|
throw new Error('invalid type - ' + value + ' ' + typeof(value));
|
||||||
}
|
}
|
||||||
return 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
|
// Array type; do a deep copy
|
||||||
if (types[i].indexOf('[') >= 0) {
|
} else if (value.type.indexOf('[') >= 0) {
|
||||||
|
value.value.forEach(function(value) {
|
||||||
// Each count (or optionally empty) array type
|
dumpValue(value);
|
||||||
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], []);
|
|
||||||
|
|
||||||
// Dynamic type: bytes
|
// Dynamic type: bytes
|
||||||
} else if (types[i] === 'bytes') {
|
} else if (value.type === 'bytes') {
|
||||||
source += indent(2) + 'bytes memory ' + name + ' = new bytes(' + values[i].length + ');\n';
|
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(2) + 'assembly {\n'
|
||||||
source += indent(3) + 'mstore(' + name + ', ' + values[i].length + ')\n';
|
source += indent(3) + 'mstore(temp, ' + value.value.length + ')\n';
|
||||||
for (var j = 0; j < values[i].length; j++) {
|
for (var i = 0; i < value.value.length; i++) {
|
||||||
source += indent(3) + 'mstore8(add(' + name + ', ' + (32 + j) + '), ' + values[i][j] + ')\n';
|
source += indent(3) + 'mstore8(add(temp, ' + (32 + i) + '), ' + value.value[i] + ')\n';
|
||||||
}
|
}
|
||||||
source += indent(2) + '}\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
|
// Dynamic type: string
|
||||||
} else if (types[i] === 'string') {
|
} else if (value.type === 'string') {
|
||||||
source += ' string memory ' + name + ' = "' + values[i] + '";\n';
|
source += indent(2) + value.localName + ' = "' + value.value + '";\n';
|
||||||
|
|
||||||
// Static type; just use the stack
|
// Static type; just use the stack
|
||||||
} else {
|
} else {
|
||||||
var value = getValue(values[i]);
|
var v = value.value;
|
||||||
source += ' ' + types[i] + ' ' + name + ' = ' + types[i] + '(' + value + ');\n';
|
if (Buffer.isBuffer(v)) { v = '0x' + v.toString('hex'); }
|
||||||
|
source += indent(2) + value.localName + ' = ' + value.type + '(' + v + ');\n';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Track the name to return
|
// Recursively (if necessary) set the parameter value
|
||||||
returns.push(name);
|
values.forEach(function(value) {
|
||||||
|
dumpValue(value);
|
||||||
|
});
|
||||||
|
|
||||||
|
// 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('');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Return the values
|
function compileContract(source, ignoreErrors) {
|
||||||
source += ' return (' + returns.join(', ') + ');\n';
|
|
||||||
|
|
||||||
source += ' }\n';
|
|
||||||
source += '}\n';
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
var contract = solc.compile(source, 0);
|
var contracts = compile(source);
|
||||||
contract = contract.contracts.Test;
|
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.sourceCode = source;
|
||||||
|
contract.version = JSON.parse(contract.metadata).compiler.version;
|
||||||
return contract;
|
return contract;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
console.log(error);
|
||||||
console.log('Failed to compile ========');
|
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(source);
|
||||||
console.log('========');
|
console.log('========');
|
||||||
process.exit();
|
process.exit();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var Address = '0xbe764deeec446f1c6e9d4c891b0f87148a2f9a00';
|
//var Address = '0xbe764deeec446f1c6e9d4c891b0f87148a2f9a00';
|
||||||
|
|
||||||
var Output = [];
|
//var Output = [];
|
||||||
|
|
||||||
function web3Promise(method, params) {
|
function web3Promise(method, params) {
|
||||||
return new Promise(function(resolve, reject) {
|
return new Promise(function(resolve, reject) {
|
||||||
@ -196,12 +337,50 @@ function sendTransaction(transaction) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function makeTests() {
|
|
||||||
|
|
||||||
function _check(name, types, values, normalizedValues) {
|
function _check(name, values, info) {
|
||||||
if (!normalizedValues) { normalizedValues = values; }
|
var test = JSON.stringify(values);
|
||||||
|
|
||||||
|
// 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;
|
||||||
|
|
||||||
|
values.forEach(function(value, index) {
|
||||||
|
populate('r' + index, value, 0, info)
|
||||||
|
});
|
||||||
|
|
||||||
|
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 contract = createContractOutput(types, values);
|
|
||||||
var transaction = { data: '0x' + contract.bytecode };
|
var transaction = { data: '0x' + contract.bytecode };
|
||||||
|
|
||||||
return sendTransaction(transaction).then(function(hash) {
|
return sendTransaction(transaction).then(function(hash) {
|
||||||
@ -242,17 +421,30 @@ function makeTests() {
|
|||||||
source: contract.sourceCode,
|
source: contract.sourceCode,
|
||||||
types: JSON.stringify(types),
|
types: JSON.stringify(types),
|
||||||
values: JSON.stringify(recursiveHexlify(values)),
|
values: JSON.stringify(recursiveHexlify(values)),
|
||||||
normalizedValues: JSON.stringify(recursiveHexlify(normalizedValues)),
|
version: contract.version,
|
||||||
|
// normalizedValues: JSON.stringify(recursiveHexlify(normalizedValues)),
|
||||||
};
|
};
|
||||||
|
|
||||||
return output;
|
return output;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function makeTests() {
|
||||||
|
|
||||||
var promiseFuncs = [];
|
var promiseFuncs = [];
|
||||||
|
|
||||||
function check(name, types, values, normalizedValues) {
|
function check(name, types, values, normalizedValues) {
|
||||||
|
if (normalizedValues == null) { normalizedValues = values; }
|
||||||
promiseFuncs.push(function(resolve, reject) {
|
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);
|
resolve(result);
|
||||||
}, function(error) {
|
}, function(error) {
|
||||||
reject(error);
|
reject(error);
|
||||||
@ -260,6 +452,7 @@ function makeTests() {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
// Test cases: https://github.com/ethereum/solidity.js/blob/master/test/coder.decodeParam.js
|
// Test cases: https://github.com/ethereum/solidity.js/blob/master/test/coder.decodeParam.js
|
||||||
check('sol-1', ['int'], [new BN(1)]);
|
check('sol-1', ['int'], [new BN(1)]);
|
||||||
check('sol-2', ['int'], [new BN(16)]);
|
check('sol-2', ['int'], [new BN(16)]);
|
||||||
@ -517,7 +710,7 @@ function makeTests() {
|
|||||||
for (var j = 0; j < count; j++) {
|
for (var j = 0; j < count; j++) {
|
||||||
var type = randomTypeValue('type-' + i + '-' + j);
|
var type = randomTypeValue('type-' + i + '-' + j);
|
||||||
types.push(type.type);
|
types.push(type.type);
|
||||||
var value = type.value();
|
var value = type.value('test-' + j);
|
||||||
values.push(value.value);
|
values.push(value.value);
|
||||||
normalized.push(value.normalized);
|
normalized.push(value.normalized);
|
||||||
}
|
}
|
||||||
@ -528,10 +721,323 @@ function makeTests() {
|
|||||||
// check('', ['uint8[4][]'], [ [] ]);
|
// check('', ['uint8[4][]'], [ [] ]);
|
||||||
|
|
||||||
promiseRationing.all(promiseFuncs, 100).then(function(result) {
|
promiseRationing.all(promiseFuncs, 100).then(function(result) {
|
||||||
utils.saveTestcase('contract-interface', result);
|
//utils.saveTestcase('contract-interface', result);
|
||||||
}, function(error) {
|
}, function(error) {
|
||||||
console.log('ERROR', 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();
|
||||||
|
@ -75,7 +75,7 @@ function equals(a, b) {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (testWeb3) {
|
if (testWeb3 || true) {
|
||||||
if (a.match && a.match(/^0x[0-9A-Fa-f]{40}$/)) { a = a.toLowerCase(); }
|
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(); }
|
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);
|
return utils.arrayify(object.value);
|
||||||
|
|
||||||
|
case 'tuple':
|
||||||
|
return getValues(object.value, format);
|
||||||
|
|
||||||
default:
|
default:
|
||||||
throw new Error('invalid type - ' + object.type);
|
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');
|
assert.ok(false, 'This testcase seems to fail');
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
//console.log(result);
|
|
||||||
var resultBuffer = new Buffer(result.substring(2), 'hex');
|
var resultBuffer = new Buffer(result.substring(2), 'hex');
|
||||||
var valuesEthereumLib = getValues(JSON.parse(test.normalizedValues), FORMAT_ETHEREUM_LIB);
|
var valuesEthereumLib = getValues(JSON.parse(test.normalizedValues), FORMAT_ETHEREUM_LIB);
|
||||||
//console.log('V', valuesEthereumLib);
|
|
||||||
var ethereumLibDecoded = ethereumLibCoder.rawDecode(types, resultBuffer);
|
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),
|
assert.ok(equals(valuesEthereumLib, ethereumLibDecoded),
|
||||||
'ethereum-lib decoded data - ' + title);
|
'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);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
BIN
tests/tests/contract-interface-abi2.json.gz
Normal file
BIN
tests/tests/contract-interface-abi2.json.gz
Normal file
Binary file not shown.
Loading…
Reference in New Issue
Block a user