538 lines
19 KiB
JavaScript
538 lines
19 KiB
JavaScript
'use strict';
|
|
|
|
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 utils = require('./utils.js');
|
|
|
|
// Create the indent given a tabstop
|
|
function indent(tabs) {
|
|
var indent = new Buffer(tabs * 4);
|
|
indent.fill(32);
|
|
return indent.toString('utf8')
|
|
}
|
|
|
|
function recursiveHexlify(object) {
|
|
if (typeof(object) === 'number') {
|
|
object = new BN(object);
|
|
}
|
|
|
|
if (Array.isArray(object)) {
|
|
var result = [];
|
|
object.forEach(function(object) {
|
|
result.push(recursiveHexlify(object));
|
|
});
|
|
return result;
|
|
|
|
} else if (BN.isBN(object)) {
|
|
return {type: 'number', value: object.toString(10)};
|
|
|
|
} else if (typeof(object) === 'string') {
|
|
return {type: 'string', value: object};
|
|
|
|
} else if (typeof(object) === 'boolean') {
|
|
return {type: 'boolean', value: 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'));
|
|
|
|
/**
|
|
*
|
|
*
|
|
*/
|
|
function getValue(value) {
|
|
if (Buffer.isBuffer(value)) {
|
|
value = utils.hexlify(value);
|
|
} else if (BN.isBN(value)) {
|
|
value = value.toString(10);
|
|
} else if (typeof(value) !== 'string' && typeof(value) !== 'number' && typeof(value) !== 'boolean') {
|
|
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);
|
|
|
|
// 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], []);
|
|
|
|
// Dynamic type: bytes
|
|
} else if (types[i] === 'bytes') {
|
|
source += indent(2) + 'bytes memory ' + name + ' = new bytes(' + values[i].length + ');\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(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';
|
|
|
|
// Static type; just use the stack
|
|
} else {
|
|
var value = getValue(values[i]);
|
|
source += ' ' + types[i] + ' ' + name + ' = ' + types[i] + '(' + value + ');\n';
|
|
}
|
|
|
|
// Track the name to return
|
|
returns.push(name);
|
|
}
|
|
|
|
// Return the values
|
|
source += ' return (' + returns.join(', ') + ');\n';
|
|
|
|
source += ' }\n';
|
|
source += '}\n';
|
|
|
|
try {
|
|
var contract = solc.compile(source, 0);
|
|
contract = contract.contracts.Test;
|
|
contract.sourceCode = source;
|
|
return contract;
|
|
} catch (error) {
|
|
console.log('Failed to compile ========');
|
|
console.log({types: types, values: values, contract: contract});
|
|
console.log(source);
|
|
console.log('========');
|
|
process.exit();
|
|
}
|
|
}
|
|
|
|
var Address = '0xbe764deeec446f1c6e9d4c891b0f87148a2f9a00';
|
|
|
|
var Output = [];
|
|
|
|
function web3Promise(method, params) {
|
|
return new Promise(function(resolve, reject) {
|
|
params.push(function(error, result) {
|
|
if (error) {
|
|
console.log(error);
|
|
return reject(error);
|
|
}
|
|
resolve(result);
|
|
});
|
|
|
|
web3.eth[method].apply(web3, params);
|
|
});
|
|
}
|
|
|
|
function sendTransaction(transaction) {
|
|
var address = '0x00Bd138aBD70e2F00903268F3Db08f2D25677C9e';
|
|
transaction.from = address;
|
|
|
|
console.log('Sending...');
|
|
|
|
return Promise.all([
|
|
web3Promise('getGasPrice', []),
|
|
web3Promise('getTransactionCount', [address, 'pending'])
|
|
]).then(function(result) {
|
|
transaction.gasPrice = '0x' + result[0].toString(16);
|
|
transaction.gas = "0x55d4a80";
|
|
//transaction.nonce = result[1];
|
|
return web3Promise('sendTransaction', [transaction]);
|
|
});
|
|
}
|
|
|
|
function makeTests() {
|
|
|
|
function _check(name, types, values, normalizedValues) {
|
|
if (!normalizedValues) { normalizedValues = values; }
|
|
|
|
var contract = createContractOutput(types, values);
|
|
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)),
|
|
normalizedValues: JSON.stringify(recursiveHexlify(normalizedValues)),
|
|
};
|
|
return output;
|
|
});
|
|
}
|
|
|
|
var promiseFuncs = [];
|
|
|
|
function check(name, types, values, normalizedValues) {
|
|
promiseFuncs.push(function(resolve, reject) {
|
|
_check(name, types, values, normalizedValues).then(function(result) {
|
|
resolve(result);
|
|
}, function(error) {
|
|
reject(error);
|
|
});
|
|
});
|
|
};
|
|
|
|
// 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)]);
|
|
|
|
check('sol-3', ['int'], [new BN(-1)]);
|
|
check('sol-4', ['int256'], [new BN(1)]);
|
|
check('sol-5', ['int256'], [new BN(16)]);
|
|
check('sol-6', ['int256'], [new BN(-1)]);
|
|
check('sol-7', ['int8'], [new BN(16)]);
|
|
check('sol-8', ['int32'], [new BN(16)]);
|
|
check('sol-9', ['int64'], [new BN(16)]);
|
|
check('sol-10', ['int128'], [new BN(16)]);
|
|
|
|
check('sol-11', ['uint'], [new BN(1)]);
|
|
check('sol-12', ['uint'], [new BN(16)]);
|
|
check('sol-13', ['uint'], [new BN(-1)], [new BN('ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff', 16)]);
|
|
check('sol-14', ['uint256'], [new BN(1)]);
|
|
check('sol-15', ['uint256'], [new BN(16)]);
|
|
check('sol-16', ['uint256'], [new BN(-1)], [new BN('ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff', 16)]);
|
|
check('sol-17', ['uint8'], [new BN(16)]);
|
|
check('sol-18', ['uint32'], [new BN(16)]);
|
|
check('sol-19', ['uint64'], [new BN(16)]);
|
|
check('sol-20', ['uint128'], [new BN(16)]);
|
|
|
|
check('sol-21', ['int', 'int'], [new BN(1), new BN(2)]);
|
|
check('sol-22', ['int', 'int'], [new BN(1), new BN(2)]);
|
|
check('sol-23', ['int[2]', 'int'], [[new BN(12), new BN(22)], new BN(3)]);
|
|
check('sol-24', ['int[2]', 'int[]'], [[new BN(32), new BN(42)], [new BN(3), new BN(4), new BN(5)]]);
|
|
|
|
check('sol-25',
|
|
['bytes32'],
|
|
[new Buffer('6761766f66796f726b0000000000000000000000000000000000000000000000', 'hex')]
|
|
);
|
|
check('sol-26',
|
|
['bytes'],
|
|
[new Buffer('6761766f66796f726b', 'hex')]
|
|
);
|
|
|
|
check('sol-27',
|
|
['string'],
|
|
['\uD835\uDF63']
|
|
);
|
|
|
|
check('sol-28',
|
|
['address', 'string', 'bytes6[4]', 'int'],
|
|
[
|
|
getAddress("0x97916ef549947a3e0d321485a31dd2715a97d455"),
|
|
"foobar2",
|
|
[
|
|
new Buffer("a165ab0173c6", 'hex'),
|
|
new Buffer("f0f37bee9244", 'hex'),
|
|
new Buffer("c8dc0bf08d2b", 'hex'),
|
|
new Buffer("c8dc0bf08d2b", 'hex')
|
|
],
|
|
34
|
|
]
|
|
);
|
|
|
|
check('sol-29',
|
|
['bytes32'],
|
|
[new Buffer('731a3afc00d1b1e3461b955e53fc866dcf303b3eb9f4c16f89e388930f48134b', 'hex')]
|
|
);
|
|
check('sol-30',
|
|
['bytes'],
|
|
[new Buffer('731a3afc00d1b1e3461b955e53fc866dcf303b3eb9f4c16f89e388930f48134b', 'hex')]
|
|
);
|
|
|
|
check('sol-31',
|
|
['bytes32[2]'],
|
|
[[
|
|
new Buffer('731a3afc00d1b1e3461b955e53fc866dcf303b3eb9f4c16f89e388930f48134b', 'hex'),
|
|
new Buffer('731a3afc00d1b1e3461b955e53fc866dcf303b3eb9f4c16f89e388930f48134b', 'hex')
|
|
]]
|
|
);
|
|
|
|
check('sol-32',
|
|
['bytes'],
|
|
[new Buffer('131a3afc00d1b1e3461b955e53fc866dcf303b3eb9f4c16f89e388930f48134b' +
|
|
'231a3afc00d1b1e3461b955e53fc866dcf303b3eb9f4c16f89e388930f48134b' +
|
|
'331a3afc00d1b1e3461b955e53fc866dcf303b3eb9f4c16f89e388930f48134b', 'hex')]
|
|
);
|
|
|
|
// Some extra checks for width and sign tests
|
|
check('sol-33', ['uint32'], [14], [new BN(14)]);
|
|
check('sol-34', ['uint32'], [14], [new BN(14)]);
|
|
check('sol-35', ['uint32'], [-14], [new BN(0xfffffff2)]);
|
|
check('sol-36', ['int32'], [14], [new BN(14)]);
|
|
check('sol-37', ['int32'], [-14], [new BN(-14)]);
|
|
|
|
check('sol-38', ['int8'], [new BN(1)], [new BN(1)]);
|
|
check('sol-39', ['int8'], [new BN(-1)], [new BN(-1)]);
|
|
check('sol-40', ['int8'], [new BN(189)], [new BN(-67)]);
|
|
check('sol-41', ['int8'], [new BN(-189)], [new BN(67)]);
|
|
check('sol-42', ['int8'], [new BN(257)], [new BN(1)]);
|
|
|
|
check('sol-43', ['uint8'], [new BN(343)], [new BN(87)]);
|
|
check('sol-44', ['uint8'], [new BN(-1)], [new BN(255)]);
|
|
|
|
check('sol-45', ['uint56[5]'], [[new BN(639), new BN(227), new BN(727), new BN(325), new BN(146)]]);
|
|
|
|
function randomTypeValue(seed, onlyStatic) {
|
|
switch (utils.randomNumber(seed + '-type', 0, (onlyStatic ? 5: 8))) {
|
|
|
|
// Fixed-size bytes
|
|
case 0:
|
|
var size = utils.randomNumber(seed + '-type-0', 1, 33);
|
|
return {
|
|
type: 'bytes' + size,
|
|
value: function(extraSeed) {
|
|
var value = new Buffer(utils.randomBytes(seed + '-' + extraSeed + '-type-0-value', size));
|
|
return {
|
|
value: value,
|
|
normalized: value
|
|
}
|
|
}
|
|
}
|
|
|
|
// uint and int
|
|
case 1:
|
|
var signed = (utils.randomNumber(seed + '-type-1a', 0, 2) === 0);
|
|
var type = (!signed ? 'u': '') + 'int';
|
|
var size = 32;
|
|
if (utils.randomNumber(seed + '-type-1b', 0, 4) > 0) {
|
|
size = utils.randomNumber(seed + '-type-1c', 1, 33)
|
|
type += (8 * size);
|
|
}
|
|
|
|
return {
|
|
type: type,
|
|
value: function(extraSeed) {
|
|
var mask = '';
|
|
for (var i = 0; i < size; i++) { mask += 'ff'; }
|
|
|
|
var value = utils.randomNumber(seed + '-' + extraSeed + '-type-1d', -500, 1000);
|
|
var normalized = (new BN(value)).toTwos(size * 8).and(new BN(mask, 16));
|
|
if (signed) {
|
|
normalized = normalized.fromTwos(size * 8);
|
|
}
|
|
return {
|
|
value: value,
|
|
normalized: normalized
|
|
};
|
|
}
|
|
}
|
|
|
|
// address
|
|
case 2:
|
|
return {
|
|
type: 'address',
|
|
value: function(extraSeed) {
|
|
var value = getAddress(utils.randomHexString(seed + '-' + extraSeed + '-type-2', 20));
|
|
return {
|
|
value: value,
|
|
normalized: value
|
|
};
|
|
}
|
|
}
|
|
|
|
// bool
|
|
case 3:
|
|
return {
|
|
type: 'bool',
|
|
value: function(extraSeed) {
|
|
var value = (utils.randomNumber(seed + '-' + extraSeed + '-type-3', 0, 2) === 0);
|
|
return {
|
|
value: value,
|
|
normalized: value
|
|
};
|
|
}
|
|
}
|
|
|
|
// fixed-length array of subtype
|
|
case 4:
|
|
// @TODO: Support random(0, 6)... Why is that even possible?
|
|
var size = utils.randomNumber(seed + '-type-4a', 1, 6);
|
|
|
|
var subTypeValue = randomTypeValue(seed + '-type-4b', true);
|
|
return {
|
|
type: subTypeValue.type + '[' + size + ']',
|
|
value: function(extraSeed) {
|
|
var values = [];
|
|
var normalized = [];
|
|
for (var i = 0; i < size; i++) {
|
|
var value = subTypeValue.value(seed + '-' + extraSeed + '-4c-' + i);
|
|
values.push(value.value);
|
|
normalized.push(value.normalized);
|
|
}
|
|
return {
|
|
value: values,
|
|
normalized: normalized
|
|
};
|
|
}
|
|
}
|
|
|
|
// bytes
|
|
case 5:
|
|
return {
|
|
type: 'bytes',
|
|
value: function(extraSeed) {
|
|
var size = utils.randomNumber(seed + '-type-5b', 0, 100);
|
|
var value = new Buffer(utils.randomBytes(seed + '-' + extraSeed + '-type-5a', size));
|
|
return {
|
|
value: value,
|
|
normalized: value
|
|
};
|
|
},
|
|
skip: 0
|
|
}
|
|
|
|
// string
|
|
case 6:
|
|
var text = 'abcdefghijklmnopqrstuvwxyz\u2014ABCDEFGHIJKLMNOPQRSTUVWXYZFOOBARfoobar'
|
|
return {
|
|
type: 'string',
|
|
value: function(extraSeed) {
|
|
var size = utils.randomNumber(seed + '-' + extraSeed + '-type-6', 0, 60);
|
|
var value = text.substring(0, size);
|
|
return {
|
|
value: value,
|
|
normalized: value
|
|
};
|
|
}
|
|
}
|
|
|
|
// variable-sized array of subtype
|
|
case 7:
|
|
// @TODO: bug in solidity or VM prevents this from being 0
|
|
var size = utils.randomNumber(seed + '-type-7a', 1, 6);
|
|
var subTypeValue = randomTypeValue(seed + '-type-7b', true);
|
|
return {
|
|
type: subTypeValue.type + '[]',
|
|
value: function(extraSeed) {
|
|
var values = [];
|
|
var normalized = [];
|
|
for (var i = 0; i < size; i++) {
|
|
var value = subTypeValue.value(seed + '-' + extraSeed + '-7c-' + i);
|
|
values.push(value.value);
|
|
normalized.push(value.normalized);
|
|
}
|
|
return {
|
|
value: values,
|
|
normalized: normalized
|
|
};
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// @TODO: Test 0 arguments
|
|
|
|
// Create a bunch of random test cases
|
|
for (var i = 0; i < 2000; i++) {
|
|
var count = utils.randomNumber('count-' + i, 1, 4);
|
|
var types = [], values = [], normalized = [];;
|
|
for (var j = 0; j < count; j++) {
|
|
var type = randomTypeValue('type-' + i + '-' + j);
|
|
types.push(type.type);
|
|
var value = type.value();
|
|
values.push(value.value);
|
|
normalized.push(value.normalized);
|
|
}
|
|
check('random-' + i, types, values, normalized);
|
|
}
|
|
|
|
// Bug in solidity or in the VM, not sure, but this fails
|
|
// check('', ['uint8[4][]'], [ [] ]);
|
|
|
|
promiseRationing.all(promiseFuncs, 100).then(function(result) {
|
|
utils.saveTestcase('contract-interface', result);
|
|
}, function(error) {
|
|
console.log('ERROR', error);
|
|
});
|
|
}
|
|
|
|
makeTests();
|