Compare commits

..

3 Commits

Author SHA1 Message Date
Richard Moore
493273d698 Added optional blockTag to call; note that this may not behave as expected on all nodes (#226). 2018-10-11 16:03:18 -04:00
Richard Moore
84344ac4c2 Check all transaction parameters are valid; protect against typos (#299). 2018-10-11 15:16:31 -04:00
Richard Moore
9b118af304 Updated dist files. 2018-10-07 01:13:10 -04:00
18 changed files with 85 additions and 40 deletions

2
_version.d.ts vendored
View File

@@ -1 +1 @@
export declare const version = "4.0.3";
export declare const version = "4.0.4";

View File

@@ -1,3 +1,3 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.version = "4.0.3";
exports.version = "4.0.4";

3
dist/ethers.js vendored
View File

@@ -1,7 +1,7 @@
(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.ethers = f()}})(function(){var define,module,exports;return (function(){function r(e,n,t){function o(i,f){if(!n[i]){if(!e[i]){var c="function"==typeof require&&require;if(!f&&c)return c(i,!0);if(u)return u(i,!0);var a=new Error("Cannot find module '"+i+"'");throw a.code="MODULE_NOT_FOUND",a}var p=n[i]={exports:{}};e[i][0].call(p.exports,function(r){var n=e[i][1][r];return o(n||r)},p,p.exports,r,e,n,t)}return n[i].exports}for(var u="function"==typeof require&&require,i=0;i<t.length;i++)o(t[i]);return o}return r})()({1:[function(require,module,exports){
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.version = "4.0.3";
exports.version = "4.0.4";
},{}],2:[function(require,module,exports){
"use strict";
@@ -13615,6 +13615,7 @@ var HDNode = /** @class */ (function () {
properties_1.defineReadOnly(this, 'keyPair', new secp256k1_1.KeyPair(privateKey));
properties_1.defineReadOnly(this, 'privateKey', this.keyPair.privateKey);
properties_1.defineReadOnly(this, 'publicKey', this.keyPair.compressedPublicKey);
properties_1.defineReadOnly(this, 'address', secp256k1_1.computeAddress(this.publicKey));
properties_1.defineReadOnly(this, 'chainCode', bytes_1.hexlify(chainCode));
properties_1.defineReadOnly(this, 'index', index);
properties_1.defineReadOnly(this, 'depth', depth);

2
dist/ethers.min.js vendored

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -152,7 +152,7 @@ declare module 'ethers/wallet' {
* Static methods to create Wallet instances.
*/
static createRandom(options?: any): Wallet;
static fromEncryptedJson(json: string, password: Arrayish, progressCallback: ProgressCallback): Promise<Wallet>;
static fromEncryptedJson(json: string, password: Arrayish, progressCallback?: ProgressCallback): Promise<Wallet>;
static fromMnemonic(mnemonic: string, path?: string, wordlist?: Wordlist): Wallet;
}
}
@@ -260,7 +260,7 @@ declare module 'ethers/utils/shims' {
}
declare module 'ethers/_version' {
export const version = "4.0.3";
export const version = "4.0.4";
}
declare module 'ethers/utils/bignumber' {
@@ -622,6 +622,7 @@ declare module 'ethers/utils/hdnode' {
export class HDNode {
readonly privateKey: string;
readonly publicKey: string;
readonly address: string;
readonly mnemonic: string;
readonly path: string;
readonly chainCode: string;

2
package-lock.json generated
View File

@@ -1,6 +1,6 @@
{
"name": "ethers",
"version": "4.0.3",
"version": "4.0.4",
"lockfileVersion": 1,
"requires": true,
"dependencies": {

View File

@@ -1,6 +1,6 @@
{
"name": "ethers",
"version": "4.0.3",
"version": "4.0.4",
"description": "Ethereum wallet library.",
"main": "./index.js",
"types": "./index.d.ts",

View File

@@ -1 +1 @@
export const version = "4.0.3";
export const version = "4.0.4";

View File

@@ -16,7 +16,7 @@ import { UnsignedTransaction } from './utils/transaction';
///////////////////////////////
// Imported Abstracts
import { Provider } from './providers/abstract-provider';
import { BlockTag, Provider } from './providers/abstract-provider';
import { Signer } from './abstract-signer';
///////////////////////////////
@@ -148,14 +148,21 @@ type RunFunction = (...params: Array<any>) => Promise<any>;
function runMethod(contract: Contract, functionName: string, estimateOnly: boolean): RunFunction {
let method = contract.interface.functions[functionName];
return function(...params): Promise<any> {
var tx: any = {}
let tx: any = {}
let blockTag: BlockTag = null;
// If 1 extra parameter was passed in, it contains overrides
if (params.length === method.inputs.length + 1 && typeof(params[params.length - 1]) === 'object') {
tx = shallowCopy(params.pop());
if (tx.blockTag != null) {
blockTag = tx.blockTag;
delete tx.blockTag;
}
// Check for unexpected keys (e.g. using "gas" instead of "gasLimit")
for (var key in tx) {
for (let key in tx) {
if (!allowedTransactionKeys[key]) {
throw new Error('unknown transaction override ' + key);
}
@@ -202,7 +209,7 @@ function runMethod(contract: Contract, functionName: string, estimateOnly: boole
tx.from = contract.signer.getAddress()
}
return contract.provider.call(tx).then((value) => {
return contract.provider.call(tx, blockTag).then((value) => {
if ((hexDataLength(value) % 32) === 4 && hexDataSlice(value, 0, 4) === '0x08c379a0') {
let reason = defaultAbiCoder.decode([ 'string' ], hexDataSlice(value, 4));

View File

@@ -125,7 +125,7 @@ export abstract class Provider implements OnceBlockable {
abstract getStorageAt(addressOrName: string | Promise<string>, position: BigNumberish | Promise<BigNumberish>, blockTag?: BlockTag | Promise<BlockTag>): Promise<string>;
abstract sendTransaction(signedTransaction: string | Promise<string>): Promise<TransactionResponse>;
abstract call(transaction: TransactionRequest): Promise<string>;
abstract call(transaction: TransactionRequest, blockTag?: BlockTag | Promise<BlockTag>): Promise<string>;
abstract estimateGas(transaction: TransactionRequest): Promise<BigNumber>;
abstract getBlock(blockHashOrBlockTag: BlockTag | string | Promise<BlockTag | string>, includeTransactions?: boolean): Promise<Block>;

View File

@@ -850,12 +850,12 @@ export class BaseProvider extends Provider {
}
call(transaction: TransactionRequest): Promise<string> {
call(transaction: TransactionRequest, blockTag?: BlockTag | Promise<BlockTag>): Promise<string> {
let tx: TransactionRequest = shallowCopy(transaction);
return this.ready.then(() => {
return resolveProperties(tx).then((tx) => {
return resolveProperties({ blockTag: blockTag, tx: tx }).then(({ blockTag, tx }) => {
return this._resolveNames(tx, [ 'to', 'from' ]).then((tx) => {
var params = { transaction: checkTransactionRequest(tx) };
let params = { blockTag: checkBlockTag(blockTag), transaction: checkTransactionRequest(tx) };
return this.perform('call', params).then((result) => {
return hexlify(result);
});

View File

@@ -108,9 +108,7 @@ export class EtherscanProvider extends BaseProvider{
perform(method: string, params: any) {
//if (!params) { params = {}; }
var url = this.baseUrl;
let url = this.baseUrl;
let apiKey = '';
if (this.apiKey) { apiKey += '&apikey=' + this.apiKey; }
@@ -118,7 +116,7 @@ export class EtherscanProvider extends BaseProvider{
switch (method) {
case 'getBlockNumber':
url += '/api?module=proxy&action=eth_blockNumber' + apiKey;
return fetchJson(url, null, getJsonResult);
return fetchJson(url, null, getJsonResult);
case 'getGasPrice':
url += '/api?module=proxy&action=eth_gasPrice' + apiKey;
@@ -193,19 +191,25 @@ export class EtherscanProvider extends BaseProvider{
return fetchJson(url, null, getJsonResult);
case 'call':
var transaction = getTransactionString(params.transaction);
case 'call': {
let transaction = getTransactionString(params.transaction);
if (transaction) { transaction = '&' + transaction; }
url += '/api?module=proxy&action=eth_call' + transaction;
//url += '&tag=' + params.blockTag + apiKey;
if (params.blockTag !== 'latest') {
throw new Error('EtherscanProvider does not support blockTag for call');
}
url += apiKey;
return fetchJson(url, null, getJsonResult);
}
case 'estimateGas':
var transaction = getTransactionString(params.transaction);
case 'estimateGas': {
let transaction = getTransactionString(params.transaction);
if (transaction) { transaction = '&' + transaction; }
url += '/api?module=proxy&action=eth_estimateGas&' + transaction;
url += apiKey;
return fetchJson(url, null, getJsonResult);
}
case 'getLogs':
url += '/api?module=logs&action=getLogs';
@@ -227,7 +231,7 @@ export class EtherscanProvider extends BaseProvider{
if (params.filter.topics.length > 1) {
throw new Error('unsupported topic format');
}
var topic0 = params.filter.topics[0];
let topic0 = params.filter.topics[0];
if (typeof(topic0) !== 'string' || topic0.length !== 66) {
throw new Error('unsupported topic0 format');
}

View File

@@ -176,6 +176,10 @@ export class JsonRpcSigner extends Signer {
}
}
const allowedTransactionKeys: { [ key: string ]: boolean } = {
chainId: true, data: true, gasLimit: true, gasPrice:true, nonce: true, to: true, value: true
}
export class JsonRpcProvider extends BaseProvider {
readonly connection: ConnectionInfo;
@@ -300,10 +304,10 @@ export class JsonRpcProvider extends BaseProvider {
return this.send('eth_getTransactionReceipt', [ params.transactionHash ]);
case 'call':
return this.send('eth_call', [ JsonRpcProvider.hexlifyTransaction(params.transaction), 'latest' ]);
return this.send('eth_call', [ JsonRpcProvider.hexlifyTransaction(params.transaction, { from: true }), params.blockTag ]);
case 'estimateGas':
return this.send('eth_estimateGas', [ JsonRpcProvider.hexlifyTransaction(params.transaction) ]);
return this.send('eth_estimateGas', [ JsonRpcProvider.hexlifyTransaction(params.transaction, { from: true }) ]);
case 'getLogs':
if (params.filter && params.filter.address != null) {
@@ -369,9 +373,22 @@ export class JsonRpcProvider extends BaseProvider {
// - gasLimit => gas
// - All values hexlified
// - All numeric values zero-striped
// @TODO: Not any, a dictionary of string to strings
static hexlifyTransaction(transaction: TransactionRequest): any {
var result: any = {};
// NOTE: This allows a TransactionRequest, but all values should be resolved
// before this is called
static hexlifyTransaction(transaction: TransactionRequest, allowExtra?: { [key: string]: boolean }): { [key: string]: string } {
if (!allowExtra) { allowExtra = {}; }
for (let key in transaction) {
if (!allowedTransactionKeys[key] && !allowExtra[key]) {
errors.throwError('invalid key - ' + key, errors.INVALID_ARGUMENT, {
argument: 'transaction',
value: transaction,
key: key
});
}
}
let result: { [key: string]: string } = {};
// Some nodes (INFURA ropsten; INFURA mainnet is fine) don't like extra zeros.
['gasLimit', 'gasPrice', 'nonce', 'value'].forEach(function(key) {
@@ -384,7 +401,7 @@ export class JsonRpcProvider extends BaseProvider {
['from', 'to', 'data'].forEach(function(key) {
if ((<any>transaction)[key] == null) { return; }
result[key] = hexlify((<any>transaction)[key]);
});
});
return result;
}

View File

@@ -24,6 +24,9 @@ import { BlockTag, TransactionRequest, TransactionResponse } from './providers/a
import * as errors from './errors';
const allowedTransactionKeys: { [ key: string ]: boolean } = {
chainId: true, data: true, gasLimit: true, gasPrice:true, nonce: true, to: true, value: true
}
export class Wallet extends AbstractSigner {
@@ -69,6 +72,15 @@ export class Wallet extends AbstractSigner {
}
sign(transaction: TransactionRequest): Promise<string> {
for (let key in transaction) {
if (!allowedTransactionKeys[key]) {
errors.throwError('unsupported transaction property - ' + key, errors.INVALID_ARGUMENT, {
argument: 'transaction',
value: transaction,
key: key
});
}
}
return resolveProperties(transaction).then((tx) => {
let rawTx = serializeTransaction(tx);
let signature = this.signingKey.signDigest(keccak256(rawTx));
@@ -98,17 +110,12 @@ export class Wallet extends AbstractSigner {
throw new Error('invalid transaction object');
}
var tx = shallowCopy(transaction);
let tx = shallowCopy(transaction);
if (tx.to != null) {
tx.to = this.provider.resolveName(tx.to);
}
if (tx.gasLimit == null) {
tx.from = this.getAddress();
tx.gasLimit = this.provider.estimateGas(tx);
}
if (tx.gasPrice == null) {
tx.gasPrice = this.provider.getGasPrice();
}
@@ -117,6 +124,12 @@ export class Wallet extends AbstractSigner {
tx.nonce = this.getTransactionCount();
}
if (tx.gasLimit == null) {
let estimate = shallowCopy(tx);
estimate.from = this.getAddress();
tx.gasLimit = this.provider.estimateGas(estimate);
}
if (tx.chainId == null) {
tx.chainId = this.provider.getNetwork().then((network) => network.chainId);
}

1
utils/hdnode.d.ts vendored
View File

@@ -5,6 +5,7 @@ export declare class HDNode {
private readonly keyPair;
readonly privateKey: string;
readonly publicKey: string;
readonly address: string;
readonly mnemonic: string;
readonly path: string;
readonly chainCode: string;

View File

@@ -54,6 +54,7 @@ var HDNode = /** @class */ (function () {
properties_1.defineReadOnly(this, 'keyPair', new secp256k1_1.KeyPair(privateKey));
properties_1.defineReadOnly(this, 'privateKey', this.keyPair.privateKey);
properties_1.defineReadOnly(this, 'publicKey', this.keyPair.compressedPublicKey);
properties_1.defineReadOnly(this, 'address', secp256k1_1.computeAddress(this.publicKey));
properties_1.defineReadOnly(this, 'chainCode', bytes_1.hexlify(chainCode));
properties_1.defineReadOnly(this, 'index', index);
properties_1.defineReadOnly(this, 'depth', depth);

2
wallet.d.ts vendored
View File

@@ -30,6 +30,6 @@ export declare class Wallet extends AbstractSigner {
* Static methods to create Wallet instances.
*/
static createRandom(options?: any): Wallet;
static fromEncryptedJson(json: string, password: Arrayish, progressCallback: ProgressCallback): Promise<Wallet>;
static fromEncryptedJson(json: string, password: Arrayish, progressCallback?: ProgressCallback): Promise<Wallet>;
static fromMnemonic(mnemonic: string, path?: string, wordlist?: Wordlist): Wallet;
}