2016-07-25 10:55:16 +03:00
|
|
|
'use strict';
|
|
|
|
|
2017-02-24 22:41:24 +03:00
|
|
|
|
|
|
|
var utils = (function() {
|
|
|
|
var convert = require('ethers-utils/convert.js');
|
|
|
|
var utf8 = require('ethers-utils/utf8.js');
|
|
|
|
|
|
|
|
return {
|
|
|
|
defineProperty: require('ethers-utils/properties.js').defineProperty,
|
|
|
|
|
|
|
|
arrayify: convert.arrayify,
|
|
|
|
padZeros: convert.padZeros,
|
|
|
|
|
|
|
|
bigNumberify: require('ethers-utils/bignumber.js').bigNumberify,
|
|
|
|
|
|
|
|
concat: convert.concat,
|
|
|
|
|
|
|
|
toUtf8Bytes: utf8.toUtf8Bytes,
|
|
|
|
toUtf8String: utf8.toUtf8String,
|
|
|
|
|
|
|
|
hexlify: convert.hexlify,
|
|
|
|
isHexString: convert.isHexString,
|
|
|
|
|
|
|
|
keccak256: require('ethers-utils/keccak256.js'),
|
|
|
|
};
|
|
|
|
})();
|
2016-07-16 06:47:35 +03:00
|
|
|
|
2016-07-21 11:21:44 +03:00
|
|
|
// Creates property that is immutable
|
2016-07-16 06:47:35 +03:00
|
|
|
function defineFrozen(object, name, value) {
|
|
|
|
var frozen = JSON.stringify(value);
|
|
|
|
Object.defineProperty(object, name, {
|
|
|
|
enumerable: true,
|
|
|
|
get: function() { return JSON.parse(frozen); }
|
|
|
|
});
|
|
|
|
}
|
2017-02-24 22:41:24 +03:00
|
|
|
/*
|
|
|
|
function concat(arrays) {
|
|
|
|
var length = 0;
|
|
|
|
for (var i = 0; i < arrays.length; i++) { length += arrays[i].length; }
|
|
|
|
|
|
|
|
var result = new Uint8Array(length);
|
|
|
|
var offset = 0;
|
|
|
|
for (var i = 0; i < arrays.length; i++) {
|
|
|
|
result.set(arrays[i], offset);
|
|
|
|
offset += arrays[i].length;
|
|
|
|
}
|
|
|
|
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
*/
|
|
|
|
/*
|
|
|
|
// http://stackoverflow.com/questions/18729405/how-to-convert-utf8-string-to-byte-array
|
|
|
|
function utf8ToBytes(str) {
|
|
|
|
var result = [];
|
|
|
|
var offset = 0;
|
|
|
|
for (var i = 0; i < str.length; i++) {
|
|
|
|
var c = str.charCodeAt(i);
|
|
|
|
if (c < 128) {
|
|
|
|
result[offset++] = c;
|
|
|
|
} else if (c < 2048) {
|
|
|
|
result[offset++] = (c >> 6) | 192;
|
|
|
|
result[offset++] = (c & 63) | 128;
|
|
|
|
} else if (((c & 0xFC00) == 0xD800) && (i + 1) < str.length && ((str.charCodeAt(i + 1) & 0xFC00) == 0xDC00)) {
|
|
|
|
// Surrogate Pair
|
|
|
|
c = 0x10000 + ((c & 0x03FF) << 10) + (str.charCodeAt(++i) & 0x03FF);
|
|
|
|
result[offset++] = (c >> 18) | 240;
|
|
|
|
result[offset++] = ((c >> 12) & 63) | 128;
|
|
|
|
result[offset++] = ((c >> 6) & 63) | 128;
|
|
|
|
result[offset++] = (c & 63) | 128;
|
|
|
|
} else {
|
|
|
|
result[offset++] = (c >> 12) | 224;
|
|
|
|
result[offset++] = ((c >> 6) & 63) | 128;
|
|
|
|
result[offset++] = (c & 63) | 128;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return result;
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// http://stackoverflow.com/questions/13356493/decode-utf-8-with-javascript#13691499
|
|
|
|
function bytesToUtf8(bytes) {
|
|
|
|
var result = '';
|
|
|
|
var i = 0;
|
|
|
|
|
|
|
|
// Invalid bytes are ignored
|
|
|
|
while(i < bytes.length) {
|
|
|
|
var c = bytes[i++];
|
|
|
|
if (c >> 7 == 0) {
|
|
|
|
// 0xxx xxxx
|
|
|
|
result += String.fromCharCode(c);
|
|
|
|
continue;
|
|
|
|
}
|
2016-07-16 06:47:35 +03:00
|
|
|
|
2017-02-24 22:41:24 +03:00
|
|
|
// Invalid starting byte
|
|
|
|
if (c >> 6 == 0x02) { continue; }
|
|
|
|
|
|
|
|
// Multibyte; how many bytes left for thus character?
|
|
|
|
var extraLength = null;
|
|
|
|
if (c >> 5 == 0x06) {
|
|
|
|
extraLength = 1;
|
|
|
|
} else if (c >> 4 == 0x0e) {
|
|
|
|
extraLength = 2;
|
|
|
|
} else if (c >> 3 == 0x1e) {
|
|
|
|
extraLength = 3;
|
|
|
|
} else if (c >> 2 == 0x3e) {
|
|
|
|
extraLength = 4;
|
|
|
|
} else if (c >> 1 == 0x7e) {
|
|
|
|
extraLength = 5;
|
|
|
|
} else {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Do we have enough bytes in our data?
|
|
|
|
if (i + extraLength > bytes.length) {
|
|
|
|
|
|
|
|
// If there is an invalid unprocessed byte, try to continue
|
|
|
|
for (; i < bytes.length; i++) {
|
|
|
|
if (bytes[i] >> 6 != 0x02) { break; }
|
|
|
|
}
|
|
|
|
if (i != bytes.length) continue;
|
|
|
|
|
|
|
|
// All leftover bytes are valid.
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Remove the UTF-8 prefix from the char (res)
|
|
|
|
var res = c & ((1 << (8 - extraLength - 1)) - 1);
|
|
|
|
|
|
|
|
var count;
|
|
|
|
for (count = 0; count < extraLength; count++) {
|
|
|
|
var nextChar = bytes[i++];
|
|
|
|
|
|
|
|
// Is the char valid multibyte part?
|
|
|
|
if (nextChar >> 6 != 0x02) {break;};
|
|
|
|
res = (res << 6) | (nextChar & 0x3f);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (count != extraLength) {
|
|
|
|
i--;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (res <= 0xffff) {
|
|
|
|
result += String.fromCharCode(res);
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
res -= 0x10000;
|
|
|
|
result += String.fromCharCode(((res >> 10) & 0x3ff) + 0xd800, (res & 0x3ff) + 0xdc00);
|
|
|
|
}
|
|
|
|
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
*/
|
2016-07-21 11:21:44 +03:00
|
|
|
// getKeys([{a: 1, b: 2}, {a: 3, b: 4}], 'a') => [1, 3]
|
2016-11-10 01:27:34 +03:00
|
|
|
function getKeys(params, key, allowEmpty) {
|
2016-07-16 06:47:35 +03:00
|
|
|
if (!Array.isArray(params)) { throw new Error('invalid params'); }
|
|
|
|
|
|
|
|
var result = [];
|
|
|
|
|
|
|
|
for (var i = 0; i < params.length; i++) {
|
2016-11-10 01:27:34 +03:00
|
|
|
var value = params[i][key];
|
|
|
|
if (allowEmpty && !value) {
|
|
|
|
value = '';
|
|
|
|
} else if (typeof(value) !== 'string') {
|
|
|
|
throw new Error('invalid abi');
|
|
|
|
}
|
|
|
|
result.push(value);
|
2016-07-16 06:47:35 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
2016-07-21 11:21:44 +03:00
|
|
|
function coderNumber(size, signed) {
|
2016-07-21 01:06:03 +03:00
|
|
|
return {
|
|
|
|
encode: function(value) {
|
2017-02-24 22:41:24 +03:00
|
|
|
value = utils.bigNumberify(value).toTwos(size * 8).maskn(size * 8);
|
|
|
|
//value = value.toTwos(size * 8).maskn(size * 8);
|
2016-07-29 11:45:46 +03:00
|
|
|
if (signed) {
|
2016-08-02 00:57:00 +03:00
|
|
|
value = value.fromTwos(size * 8).toTwos(256);
|
2016-07-29 11:45:46 +03:00
|
|
|
}
|
2017-02-24 22:41:24 +03:00
|
|
|
return utils.padZeros(utils.arrayify(value), 32);
|
2016-07-21 01:06:03 +03:00
|
|
|
},
|
|
|
|
decode: function(data, offset) {
|
2016-08-02 01:26:12 +03:00
|
|
|
var junkLength = 32 - size;
|
2017-02-24 22:41:24 +03:00
|
|
|
var value = utils.bigNumberify(data.slice(offset + junkLength, offset + 32));
|
2016-07-29 11:45:46 +03:00
|
|
|
if (signed) {
|
|
|
|
value = value.fromTwos(size * 8);
|
|
|
|
} else {
|
2016-11-10 00:39:33 +03:00
|
|
|
value = value.maskn(size * 8);
|
2016-07-29 11:45:46 +03:00
|
|
|
}
|
2016-07-21 01:06:03 +03:00
|
|
|
return {
|
|
|
|
consumed: 32,
|
|
|
|
value: value,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
}
|
2016-07-21 11:21:44 +03:00
|
|
|
var uint256Coder = coderNumber(32, false);
|
|
|
|
|
|
|
|
var coderBoolean = {
|
|
|
|
encode: function(value) {
|
|
|
|
return uint256Coder.encode(value ? 1: 0);
|
|
|
|
},
|
|
|
|
decode: function(data, offset) {
|
|
|
|
var result = uint256Coder.decode(data, offset);
|
|
|
|
return {
|
|
|
|
consumed: result.consumed,
|
|
|
|
value: !result.value.isZero()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2016-07-21 01:06:03 +03:00
|
|
|
|
|
|
|
function coderFixedBytes(length) {
|
|
|
|
return {
|
|
|
|
encode: function(value) {
|
2017-02-24 22:41:24 +03:00
|
|
|
value = utils.arrayify(value);
|
2016-07-21 01:06:03 +03:00
|
|
|
if (length === 32) { return value; }
|
|
|
|
|
2017-02-24 22:41:24 +03:00
|
|
|
var result = new Uint8Array(32);
|
|
|
|
result.set(value);
|
2016-07-21 01:06:03 +03:00
|
|
|
return result;
|
|
|
|
},
|
|
|
|
decode: function(data, offset) {
|
|
|
|
if (data.length < offset + 32) { throw new Error('invalid bytes' + length); }
|
|
|
|
|
|
|
|
return {
|
|
|
|
consumed: 32,
|
2017-02-24 22:41:24 +03:00
|
|
|
value: data.slice(offset, offset + length)
|
2016-07-21 01:06:03 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
var coderAddress = {
|
|
|
|
encode: function(value) {
|
2017-02-24 22:41:24 +03:00
|
|
|
if (!utils.isHexString(value) && value.length === 42) { throw new Error('invalid address'); }
|
|
|
|
value = utils.arrayify(value);
|
|
|
|
var result = new Uint8Array(32);
|
|
|
|
result.set(value, 12);
|
2016-07-21 01:06:03 +03:00
|
|
|
return result;
|
|
|
|
},
|
|
|
|
decode: function(data, offset) {
|
|
|
|
if (data.length < offset + 32) { throw new Error('invalid address'); }
|
|
|
|
return {
|
|
|
|
consumed: 32,
|
2017-02-24 22:41:24 +03:00
|
|
|
value: utils.hexlify(data.slice(offset + 12, offset + 32))
|
2016-07-21 01:06:03 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
function _encodeDynamicBytes(value) {
|
|
|
|
var dataLength = parseInt(32 * Math.ceil(value.length / 32));
|
2017-02-24 22:41:24 +03:00
|
|
|
var padding = new Uint8Array(dataLength - value.length);
|
2016-07-21 01:06:03 +03:00
|
|
|
|
2017-02-24 22:41:24 +03:00
|
|
|
return utils.concat([
|
2016-07-21 11:21:44 +03:00
|
|
|
uint256Coder.encode(value.length),
|
2016-07-21 01:06:03 +03:00
|
|
|
value,
|
|
|
|
padding
|
|
|
|
]);
|
|
|
|
}
|
|
|
|
|
|
|
|
function _decodeDynamicBytes(data, offset) {
|
|
|
|
if (data.length < offset + 32) { throw new Error('invalid bytes'); }
|
|
|
|
|
2016-07-21 11:21:44 +03:00
|
|
|
var length = uint256Coder.decode(data, offset).value;
|
2016-07-21 01:06:03 +03:00
|
|
|
length = length.toNumber();
|
|
|
|
if (data.length < offset + 32 + length) { throw new Error('invalid bytes'); }
|
|
|
|
|
|
|
|
return {
|
|
|
|
consumed: parseInt(32 + 32 * Math.ceil(length / 32)),
|
|
|
|
value: data.slice(offset + 32, offset + 32 + length),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
var coderDynamicBytes = {
|
|
|
|
encode: function(value) {
|
2017-02-24 22:41:24 +03:00
|
|
|
return _encodeDynamicBytes(utils.arrayify(value));
|
2016-07-21 01:06:03 +03:00
|
|
|
},
|
|
|
|
decode: function(data, offset) {
|
|
|
|
var result = _decodeDynamicBytes(data, offset);
|
2017-02-24 22:41:24 +03:00
|
|
|
result.value = result.value;
|
2016-07-21 01:06:03 +03:00
|
|
|
return result;
|
|
|
|
},
|
|
|
|
dynamic: true
|
|
|
|
};
|
|
|
|
|
|
|
|
var coderString = {
|
|
|
|
encode: function(value) {
|
2017-02-24 22:41:24 +03:00
|
|
|
return _encodeDynamicBytes(utils.toUtf8Bytes(value));
|
2016-07-21 01:06:03 +03:00
|
|
|
},
|
|
|
|
decode: function(data, offset) {
|
|
|
|
var result = _decodeDynamicBytes(data, offset);
|
2017-02-24 22:41:24 +03:00
|
|
|
result.value = utils.toUtf8String(result.value);
|
2016-07-21 01:06:03 +03:00
|
|
|
return result;
|
|
|
|
},
|
|
|
|
dynamic: true
|
|
|
|
};
|
|
|
|
|
|
|
|
function coderArray(coder, length) {
|
|
|
|
return {
|
|
|
|
encode: function(value) {
|
|
|
|
if (!Array.isArray(value)) { throw new Error('invalid array'); }
|
|
|
|
|
2017-02-24 22:41:24 +03:00
|
|
|
var result = new Uint8Array(0);
|
2016-07-21 01:06:03 +03:00
|
|
|
if (length === -1) {
|
|
|
|
length = value.length;
|
2016-07-21 11:21:44 +03:00
|
|
|
result = uint256Coder.encode(length);
|
2016-07-21 01:06:03 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
if (length !== value.length) { throw new Error('size mismatch'); }
|
|
|
|
|
|
|
|
value.forEach(function(value) {
|
2017-02-24 22:41:24 +03:00
|
|
|
result = utils.concat([result, coder.encode(value)]);
|
2016-07-21 01:06:03 +03:00
|
|
|
});
|
|
|
|
|
|
|
|
return result;
|
|
|
|
},
|
|
|
|
decode: function(data, offset) {
|
|
|
|
// @TODO:
|
|
|
|
//if (data.length < offset + length * 32) { throw new Error('invalid array'); }
|
|
|
|
|
|
|
|
var consumed = 0;
|
|
|
|
|
|
|
|
var result;
|
|
|
|
if (length === -1) {
|
2016-07-21 11:21:44 +03:00
|
|
|
result = uint256Coder.decode(data, offset);
|
2016-07-21 01:06:03 +03:00
|
|
|
length = result.value.toNumber();
|
|
|
|
consumed += result.consumed;
|
|
|
|
offset += result.consumed;
|
|
|
|
}
|
|
|
|
|
|
|
|
var value = [];
|
|
|
|
|
|
|
|
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,
|
|
|
|
}
|
|
|
|
},
|
|
|
|
dynamic: (length === -1)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-07-21 11:21:44 +03:00
|
|
|
// 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) { throw new Error('invalid type: ' + type); }
|
|
|
|
type = type.substring(part[0].length);
|
|
|
|
|
|
|
|
var prefix = (part[2] || part[4] || part[5]);
|
|
|
|
switch (prefix) {
|
|
|
|
case 'int': case 'uint':
|
|
|
|
if (coder) { throw new Error('invalid type ' + type); }
|
|
|
|
var size = parseInt(part[3] || 256);
|
|
|
|
if (size === 0 || size > 256 || (size % 8) !== 0) {
|
|
|
|
throw new Error('invalid type ' + type);
|
|
|
|
}
|
|
|
|
coder = coderNumber(size / 8, (prefix === 'int'));
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 'bool':
|
|
|
|
if (coder) { throw new Error('invalid type ' + type); }
|
|
|
|
coder = coderBoolean;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 'string':
|
|
|
|
if (coder) { throw new Error('invalid type ' + type); }
|
|
|
|
coder = coderString;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 'bytes':
|
|
|
|
if (coder) { throw new Error('invalid type ' + type); }
|
|
|
|
if (part[3]) {
|
|
|
|
var size = parseInt(part[3]);
|
|
|
|
if (size === 0 || size > 32) {
|
|
|
|
throw new Error('invalid type ' + type);
|
|
|
|
}
|
|
|
|
coder = coderFixedBytes(size);
|
|
|
|
} else {
|
|
|
|
coder = coderDynamicBytes;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 'address':
|
|
|
|
if (coder) { throw new Error('invalid type ' + type); }
|
|
|
|
coder = coderAddress;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case '[]':
|
|
|
|
if (!coder || coder.dynamic) { throw new Error('invalid type ' + type); }
|
|
|
|
coder = coderArray(coder, -1);
|
|
|
|
break;
|
|
|
|
|
|
|
|
// "[0-9+]"
|
|
|
|
default:
|
|
|
|
if (!coder || coder.dynamic) { throw new Error('invalid type ' + type); }
|
|
|
|
var size = parseInt(part[6]);
|
|
|
|
coder = coderArray(coder, size);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!coder) { throw new Error('invalid type'); }
|
|
|
|
return coder;
|
|
|
|
}
|
|
|
|
|
2016-11-10 01:27:34 +03:00
|
|
|
function populateDescription(object, items) {
|
|
|
|
for (var key in items) {
|
|
|
|
utils.defineProperty(object, key, items[key]);
|
|
|
|
}
|
|
|
|
return object;
|
|
|
|
}
|
|
|
|
|
|
|
|
function CallDescription() { }
|
|
|
|
utils.defineProperty(CallDescription.prototype, 'type', 'call');
|
|
|
|
|
|
|
|
function TransactionDescription() { }
|
|
|
|
utils.defineProperty(TransactionDescription.prototype, 'type', 'transaction');
|
|
|
|
|
|
|
|
function EventDescription() { }
|
|
|
|
utils.defineProperty(EventDescription.prototype, 'type', 'event');
|
|
|
|
|
|
|
|
function UnsupportedDescription() { }
|
|
|
|
utils.defineProperty(UnsupportedDescription.prototype, 'type', 'unknown');
|
|
|
|
|
|
|
|
|
2016-07-21 11:21:44 +03:00
|
|
|
function Interface(abi) {
|
|
|
|
if (!(this instanceof Interface)) { throw new Error('missing new'); }
|
|
|
|
|
|
|
|
//defineProperty(this, 'address', address);
|
|
|
|
|
|
|
|
// Wrap this up as JSON so we can return a "copy" and avoid mutation
|
|
|
|
defineFrozen(this, 'abi', abi);
|
|
|
|
|
|
|
|
var methods = [], events = [];
|
|
|
|
abi.forEach(function(method) {
|
|
|
|
|
2016-07-25 10:55:16 +03:00
|
|
|
var func = null;
|
|
|
|
|
2016-07-21 11:21:44 +03:00
|
|
|
switch (method.type) {
|
|
|
|
case 'function':
|
|
|
|
methods.push(method.name);
|
|
|
|
func = (function() {
|
|
|
|
var inputTypes = getKeys(method.inputs, 'type');
|
|
|
|
var outputTypes = getKeys(method.outputs, 'type');
|
2016-11-10 01:27:34 +03:00
|
|
|
var outputNames = getKeys(method.outputs, 'name', true);
|
2016-07-21 11:21:44 +03:00
|
|
|
|
|
|
|
var func = function() {
|
|
|
|
var signature = method.name + '(' + getKeys(method.inputs, 'type').join(',') + ')';
|
|
|
|
var result = {
|
|
|
|
name: method.name,
|
|
|
|
signature: signature,
|
|
|
|
};
|
|
|
|
|
|
|
|
var params = Array.prototype.slice.call(arguments, 0);
|
|
|
|
|
|
|
|
if (params.length < inputTypes.length) {
|
|
|
|
throw new Error('missing parameter');
|
|
|
|
} else if (params.length > inputTypes.length) {
|
|
|
|
throw new Error('too many parameters');
|
|
|
|
}
|
|
|
|
|
2017-02-24 22:41:24 +03:00
|
|
|
signature = utils.keccak256(utils.toUtf8Bytes(signature)).substring(0, 10);
|
2016-07-21 11:21:44 +03:00
|
|
|
|
2016-07-22 21:49:26 +03:00
|
|
|
result.data = signature + Interface.encodeParams(inputTypes, params).substring(2);
|
2016-07-21 11:21:44 +03:00
|
|
|
if (method.constant) {
|
|
|
|
result.parse = function(data) {
|
|
|
|
return Interface.decodeParams(
|
2016-11-10 01:27:34 +03:00
|
|
|
outputNames,
|
2016-07-21 11:21:44 +03:00
|
|
|
outputTypes,
|
2017-02-24 22:41:24 +03:00
|
|
|
utils.arrayify(data)
|
2016-07-29 11:45:46 +03:00
|
|
|
);
|
2016-07-21 11:21:44 +03:00
|
|
|
};
|
2016-11-10 01:27:34 +03:00
|
|
|
return populateDescription(new CallDescription(), result);
|
2016-07-21 11:21:44 +03:00
|
|
|
}
|
|
|
|
|
2016-11-10 01:27:34 +03:00
|
|
|
return populateDescription(new TransactionDescription(), result);
|
2016-07-21 11:21:44 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
defineFrozen(func, 'inputs', getKeys(method.inputs, 'name'));
|
|
|
|
defineFrozen(func, 'outputs', getKeys(method.outputs, 'name'));
|
|
|
|
|
|
|
|
return func;
|
|
|
|
})();
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 'event':
|
|
|
|
events.push(method.name);
|
|
|
|
func = (function() {
|
|
|
|
var inputTypes = getKeys(method.inputs, 'type');
|
2016-11-10 01:27:34 +03:00
|
|
|
var inputNames = getKeys(method.inputs, 'name', true);
|
2016-07-21 11:21:44 +03:00
|
|
|
var func = function() {
|
|
|
|
var signature = method.name + '(' + getKeys(method.inputs, 'type').join(',') + ')';
|
|
|
|
var result = {
|
|
|
|
inputs: method.inputs,
|
|
|
|
name: method.name,
|
|
|
|
signature: signature,
|
2017-02-24 22:41:24 +03:00
|
|
|
topics: [utils.keccak256(utils.toUtf8Bytes(signature))],
|
2016-07-21 11:21:44 +03:00
|
|
|
};
|
|
|
|
result.parse = function(data) {
|
|
|
|
return Interface.decodeParams(
|
2016-11-10 01:27:34 +03:00
|
|
|
inputNames,
|
2016-07-21 11:21:44 +03:00
|
|
|
inputTypes,
|
2017-02-24 22:41:24 +03:00
|
|
|
utils.arrayify(data)
|
2016-07-21 11:21:44 +03:00
|
|
|
);
|
|
|
|
};
|
2016-11-10 01:27:34 +03:00
|
|
|
return populateDescription(new EventDescription(), result);
|
2016-07-21 11:21:44 +03:00
|
|
|
}
|
|
|
|
defineFrozen(func, 'inputs', getKeys(method.inputs, 'name'));
|
|
|
|
return func;
|
|
|
|
})();
|
|
|
|
break;
|
|
|
|
|
|
|
|
default:
|
|
|
|
func = (function() {
|
|
|
|
return function() {
|
2016-11-10 01:27:34 +03:00
|
|
|
return populateDescription(new UnsupportedDescription(), {});
|
2016-07-21 11:21:44 +03:00
|
|
|
}
|
|
|
|
})();
|
|
|
|
break;
|
|
|
|
}
|
2016-07-25 10:55:16 +03:00
|
|
|
|
2017-02-03 01:06:07 +03:00
|
|
|
if (method.name) {
|
|
|
|
utils.defineProperty(this, method.name, func);
|
|
|
|
}
|
2016-07-21 11:21:44 +03:00
|
|
|
}, this);
|
|
|
|
|
|
|
|
defineFrozen(this, 'methods', methods);
|
|
|
|
defineFrozen(this, 'events', events);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
utils.defineProperty(Interface, 'encodeParams', function(types, values) {
|
2016-07-21 01:06:03 +03:00
|
|
|
if (types.length !== values.length) { throw new Error('types/values mismatch'); }
|
|
|
|
|
|
|
|
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 offset = 0, dynamicOffset = staticSize;
|
2017-02-24 22:41:24 +03:00
|
|
|
var data = new Uint8Array(staticSize + dynamicSize);
|
2016-07-21 01:06:03 +03:00
|
|
|
|
|
|
|
parts.forEach(function(part, index) {
|
|
|
|
if (part.dynamic) {
|
2017-02-24 22:41:24 +03:00
|
|
|
//uint256Coder.encode(dynamicOffset).copy(data, offset);
|
|
|
|
data.set(uint256Coder.encode(dynamicOffset), offset);
|
2016-07-21 01:06:03 +03:00
|
|
|
offset += 32;
|
|
|
|
|
2017-02-24 22:41:24 +03:00
|
|
|
//part.value.copy(data, dynamicOffset); @TODO
|
|
|
|
data.set(part.value, dynamicOffset);
|
2016-07-21 01:06:03 +03:00
|
|
|
dynamicOffset += alignSize(part.value.length);
|
|
|
|
} else {
|
2017-02-24 22:41:24 +03:00
|
|
|
//part.value.copy(data, offset); @TODO
|
|
|
|
data.set(part.value, offset);
|
2016-07-21 01:06:03 +03:00
|
|
|
offset += alignSize(part.value.length);
|
|
|
|
}
|
|
|
|
});
|
2017-02-24 22:41:24 +03:00
|
|
|
return utils.hexlify(data);
|
2016-07-21 01:06:03 +03:00
|
|
|
});
|
|
|
|
|
2017-02-24 22:41:24 +03:00
|
|
|
|
2016-11-10 01:27:34 +03:00
|
|
|
function Result() {}
|
|
|
|
|
|
|
|
utils.defineProperty(Interface, 'decodeParams', function(names, types, data) {
|
2016-11-22 01:46:36 +03:00
|
|
|
|
|
|
|
// Names is optional, so shift over all the parameters if not provided
|
|
|
|
if (arguments.length < 3) {
|
|
|
|
data = types;
|
|
|
|
types = names;
|
|
|
|
names = [];
|
|
|
|
}
|
|
|
|
|
2017-02-24 22:41:24 +03:00
|
|
|
data = utils.arrayify(data);
|
2016-11-10 01:27:34 +03:00
|
|
|
var values = new Result();
|
2016-07-21 01:06:03 +03:00
|
|
|
|
|
|
|
var offset = 0;
|
2016-11-10 01:27:34 +03:00
|
|
|
types.forEach(function(type, index) {
|
2016-07-21 01:06:03 +03:00
|
|
|
var coder = getParamCoder(type);
|
|
|
|
if (coder.dynamic) {
|
2016-07-21 11:21:44 +03:00
|
|
|
var dynamicOffset = uint256Coder.decode(data, offset);
|
2016-07-21 01:06:03 +03:00
|
|
|
var result = coder.decode(data, dynamicOffset.value.toNumber());
|
|
|
|
offset += dynamicOffset.consumed;
|
|
|
|
} else {
|
|
|
|
var result = coder.decode(data, offset);
|
|
|
|
offset += result.consumed;
|
|
|
|
}
|
2016-11-10 01:27:34 +03:00
|
|
|
values[index] = result.value;
|
|
|
|
if (names[index]) { values[names[index]] = result.value; }
|
2016-07-21 01:06:03 +03:00
|
|
|
});
|
|
|
|
return values;
|
|
|
|
});
|
|
|
|
|
2016-07-21 11:21:44 +03:00
|
|
|
|
|
|
|
var allowedTransactionKeys = {
|
|
|
|
data: true, from: true, gasLimit: true, gasPrice:true, to: true, value: true
|
|
|
|
}
|
|
|
|
|
2016-08-03 00:43:18 +03:00
|
|
|
function Contract(wallet, contractAddress, contractInterface) {
|
2016-07-21 11:21:44 +03:00
|
|
|
utils.defineProperty(this, 'wallet', wallet);
|
2016-07-27 00:57:11 +03:00
|
|
|
|
2016-07-21 11:21:44 +03:00
|
|
|
utils.defineProperty(this, 'contractAddress', contractAddress);
|
2016-07-25 10:55:16 +03:00
|
|
|
utils.defineProperty(this, 'interface', contractInterface);
|
2016-08-03 00:43:18 +03:00
|
|
|
|
2016-07-21 11:21:44 +03:00
|
|
|
var self = this;
|
|
|
|
|
|
|
|
var filters = {};
|
|
|
|
function setupFilter(call, callback) {
|
|
|
|
var info = filters[call.name];
|
|
|
|
|
|
|
|
// Stop and remove the filter
|
|
|
|
if (!callback) {
|
|
|
|
if (info) { info.filter.stopWatching(); }
|
|
|
|
delete filters[call.name];
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (typeof(callback) !== 'function') {
|
|
|
|
throw new Error('invalid callback');
|
|
|
|
}
|
|
|
|
|
|
|
|
// Already have a filter, just update the callback
|
|
|
|
if (info) {
|
|
|
|
info.callback = callback;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
info = {callback: callback};
|
|
|
|
filters[call.name] = info;
|
|
|
|
|
|
|
|
// Start a new filter
|
2016-07-27 00:57:11 +03:00
|
|
|
/*
|
2016-07-21 11:21:44 +03:00
|
|
|
info.filter = web3.eth.filter({
|
|
|
|
address: contractAddress,
|
|
|
|
topics: call.topics
|
|
|
|
}, function(error, result) {
|
|
|
|
// @TODO: Emit errors to .onerror? Maybe?
|
|
|
|
if (error) {
|
|
|
|
console.log(error);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
try {
|
|
|
|
info.callback.apply(self, call.parse(result.data));
|
|
|
|
} catch(error) {
|
|
|
|
console.log(error);
|
|
|
|
}
|
|
|
|
});
|
2016-07-27 00:57:11 +03:00
|
|
|
*/
|
2016-07-21 11:21:44 +03:00
|
|
|
}
|
2016-08-03 00:43:18 +03:00
|
|
|
function runMethod(method, estimateOnly) {
|
2016-07-21 11:21:44 +03:00
|
|
|
return function() {
|
2016-08-03 00:43:18 +03:00
|
|
|
var provider = wallet._provider;
|
|
|
|
|
2016-07-21 11:21:44 +03:00
|
|
|
var transaction = {}
|
|
|
|
|
|
|
|
var params = Array.prototype.slice.call(arguments);
|
2016-07-25 10:55:16 +03:00
|
|
|
if (params.length == contractInterface[method].inputs.length + 1) {
|
2016-07-21 11:21:44 +03:00
|
|
|
transaction = params.pop();
|
|
|
|
if (typeof(transaction) !== 'object') {
|
|
|
|
throw new Error('invalid transaction overrides');
|
|
|
|
}
|
|
|
|
for (var key in transaction) {
|
|
|
|
if (!allowedTransactionKeys[key]) {
|
|
|
|
throw new Error('unknown transaction override ' + key);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-07-27 00:57:11 +03:00
|
|
|
|
2016-07-25 10:55:16 +03:00
|
|
|
var call = contractInterface[method].apply(contractInterface, params);
|
2016-07-21 11:21:44 +03:00
|
|
|
switch (call.type) {
|
|
|
|
case 'call':
|
|
|
|
['data', 'gasLimit', 'gasPrice', 'to', 'value'].forEach(function(key) {
|
|
|
|
if (transaction[key] != null) {
|
|
|
|
throw new Error('call cannot override ' + key) ;
|
|
|
|
}
|
|
|
|
});
|
|
|
|
transaction.data = call.data;
|
|
|
|
if (transaction.from == null) {
|
|
|
|
transaction.from = wallet.address;
|
|
|
|
}
|
|
|
|
transaction.to = contractAddress;
|
|
|
|
|
2016-08-03 00:43:18 +03:00
|
|
|
if (estimateOnly) {
|
|
|
|
return new Promise(function(resolve, reject) {
|
2017-02-24 22:41:24 +03:00
|
|
|
resolve(new utils.bigNumberify(0));
|
2016-08-03 00:43:18 +03:00
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2016-07-21 11:21:44 +03:00
|
|
|
return new Promise(function(resolve, reject) {
|
2016-08-03 00:43:18 +03:00
|
|
|
provider.call(transaction).then(function(value) {
|
2016-07-21 11:21:44 +03:00
|
|
|
resolve(call.parse(value));
|
|
|
|
}, function(error) {
|
|
|
|
reject(error);
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
case 'transaction':
|
|
|
|
['data', 'from', 'to'].forEach(function(key) {
|
|
|
|
if (transaction[key] != null) {
|
|
|
|
throw new Error('transaction cannot override ' + key) ;
|
|
|
|
}
|
|
|
|
});
|
|
|
|
transaction.data = call.data;
|
|
|
|
transaction.to = contractAddress;
|
|
|
|
if (transaction.gasLimit == null) {
|
|
|
|
transaction.gasLimit = 3000000;
|
|
|
|
}
|
|
|
|
|
2016-08-03 00:43:18 +03:00
|
|
|
if (estimateOnly) {
|
|
|
|
return new Promise(function(resolve, reject) {
|
|
|
|
provider.estimateGas(transaction).then(function(gasEstimate) {
|
2016-08-03 09:26:36 +03:00
|
|
|
resolve(gasEstimate);
|
2016-08-03 00:43:18 +03:00
|
|
|
}, function(error) {
|
|
|
|
reject(error);
|
|
|
|
});
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2016-07-21 11:21:44 +03:00
|
|
|
return new Promise(function(resolve, reject) {
|
|
|
|
Promise.all([
|
2016-08-03 00:43:18 +03:00
|
|
|
provider.getTransactionCount(wallet.address, 'pending'),
|
|
|
|
provider.getGasPrice(),
|
2016-07-21 11:21:44 +03:00
|
|
|
]).then(function(results) {
|
|
|
|
if (transaction.nonce == null) {
|
|
|
|
transaction.nonce = results[0];
|
|
|
|
} else if (console.warn) {
|
|
|
|
console.warn('Overriding suggested nonce: ' + results[0]);
|
|
|
|
}
|
|
|
|
if (transaction.gasPrice == null) {
|
|
|
|
transaction.gasPrice = results[1];
|
|
|
|
} else if (console.warn) {
|
|
|
|
console.warn('Overriding suggested gasPrice: ' + utils.hexlify(results[1]));
|
|
|
|
}
|
2016-07-27 00:57:11 +03:00
|
|
|
|
2016-07-21 11:21:44 +03:00
|
|
|
var signedTransaction = wallet.sign(transaction);
|
2016-08-03 00:43:18 +03:00
|
|
|
provider.sendTransaction(signedTransaction).then(function(txid) {
|
2016-07-21 11:21:44 +03:00
|
|
|
resolve(txid);
|
|
|
|
}, function(error) {
|
|
|
|
reject(error);
|
|
|
|
});
|
|
|
|
}, function(error) {
|
|
|
|
reject(error);
|
|
|
|
});
|
|
|
|
});
|
|
|
|
}
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2016-08-03 00:43:18 +03:00
|
|
|
var estimate = {};
|
|
|
|
utils.defineProperty(this, 'estimate', estimate);
|
|
|
|
|
2016-07-25 10:55:16 +03:00
|
|
|
contractInterface.methods.forEach(function(method) {
|
2016-08-03 00:43:18 +03:00
|
|
|
utils.defineProperty(this, method, runMethod(method, false));
|
|
|
|
utils.defineProperty(estimate, method, runMethod(method, true));
|
2016-07-21 11:21:44 +03:00
|
|
|
}, this);
|
|
|
|
|
2016-07-25 10:55:16 +03:00
|
|
|
contractInterface.events.forEach(function(method) {
|
|
|
|
var call = contractInterface[method].apply(contractInterface, []);
|
2016-07-21 11:21:44 +03:00
|
|
|
Object.defineProperty(self, 'on' + call.name.toLowerCase(), {
|
|
|
|
enumerable: true,
|
|
|
|
get: function() {
|
|
|
|
var info = filters[call.name];
|
|
|
|
if (!info || !info[call.name]) { return null; }
|
|
|
|
return info.callback;
|
|
|
|
},
|
|
|
|
set: function(value) {
|
|
|
|
setupFilter(call, value);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}, this);
|
|
|
|
}
|
|
|
|
utils.defineProperty(Contract, 'Interface', Interface);
|
|
|
|
|
2016-07-16 06:47:35 +03:00
|
|
|
module.exports = Contract;
|