Merge branch 'master' of github.com:ethers-io/ethers.js

This commit is contained in:
Richard Moore 2017-10-27 01:16:10 -04:00
commit 3c9f6f6ab9
No known key found for this signature in database
GPG Key ID: 525F70A6FCABC295
11 changed files with 338 additions and 38 deletions

View File

@ -383,11 +383,13 @@ function Interface(abi) {
var outputNames = getKeys(method.outputs, 'name', true); var outputNames = getKeys(method.outputs, 'name', true);
} }
var signature = method.name + '(' + getKeys(method.inputs, 'type').join(',') + ')';
var sighash = utils.keccak256(utils.toUtf8Bytes(signature)).substring(0, 10);
var func = function() { var func = function() {
var signature = method.name + '(' + getKeys(method.inputs, 'type').join(',') + ')';
var result = { var result = {
name: method.name, name: method.name,
signature: signature, signature: signature,
sighash: sighash
}; };
var params = Array.prototype.slice.call(arguments, 0); var params = Array.prototype.slice.call(arguments, 0);
@ -398,9 +400,7 @@ function Interface(abi) {
throwError('too many parameters'); throwError('too many parameters');
} }
signature = utils.keccak256(utils.toUtf8Bytes(signature)).substring(0, 10); result.data = sighash + Interface.encodeParams(inputTypes, params).substring(2);
result.data = signature + Interface.encodeParams(inputTypes, params).substring(2);
if (method.constant) { if (method.constant) {
result.parse = function(data) { result.parse = function(data) {
return Interface.decodeParams( return Interface.decodeParams(
@ -417,6 +417,8 @@ function Interface(abi) {
defineFrozen(func, 'inputs', getKeys(method.inputs, 'name')); defineFrozen(func, 'inputs', getKeys(method.inputs, 'name'));
defineFrozen(func, 'outputs', getKeys(method.outputs, 'name')); defineFrozen(func, 'outputs', getKeys(method.outputs, 'name'));
utils.defineProperty(func, 'signature', signature);
utils.defineProperty(func, 'sighash', sighash);
return func; return func;
})(); })();
@ -487,7 +489,9 @@ function Interface(abi) {
}; };
return populateDescription(new EventDescription(), result); return populateDescription(new EventDescription(), result);
} }
defineFrozen(func, 'inputs', getKeys(method.inputs, 'name')); defineFrozen(func, 'inputs', getKeys(method.inputs, 'name'));
return func; return func;
})(); })();

View File

@ -1,6 +1,6 @@
{ {
"name": "ethers-contracts", "name": "ethers-contracts",
"version": "2.1.2", "version": "2.1.3",
"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",

View File

@ -14,6 +14,7 @@ module.exports = {
Contract: contracts.Contract, Contract: contracts.Contract,
Interface: contracts.Interface, Interface: contracts.Interface,
networks: providers.networks,
providers: providers, providers: providers,
utils: utils, utils: utils,

View File

@ -11,17 +11,48 @@ var utils = (function() {
}; };
})(); })();
// @TODO: Add this to utils; lots of things need this now
function stripHexZeros(value) {
while (value.length > 3 && value.substring(0, 3) === '0x0') {
value = '0x' + value.substring(3);
}
return value;
}
function getTransactionString(transaction) { function getTransactionString(transaction) {
var result = []; var result = [];
for (var key in transaction) { for (var key in transaction) {
if (transaction[key] == null) { continue; } if (transaction[key] == null) { continue; }
result.push(key + '=' + utils.hexlify(transaction[key])); var value = utils.hexlify(transaction[key]);
if ({ gasLimit: true, gasPrice: true, nonce: true, value: true }[key]) {
value = stripHexZeros(value);
}
result.push(key + '=' + value);
} }
return result.join('&'); return result.join('&');
} }
function EtherscanProvider(testnet, apiKey) { function EtherscanProvider(network, apiKey) {
Provider.call(this, testnet); Provider.call(this, network);
var baseUrl = null;
switch(this.name) {
case 'homestead':
baseUrl = 'https://api.etherscan.io';
break;
case 'ropsten':
baseUrl = 'https://ropsten.etherscan.io';
break;
case 'rinkeby':
baseUrl = 'https://rinkeby.etherscan.io';
break;
case 'kovan':
baseUrl = 'https://kovan.etherscan.io';
break;
default:
throw new Error('unsupported network');
}
utils.defineProperty(this, 'baseUrl', baseUrl);
utils.defineProperty(this, 'apiKey', apiKey || null); utils.defineProperty(this, 'apiKey', apiKey || null);
} }
@ -72,10 +103,11 @@ function checkLogTag(blockTag) {
return parseInt(blockTag.substring(2), 16); return parseInt(blockTag.substring(2), 16);
} }
utils.defineProperty(EtherscanProvider.prototype, 'perform', function(method, params) { utils.defineProperty(EtherscanProvider.prototype, 'perform', function(method, params) {
if (!params) { params = {}; } if (!params) { params = {}; }
var url = this.testnet ? 'https://ropsten.etherscan.io': 'https://api.etherscan.io'; var url = this.baseUrl;
var apiKey = ''; var apiKey = '';
if (this.apiKey) { apiKey += '&apikey=' + this.apiKey; } if (this.apiKey) { apiKey += '&apikey=' + this.apiKey; }
@ -108,8 +140,8 @@ utils.defineProperty(EtherscanProvider.prototype, 'perform', function(method, pa
case 'getStorageAt': case 'getStorageAt':
url += '/api?module=proxy&action=eth_getStorageAt&address=' + params.address; url += '/api?module=proxy&action=eth_getStorageAt&address=' + params.address;
url += '&position=' + params.position; url += '&position=' + stripHexZeros(params.position);
url += '&tag=' + params.blockTag + apiKey; url += '&tag=' + stripHexZeros(params.blockTag) + apiKey;
return Provider.fetchJSON(url, null, getJsonResult); return Provider.fetchJSON(url, null, getJsonResult);
case 'sendTransaction': case 'sendTransaction':
@ -120,7 +152,7 @@ utils.defineProperty(EtherscanProvider.prototype, 'perform', function(method, pa
case 'getBlock': case 'getBlock':
if (params.blockTag) { if (params.blockTag) {
url += '/api?module=proxy&action=eth_getBlockByNumber&tag=' + params.blockTag; url += '/api?module=proxy&action=eth_getBlockByNumber&tag=' + stripHexZeros(params.blockTag);
url += '&boolean=false'; url += '&boolean=false';
url += apiKey; url += apiKey;
return Provider.fetchJSON(url, null, getJsonResult); return Provider.fetchJSON(url, null, getJsonResult);

View File

@ -7,10 +7,10 @@ var FallbackProvider = require('./fallback-provider.js');
var InfuraProvider = require('./infura-provider.js'); var InfuraProvider = require('./infura-provider.js');
var JsonRpcProvider = require('./json-rpc-provider.js'); var JsonRpcProvider = require('./json-rpc-provider.js');
function getDefaultProvider(testnet) { function getDefaultProvider(network) {
return new FallbackProvider([ return new FallbackProvider([
new InfuraProvider(testnet), new InfuraProvider(network),
new EtherscanProvider(testnet), new EtherscanProvider(network),
]); ]);
} }
@ -20,7 +20,9 @@ module.exports = {
InfuraProvider: InfuraProvider, InfuraProvider: InfuraProvider,
JsonRpcProvider: JsonRpcProvider, JsonRpcProvider: JsonRpcProvider,
isProvder: Provider.isProvider, isProvider: Provider.isProvider,
networks: Provider.networks,
getDefaultProvider:getDefaultProvider, getDefaultProvider:getDefaultProvider,

View File

@ -1,4 +1,7 @@
var JsonRpcProvider = require('./json-rpc-provider.js'); 'use strict';
var Provider = require('./provider');
var JsonRpcProvider = require('./json-rpc-provider');
var utils = (function() { var utils = (function() {
return { return {
@ -6,13 +9,40 @@ var utils = (function() {
} }
})(); })();
function InfuraProvider(testnet, apiAccessToken) { function InfuraProvider(network, apiAccessToken) {
if (!(this instanceof InfuraProvider)) { throw new Error('missing new'); } if (!(this instanceof InfuraProvider)) { throw new Error('missing new'); }
var host = (testnet ? "ropsten": "mainnet") + '.infura.io'; // Legacy constructor (testnet, chainId, apiAccessToken)
// @TODO: Remove this in the next major release
if (arguments.length === 3) {
apiAccessToken = arguments[2];
network = Provider._legacyConstructor(network, 2, arguments[0], arguments[1]);
} else {
apiAccessToken = null;
network = Provider._legacyConstructor(network, arguments.length, arguments[0], arguments[1]);
}
var host = null;
switch(network.name) {
case 'homestead':
host = 'mainnet.infura.io';
break;
case 'ropsten':
host = 'ropsten.infura.io';
break;
case 'rinkeby':
host = 'rinkeby.infura.io';
break;
case 'kovan':
host = 'kovan.infura.io';
break;
default:
throw new Error('unsupported network');
}
var url = 'https://' + host + '/' + (apiAccessToken || ''); var url = 'https://' + host + '/' + (apiAccessToken || '');
JsonRpcProvider.call(this, url, testnet); JsonRpcProvider.call(this, url, network);
utils.defineProperty(this, 'apiAccessToken', apiAccessToken || null); utils.defineProperty(this, 'apiAccessToken', apiAccessToken || null);
} }

View File

@ -48,10 +48,12 @@ function getTransaction(transaction) {
return result; return result;
} }
function JsonRpcProvider(url, testnet, chainId) { function JsonRpcProvider(url, network) {
if (!(this instanceof JsonRpcProvider)) { throw new Error('missing new'); } if (!(this instanceof JsonRpcProvider)) { throw new Error('missing new'); }
Provider.call(this, testnet, chainId); network = Provider._legacyConstructor(network, arguments.length - 1, arguments[1], arguments[2]);
Provider.call(this, network);
if (!url) { url = 'http://localhost:8545'; } if (!url) { url = 'http://localhost:8545'; }

42
providers/networks.json Normal file
View File

@ -0,0 +1,42 @@
{
"unspecified": {
"chainId": 0,
"name": "unspecified"
},
"homestead": {
"chainId": 1,
"ensAddress": "0x314159265dd8dbb310642f98f50c066173c1259b",
"name": "homestead"
},
"mainnet": {
"chainId": 1,
"ensAddress": "0x314159265dd8dbb310642f98f50c066173c1259b",
"name": "homestead"
},
"morden": {
"chainId": 2,
"name": "morden"
},
"ropsten": {
"chainId": 3,
"ensAddress": "0x112234455c3a32fd11230c42e7bccd4a84e02010",
"name": "ropsten"
},
"testnet": {
"chainId": 3,
"ensAddress": "0x112234455c3a32fd11230c42e7bccd4a84e02010",
"name": "ropsten"
},
"rinkeby": {
"chainId": 4,
"name": "rinkeby"
},
"kovan": {
"chainId": 42,
"name": "kovan"
}
}

View File

@ -4,6 +4,8 @@ var inherits = require('inherits');
var XMLHttpRequest = require('xmlhttprequest').XMLHttpRequest; var XMLHttpRequest = require('xmlhttprequest').XMLHttpRequest;
var networks = require('./networks.json');
var utils = (function() { var utils = (function() {
var convert = require('ethers-utils/convert'); var convert = require('ethers-utils/convert');
return { return {
@ -127,8 +129,8 @@ var formatBlock = {
number: checkNumber, number: checkNumber,
timestamp: checkNumber, timestamp: checkNumber,
nonce: utils.hexlify, nonce: allowNull(utils.hexlify),
difficulty: checkNumber, difficulty: allowNull(checkNumber),
gasLimit: utils.bigNumberify, gasLimit: utils.bigNumberify,
gasUsed: utils.bigNumberify, gasUsed: utils.bigNumberify,
@ -326,25 +328,24 @@ function checkLog(log) {
return check(formatLog, log); return check(formatLog, log);
} }
var ensAddressTestnet = '0x112234455c3a32fd11230c42e7bccd4a84e02010'; function Provider(network) {
var ensAddressMainnet = '0x314159265dd8dbb310642f98f50c066173c1259b';
function Provider(testnet, chainId) {
if (!(this instanceof Provider)) { throw new Error('missing new'); } if (!(this instanceof Provider)) { throw new Error('missing new'); }
testnet = !!testnet; network = Provider._legacyConstructor(network, arguments.length, arguments[0], arguments[1]);
if (chainId == null) { // Check the ensAddress (if any)
chainId = (testnet ? Provider.chainId.ropsten: Provider.chainId.homestead); var ensAddress = null;
if (network.ensAddress) {
ensAddress = utils.getAddress(network.ensAddress);
} }
// Figure out which ENS to talk to // Setup our network properties
this.ensAddress = (testnet ? ensAddressTestnet: ensAddressMainnet); utils.defineProperty(this, 'chainId', network.chainId);
utils.defineProperty(this, 'ensAddress', ensAddress);
utils.defineProperty(this, 'name', network.name);
if (typeof(chainId) !== 'number') { throw new Error('invalid chainId'); } // @TODO: Remove in the next major release
utils.defineProperty(this, 'testnet', (network.name !== 'homestead'));
utils.defineProperty(this, 'testnet', testnet);
utils.defineProperty(this, 'chainId', chainId);
var events = {}; var events = {};
utils.defineProperty(this, '_events', events); utils.defineProperty(this, '_events', events);
@ -452,6 +453,35 @@ function(child) {
} }
}); });
*/ */
utils.defineProperty(Provider, '_legacyConstructor', function(network, length, arg0, arg1) {
// Legacy parameters Provider(testnet:boolean, chainId:Number)
if (typeof(arg0) === 'boolean' || length === 2) {
var testnet = !!arg0;
var chainId = arg1;
// true => testnet, false => mainnet
network = networks[testnet ? 'ropsten': 'homestead'];
// Overriding chain ID
if (length === 2 && chainId != null) {
network = {
chainId: chainId,
ensAddress: network.ensAddress,
name: network.name
};
}
} else if (typeof(network) === 'string') {
network = networks[network];
if (!network) { throw new Error('unknown network'); }
}
if (typeof(network.chainId) !== 'number') { throw new Error('invalid chainId'); }
return network;
});
utils.defineProperty(Provider, 'chainId', { utils.defineProperty(Provider, 'chainId', {
homestead: 1, homestead: 1,
morden: 2, morden: 2,
@ -462,6 +492,8 @@ utils.defineProperty(Provider, 'chainId', {
// return (object instanceof Provider); // return (object instanceof Provider);
//}); //});
utils.defineProperty(Provider, 'networks', networks);
utils.defineProperty(Provider, 'fetchJSON', function(url, json, processFunc) { utils.defineProperty(Provider, 'fetchJSON', function(url, json, processFunc) {
return new Promise(function(resolve, reject) { return new Promise(function(resolve, reject) {

View File

@ -10,7 +10,7 @@ var providers = require('../providers');
var contracts = require('../contracts'); var contracts = require('../contracts');
var TestContracts = require('./tests/test-contract.json'); var TestContracts = require('./test-contract.json');
var TestContract = TestContracts.test; var TestContract = TestContracts.test;
var TestContractDeploy = TestContracts.deploy; var TestContractDeploy = TestContracts.deploy;

155
tests/test-providers.js Normal file
View File

@ -0,0 +1,155 @@
'use strict';
var assert = require('assert');
var providers = require('../providers');
var bigNumberify = require('../utils/bignumber').bigNumberify;
var blockchainData = {
homestead: {
balance: {
address: '0xAC1639CF97a3A46D431e6d1216f576622894cBB5',
balance: bigNumberify('4918774100000000')
},
block3: {
hash: '0x3d6122660cc824376f11ee842f83addc3525e2dd6756b9bcf0affa6aa88cf741',
parentHash: '0xb495a1d7e6663152ae92708da4843337b958146015a2802f4193a410044698c9',
number: 3,
timestamp: 1438270048,
nonce: '0x2e9344e0cbde83ce',
difficulty: 17154715646,
gasLimit: bigNumberify('0x1388'),
gasUsed: bigNumberify('0'),
miner: '0x5088D623ba0fcf0131E0897a91734A4D83596AA0',
extraData: '0x476574682f76312e302e302d66633739643332642f6c696e75782f676f312e34',
transactions: []
},
},
kovan: {
balance: {
address: '0x09c967A0385eE3B3717779738cA0B9D116e0EcE7',
balance: bigNumberify('997787946734641021')
},
block3: {
hash: '0xf0ec9bf41b99a6bd1f6cd29f91302f71a1a82d14634d2e207edea4b7962f3676',
parentHash: '0xf110ecd84454f116e2222378e7bca81ac3e59be0dac96d7ec56d5ef1c3bc1d64',
number: 3,
timestamp: 1488459452,
difficulty: 131072,
gasLimit: bigNumberify('0x5b48ec'),
gasUsed: bigNumberify('0'),
miner: '0x00A0A24b9f0E5EC7Aa4c7389b8302fd0123194dE',
extraData: '0xd5830105048650617269747986312e31352e31826c69',
transactions: []
},
},
rinkeby: {
balance: {
address: '0xd09a624630a656a7dbb122cb05e41c12c7cd8c0e',
balance: bigNumberify('3000000000000000000')
},
block3: {
hash: '0x9eb9db9c3ec72918c7db73ae44e520139e95319c421ed6f9fc11fa8dd0cddc56',
parentHash: '0x9b095b36c15eaf13044373aef8ee0bd3a382a5abb92e402afa44b8249c3a90e9',
number: 3,
timestamp: 1492010489,
nonce: '0x0000000000000000',
difficulty: 2,
gasLimit: bigNumberify('0x47e7c4'),
gasUsed: bigNumberify(0),
miner: '0x0000000000000000000000000000000000000000',
extraData: '0xd783010600846765746887676f312e372e33856c696e757800000000000000004e10f96536e45ceca7e34cc1bdda71db3f3bb029eb69afd28b57eb0202c0ec0859d383a99f63503c4df9ab6c1dc63bf6b9db77be952f47d86d2d7b208e77397301',
transactions: []
},
},
ropsten: {
balance: {
address: '0x03a6F7a5ce5866d9A0CCC1D4C980b8d523f80480',
balance: bigNumberify('21991148575128552666')
},
block3: {
hash: '0xaf2f2d55e6514389bcc388ccaf40c6ebf7b3814a199a214f1203fb674076e6df',
parentHash: '0x88e8bc1dd383672e96d77ee247e7524622ff3b15c337bd33ef602f15ba82d920',
number: 3,
timestamp: 1479642588,
nonce: '0x04668f72247a130c',
difficulty: 996427,
gasLimit: bigNumberify('0xff4033'),
gasUsed: bigNumberify('0'),
miner: '0xD1aEb42885A43b72B518182Ef893125814811048',
extraData: '0xd883010503846765746887676f312e372e318664617277696e',
transactions: []
},
},
}
function equals(name, actual, expected) {
if (expected.eq) {
assert.ok(expected.eq(actual), name + ' matches');
} else if (Array.isArray(expected)) {
assert.equal(actual.length, expected.length, name + ' array lengths match');
for (var i = 0; i < expected.length; i++) {
equals(name + ' item ' + i, actual[i], expected[i]);
}
} else {
assert.equal(actual, expected, name + ' matches');
}
}
function testProvider(providerName, networkName) {
describe(('Read-Only ' + providerName + ' (' + networkName + ')'), function() {
var provider = new providers[providerName](networkName);
it('fetches block #3', function() {
this.timeout(20000);
var test = blockchainData[networkName].block3;
return provider.getBlock(3).then(function(block) {
for (var key in test) {
equals('Block ' + key, block[key], test[key]);
}
});
});
it('fetches address balance', function() {
// @TODO: These tests could be fiddled with if someone sends ether to our address
// We should set up a contract on each network like:
//
// contract TestBalance {
// function resetBalance() {
// assert(_owner.send(this.balance - 0.0000314159 ether));
// }
// }
this.timeout(20000);
var test = blockchainData[networkName].balance;
return provider.getBalance(test.address).then(function(balance) {
equals('Balance', test.balance, balance);
});
});
// Obviously many more cases to add here
// - getTransactionCount
// - getCode
// - getStorageAt
// - getBlockNumber
// - getGasPrice
// - estimateGas
// - sendTransaction
// - getTransaction
// - getTransactionReceipt
// - call
// - getLogs
//
// Many of these are tested in run-providers, which uses nodeunit, but
// also creates a local private key which must then be funded to
// execute the tests. I am working on a better test contract to deploy
// to all the networks to help test these.
});
}
['homestead', 'ropsten', 'rinkeby', 'kovan'].forEach(function(networkName) {
['InfuraProvider', 'EtherscanProvider'].forEach(function(providerName) {
testProvider(providerName, networkName);
});
});