From c411d9744da1d56c8d083f27f0a560ed1c942a04 Mon Sep 17 00:00:00 2001 From: Richard Moore Date: Wed, 13 Jun 2018 21:10:41 -0400 Subject: [PATCH] Added new checks and fixed up deploy for Contract. --- src.ts/contracts/contract.ts | 77 ++++++++++++++++---------- src.ts/contracts/interface.ts | 2 +- src.ts/providers/etherscan-provider.ts | 2 + src.ts/providers/fallback-provider.ts | 4 +- src.ts/providers/infura-provider.ts | 3 +- src.ts/providers/ipc-provider.ts | 4 +- src.ts/providers/json-rpc-provider.ts | 4 +- src.ts/providers/provider.ts | 15 ++--- src.ts/providers/web3-provider.ts | 3 +- src.ts/utils/address.ts | 17 +++++- src.ts/utils/contract-address.ts | 18 ------ src.ts/utils/index.ts | 3 +- src.ts/wallet/hdnode.ts | 4 +- src.ts/wallet/signing-key.ts | 2 +- src/contracts/contract.d.ts | 19 ++----- src/contracts/contract.js | 57 +++++++++++-------- src/contracts/interface.js | 4 +- src/providers/etherscan-provider.js | 9 +++ src/providers/fallback-provider.js | 3 +- src/providers/infura-provider.js | 2 +- src/providers/ipc-provider.js | 2 +- src/providers/json-rpc-provider.js | 4 +- src/providers/provider.d.ts | 8 +-- src/providers/provider.js | 6 +- src/providers/web3-provider.js | 2 +- src/utils/address.d.ts | 6 ++ src/utils/address.js | 14 +++++ src/utils/index.d.ts | 3 +- src/utils/index.js | 3 +- src/wallet/hdnode.js | 3 +- src/wallet/signing-key.js | 2 +- 31 files changed, 177 insertions(+), 128 deletions(-) delete mode 100644 src.ts/utils/contract-address.ts diff --git a/src.ts/contracts/contract.ts b/src.ts/contracts/contract.ts index 82b5e0459..39cede000 100644 --- a/src.ts/contracts/contract.ts +++ b/src.ts/contracts/contract.ts @@ -2,10 +2,9 @@ import { Interface } from './interface'; -// @TODO: Move to utils? -import { TransactionResponse } from '../providers/provider'; -import { Network } from '../providers/networks'; +import { Provider, TransactionResponse } from '../providers/provider'; +import { getContractAddress } from '../utils/address'; import { ParamType } from '../utils/abi-coder'; import { BigNumber, ConstantZero } from '../utils/bignumber'; import { defineReadOnly, resolveProperties } from '../utils/properties'; @@ -211,6 +210,7 @@ function runMethod(contract: Contract, functionName: string, estimateOnly: boole throw new Error('unsupport type - ' + method.type); } } +/* interface Provider { getNetwork(): Promise; getGasPrice(): Promise; @@ -219,11 +219,12 @@ interface Provider { estimateGas(tx: any): Promise; sendTransaction(signedTransaction: string | Promise): Promise; } - +*/ interface Signer { defaultGasLimit?: BigNumber; defaultGasPrice?: BigNumber; address?: string; + provider?: Provider; getAddress(): Promise; getTransactionCount(): Promise; @@ -232,6 +233,10 @@ interface Signer { sign(tx: any): string | Promise; } +function isSigner(value: any): value is Signer { + return (value && value.provider != null); +} + export type ContractEstimate = (...params: Array) => Promise; export type ContractFunction = (...params: Array) => Promise; export type ContractEvent = (...params: Array) => void; @@ -253,11 +258,14 @@ export class Contract { readonly addressPromise: Promise; + // This is only set if the contract was created with a call to deploy + readonly deployTransaction: TransactionResponse; + // https://github.com/Microsoft/TypeScript/issues/5453 // Once this issue is resolved (there are open PR) we can do this nicer. :) - constructor(addressOrName: string, contractInterface: Contractish, signerOrProvider: any) { - //if (!(this instanceof Contract)) { throw new Error('missing new'); } + constructor(addressOrName: string, contractInterface: Contractish, signerOrProvider: Signer | Provider) { + errors.checkNew(this, Contract); // @TODO: Maybe still check the addressOrName looks like a valid address or name? //address = getAddress(address); @@ -269,28 +277,29 @@ export class Contract { if (!signerOrProvider) { throw new Error('missing signer or provider'); } - var signer = signerOrProvider; - var provider = null; - - if (signerOrProvider.provider) { - provider = signerOrProvider.provider; + if (isSigner(signerOrProvider)) { + defineReadOnly(this, 'provider', signerOrProvider.provider); + defineReadOnly(this, 'signer', signerOrProvider); } else { - provider = signerOrProvider; - signer = null; + defineReadOnly(this, 'provider', signerOrProvider); + defineReadOnly(this, 'signer', null); } - defineReadOnly(this, 'signer', signer); - defineReadOnly(this, 'provider', provider); - - if (!addressOrName) { return; } - - defineReadOnly(this, 'address', addressOrName); - defineReadOnly(this, 'addressPromise', provider.resolveName(addressOrName)); defineReadOnly(this, 'estimate', { }); defineReadOnly(this, 'events', { }); defineReadOnly(this, 'functions', { }); + // Not connected to an on-chain instance, so do not connect functions and events + if (!addressOrName) { + defineReadOnly(this, 'address', null); + defineReadOnly(this, 'addressPromise', Promise.resolve(null)); + return; + } + + defineReadOnly(this, 'address', addressOrName || null); + defineReadOnly(this, 'addressPromise', this.provider.resolveName(addressOrName || null)); + Object.keys(this.interface.functions).forEach((name) => { var run = runMethod(this, name, false); @@ -311,9 +320,9 @@ export class Contract { let eventCallback = null; - let addressPromise = this.addressPromise; + let contract = this; function handleEvent(log) { - addressPromise.then((address) => { + contract.addressPromise.then((address) => { // Not meant for us (the topics just has the same name) if (address != log.address) { return; } @@ -325,12 +334,12 @@ export class Contract { log.event = eventName; log.parse = eventInfo.parse; log.removeListener = function() { - provider.removeListener(eventInfo.topics, handleEvent); + contract.provider.removeListener(eventInfo.topics, handleEvent); } - log.getBlock = function() { return provider.getBlock(log.blockHash);; } - log.getTransaction = function() { return provider.getTransaction(log.transactionHash); } - log.getTransactionReceipt = function() { return provider.getTransactionReceipt(log.transactionHash); } + log.getBlock = function() { return contract.provider.getBlock(log.blockHash);; } + log.getTransaction = function() { return contract.provider.getTransaction(log.transactionHash); } + log.getTransactionReceipt = function() { return contract.provider.getTransactionReceipt(log.transactionHash); } log.eventSignature = eventInfo.signature; eventCallback.apply(log, Array.prototype.slice.call(result)); @@ -349,10 +358,10 @@ export class Contract { if (!value) { value = null; } if (!value && eventCallback) { - provider.removeListener(eventInfo.topics, handleEvent); + contract.provider.removeListener(eventInfo.topics, handleEvent); } else if (value && !eventCallback) { - provider.on(eventInfo.topics, handleEvent); + contract.provider.on(eventInfo.topics, handleEvent); } eventCallback = value; @@ -369,11 +378,15 @@ export class Contract { }, this); } - connect(signerOrProvider) { + // Reconnect to a different signer or provider + connect(signerOrProvider: Signer | Provider): Contract { return new Contract(this.address, this.interface, signerOrProvider); } - deploy(bytecode: string, ...args): Promise { + // Deploy the contract with the bytecode, resolving to the deployed address. + // Use contract.deployTransaction.wait() to wait until the contract has + // been mined. + deploy(bytecode: string, ...args: Array): Promise { if (this.signer == null) { throw new Error('missing signer'); // @TODO: errors.throwError } @@ -381,6 +394,10 @@ export class Contract { // @TODO: overrides of args.length = this.interface.deployFunction.inputs.length + 1 return this.signer.sendTransaction({ data: this.interface.deployFunction.encode(bytecode, args) + }).then((tx) => { + let contract = new Contract(getContractAddress(tx), this.interface, this.provider); + defineReadOnly(contract, 'deployTransaction', tx); + return contract; }); } } diff --git a/src.ts/contracts/interface.ts b/src.ts/contracts/interface.ts index fe5fbcdf2..a98fe815d 100644 --- a/src.ts/contracts/interface.ts +++ b/src.ts/contracts/interface.ts @@ -331,7 +331,7 @@ export class Interface { readonly deployFunction: DeployDescription; constructor(abi: Array | string) { - if (!(this instanceof Interface)) { throw new Error('missing new'); } + errors.checkNew(this, Interface); if (typeof(abi) === 'string') { try { diff --git a/src.ts/providers/etherscan-provider.ts b/src.ts/providers/etherscan-provider.ts index 9fca4e02c..68f455568 100644 --- a/src.ts/providers/etherscan-provider.ts +++ b/src.ts/providers/etherscan-provider.ts @@ -5,6 +5,7 @@ import { Network } from './networks'; import { hexlify, hexStripZeros } from '../utils/convert'; import { fetchJson } from '../utils/web'; +import * as errors from '../utils/errors'; // The transaction has already been sanitized by the calls in Provider function getTransactionString(transaction: TransactionRequest): string { @@ -70,6 +71,7 @@ export class EtherscanProvider extends Provider{ constructor(network?: Network | string, apiKey?: string) { super(network || 'homestead'); + errors.checkNew(this, EtherscanProvider); let name = 'invalid'; if (this.network) { name = this.network.name; } diff --git a/src.ts/providers/fallback-provider.ts b/src.ts/providers/fallback-provider.ts index 90078a002..746bd2dd7 100644 --- a/src.ts/providers/fallback-provider.ts +++ b/src.ts/providers/fallback-provider.ts @@ -46,15 +46,16 @@ export class FallbackProvider extends Provider { private _providers: Array; constructor(providers: Array) { - //if (!(this instanceof FallbackProvider)) { throw new Error('missing new'); } if (providers.length === 0) { throw new Error('no providers'); } let ready = checkNetworks(providers.map((p) => p.network)); if (ready) { super(providers[0].network); + errors.checkNew(this, FallbackProvider); } else { super(null); + errors.checkNew(this, FallbackProvider); // We re-assign the ready function to make sure all networks actually match this.ready = Promise.all(providers.map((p) => p.getNetwork())).then((networks) => { @@ -65,6 +66,7 @@ export class FallbackProvider extends Provider { }); } + this._providers = providers.slice(0); } diff --git a/src.ts/providers/infura-provider.ts b/src.ts/providers/infura-provider.ts index 17534906b..b66180e91 100644 --- a/src.ts/providers/infura-provider.ts +++ b/src.ts/providers/infura-provider.ts @@ -9,7 +9,6 @@ export class InfuraProvider extends JsonRpcProvider { readonly apiAccessToken: string; constructor(network?: Network | string, apiAccessToken?: string) { - //errors.checkNew(this, InfuraProvider); network = getNetwork(network || 'homestead'); @@ -32,6 +31,8 @@ export class InfuraProvider extends JsonRpcProvider { } super('https://' + host + '/' + (apiAccessToken || ''), network); + errors.checkNew(this, InfuraProvider); + this.apiAccessToken = (apiAccessToken || null); } diff --git a/src.ts/providers/ipc-provider.ts b/src.ts/providers/ipc-provider.ts index 7390d6c18..939ee60e0 100644 --- a/src.ts/providers/ipc-provider.ts +++ b/src.ts/providers/ipc-provider.ts @@ -9,13 +9,13 @@ import * as errors from '../utils/errors'; export class IpcProvider extends JsonRpcProvider { readonly path: string; constructor(path: string, network?: Network | string) { - //errors.checkNew(this, IpcProvider); - if (path == null) { errors.throwError('missing path', errors.MISSING_ARGUMENT, { arg: 'path' }); } super('ipc://' + path, network); + errors.checkNew(this, IpcProvider); + this.path = path; } diff --git a/src.ts/providers/json-rpc-provider.ts b/src.ts/providers/json-rpc-provider.ts index 2bbbcd18a..8d0d1ad16 100644 --- a/src.ts/providers/json-rpc-provider.ts +++ b/src.ts/providers/json-rpc-provider.ts @@ -67,7 +67,7 @@ export class JsonRpcSigner { // private _syncAddress: boolean; constructor(provider: JsonRpcProvider, address?: string) { - //errors.checkNew(this, JsonRpcSigner); + errors.checkNew(this, JsonRpcSigner); this.provider = provider; @@ -161,7 +161,6 @@ export class JsonRpcProvider extends Provider { private _pendingFilter: Promise; constructor(url?: ConnectionInfo | string, network?: Network | string) { - //errors.checkNew(this, JsonRpcProvider); // One parameter, but it is a network name, so swap it with the URL if (typeof(url) === 'string') { @@ -172,6 +171,7 @@ export class JsonRpcProvider extends Provider { } super(network); + errors.checkNew(this, JsonRpcProvider); // Default URL if (!url) { url = 'http://localhost:8545'; } diff --git a/src.ts/providers/provider.ts b/src.ts/providers/provider.ts index d8dd6e29f..c326716b7 100644 --- a/src.ts/providers/provider.ts +++ b/src.ts/providers/provider.ts @@ -2,11 +2,8 @@ //import inherits = require('inherits'); -//import networks = require('./networks.json'); - -import { getAddress } from '../utils/address'; +import { getAddress, getContractAddress } from '../utils/address'; import { BigNumber, bigNumberify, BigNumberish } from '../utils/bignumber'; -import { getContractAddress } from '../utils/contract-address'; import { Arrayish, hexlify, hexStripZeros, isHexString, stripZeros } from '../utils/convert'; import { toUtf8String } from '../utils/utf8'; import { decode as rlpDecode, encode as rlpEncode } from '../utils/rlp'; @@ -604,7 +601,7 @@ export class Provider { */ constructor(network: string | Network) { - //if (!(this instanceof Provider)) { throw new Error('missing new'); } + errors.checkNew(this, Provider); network = getNetwork(network); @@ -803,7 +800,7 @@ export class Provider { } - getBalance(addressOrName: string | Promise, blockTag: BlockTag | Promise): Promise { + getBalance(addressOrName: string | Promise, blockTag?: BlockTag | Promise): Promise { return this.ready.then(() => { return resolveProperties({ addressOrName: addressOrName, blockTag: blockTag }).then(({ addressOrName, blockTag }) => { return this.resolveName(addressOrName).then((address) => { @@ -816,7 +813,7 @@ export class Provider { }); } - getTransactionCount(addressOrName: string | Promise, blockTag: BlockTag | Promise): Promise { + getTransactionCount(addressOrName: string | Promise, blockTag?: BlockTag | Promise): Promise { return this.ready.then(() => { return resolveProperties({ addressOrName: addressOrName, blockTag: blockTag }).then(({ addressOrName, blockTag }) => { return this.resolveName(addressOrName).then((address) => { @@ -831,7 +828,7 @@ export class Provider { }); } - getCode(addressOrName: string | Promise, blockTag: BlockTag | Promise): Promise { + getCode(addressOrName: string | Promise, blockTag?: BlockTag | Promise): Promise { return this.ready.then(() => { return resolveProperties({ addressOrName: addressOrName, blockTag: blockTag }).then(({ addressOrName, blockTag }) => { return this.resolveName(addressOrName).then((address) => { @@ -844,7 +841,7 @@ export class Provider { }); } - getStorageAt(addressOrName: string | Promise, position: BigNumberish | Promise, blockTag: BlockTag | Promise): Promise { + getStorageAt(addressOrName: string | Promise, position: BigNumberish | Promise, blockTag?: BlockTag | Promise): Promise { return this.ready.then(() => { return resolveProperties({ addressOrName: addressOrName, position: position, blockTag: blockTag }).then(({ addressOrName, position, blockTag }) => { return this.resolveName(addressOrName).then((address) => { diff --git a/src.ts/providers/web3-provider.ts b/src.ts/providers/web3-provider.ts index 9f74169c1..42c108fb2 100644 --- a/src.ts/providers/web3-provider.ts +++ b/src.ts/providers/web3-provider.ts @@ -24,7 +24,6 @@ export class Web3Provider extends JsonRpcProvider { readonly _web3Provider: AsyncProvider; constructor(web3Provider: AsyncProvider, network?: Network | string) { - //errors.checkNew(this, Web3Provider); if (!web3Provider || !web3Provider.sendAsync) { errors.throwError( @@ -38,6 +37,8 @@ export class Web3Provider extends JsonRpcProvider { var url = web3Provider.host || web3Provider.path || 'unknown'; super(url, network); + errors.checkNew(this, Web3Provider); + this._web3Provider = web3Provider; } diff --git a/src.ts/utils/address.ts b/src.ts/utils/address.ts index a8737df45..b3681ae4e 100644 --- a/src.ts/utils/address.ts +++ b/src.ts/utils/address.ts @@ -1,12 +1,16 @@ 'use strict'; +// We use this for base 36 maths import BN = require('bn.js'); -import { arrayify } from './convert'; +import { BigNumber } from './bignumber'; +import { arrayify, Arrayish, stripZeros, hexlify } from './convert'; import { keccak256 } from './keccak256'; +import { encode } from './rlp'; import errors = require('./errors'); + function getChecksumAddress(address: string): string { if (typeof(address) !== 'string' || !address.match(/^0x[0-9A-Fa-f]{40}$/)) { errors.throwError('invalid address', errors.INVALID_ARGUMENT, { arg: 'address', value: address }); @@ -118,3 +122,14 @@ export function getAddress(address: string, icapFormat?: boolean): string { return result; } +// http://ethereum.stackexchange.com/questions/760/how-is-the-address-of-an-ethereum-contract-computed +export function getContractAddress(transaction: { from: string, nonce: Arrayish | BigNumber | number }) { + if (!transaction.from) { throw new Error('missing from address'); } + var nonce = transaction.nonce; + + return getAddress('0x' + keccak256(encode([ + getAddress(transaction.from), + stripZeros(hexlify(nonce)) + ])).substring(26)); +} + diff --git a/src.ts/utils/contract-address.ts b/src.ts/utils/contract-address.ts deleted file mode 100644 index 8f2b3474c..000000000 --- a/src.ts/utils/contract-address.ts +++ /dev/null @@ -1,18 +0,0 @@ -'use strict'; - -import { getAddress } from './address'; -import { BigNumber } from './bignumber'; -import { Arrayish, stripZeros, hexlify } from './convert'; -import { keccak256 } from './keccak256'; -import { encode } from './rlp'; - -// http://ethereum.stackexchange.com/questions/760/how-is-the-address-of-an-ethereum-contract-computed -export function getContractAddress(transaction: { from: string, nonce: Arrayish | BigNumber | number }) { - if (!transaction.from) { throw new Error('missing from address'); } - var nonce = transaction.nonce; - - return getAddress('0x' + keccak256(encode([ - getAddress(transaction.from), - stripZeros(hexlify(nonce)) - ])).substring(26)); -} diff --git a/src.ts/utils/index.ts b/src.ts/utils/index.ts index 622d9e06e..e443f748e 100644 --- a/src.ts/utils/index.ts +++ b/src.ts/utils/index.ts @@ -3,11 +3,10 @@ // This is SUPER useful, but adds 140kb (even zipped, adds 40kb) //var unorm = require('unorm'); -import { getAddress } from './address'; +import { getAddress, getContractAddress } from './address'; import { AbiCoder, defaultAbiCoder, parseSignature } from './abi-coder'; import * as base64 from './base64'; import * as bigNumber from './bignumber'; -import { getContractAddress } from './contract-address'; import * as convert from './convert'; import { id } from './id'; import { keccak256 } from './keccak256'; diff --git a/src.ts/wallet/hdnode.ts b/src.ts/wallet/hdnode.ts index 5fdd8dfa0..84594457c 100644 --- a/src.ts/wallet/hdnode.ts +++ b/src.ts/wallet/hdnode.ts @@ -15,6 +15,8 @@ import { pbkdf2 } from '../utils/pbkdf2'; import { createSha512Hmac } from '../utils/hmac'; import { sha256 } from '../utils/sha2'; +import * as errors from '../utils/errors'; + // "Bitcoin seed" var MasterSecret = toUtf8Bytes('Bitcoin seed'); @@ -46,7 +48,7 @@ export class HDNode { // @TODO: Private constructor? constructor(keyPair: secp256k1.KeyPair, chainCode: Uint8Array, index: number, depth: number, mnemonic: string, path: string) { - //if (!(this instanceof HDNode)) { throw new Error('missing new'); } + errors.checkNew(this, HDNode); this.keyPair = keyPair; diff --git a/src.ts/wallet/signing-key.ts b/src.ts/wallet/signing-key.ts index 2e83bb024..2b067fe21 100644 --- a/src.ts/wallet/signing-key.ts +++ b/src.ts/wallet/signing-key.ts @@ -27,7 +27,7 @@ export class SigningKey { private readonly keyPair: secp256k1.KeyPair; constructor(privateKey: any) { - //errors.checkNew(this, SigningKey); + errors.checkNew(this, SigningKey); if (privateKey.privateKey) { this.mnemonic = privateKey.mnemonic; diff --git a/src/contracts/contract.d.ts b/src/contracts/contract.d.ts index ed871174c..12fe670f9 100644 --- a/src/contracts/contract.d.ts +++ b/src/contracts/contract.d.ts @@ -1,20 +1,12 @@ import { Interface } from './interface'; -import { TransactionResponse } from '../providers/provider'; -import { Network } from '../providers/networks'; +import { Provider, TransactionResponse } from '../providers/provider'; import { ParamType } from '../utils/abi-coder'; import { BigNumber } from '../utils/bignumber'; -interface Provider { - getNetwork(): Promise; - getGasPrice(): Promise; - getTransactionCount(address: string | Promise): Promise; - call(data: string): Promise; - estimateGas(tx: any): Promise; - sendTransaction(signedTransaction: string | Promise): Promise; -} interface Signer { defaultGasLimit?: BigNumber; defaultGasPrice?: BigNumber; address?: string; + provider?: Provider; getAddress(): Promise; getTransactionCount(): Promise; estimateGas(tx: any): Promise; @@ -37,8 +29,9 @@ export declare class Contract { readonly functions: Bucket; readonly events: Bucket; readonly addressPromise: Promise; - constructor(addressOrName: string, contractInterface: Contractish, signerOrProvider: any); - connect(signerOrProvider: any): Contract; - deploy(bytecode: string, ...args: any[]): Promise; + readonly deployTransaction: TransactionResponse; + constructor(addressOrName: string, contractInterface: Contractish, signerOrProvider: Signer | Provider); + connect(signerOrProvider: Signer | Provider): Contract; + deploy(bytecode: string, ...args: Array): Promise; } export {}; diff --git a/src/contracts/contract.js b/src/contracts/contract.js index 40957de02..a6f3e8614 100644 --- a/src/contracts/contract.js +++ b/src/contracts/contract.js @@ -8,6 +8,7 @@ var __importStar = (this && this.__importStar) || function (mod) { }; Object.defineProperty(exports, "__esModule", { value: true }); var interface_1 = require("./interface"); +var address_1 = require("../utils/address"); var bignumber_1 = require("../utils/bignumber"); var properties_1 = require("../utils/properties"); var errors = __importStar(require("../utils/errors")); @@ -193,12 +194,15 @@ function runMethod(contract, functionName, estimateOnly) { throw new Error('unsupport type - ' + method.type); }; } +function isSigner(value) { + return (value && value.provider != null); +} var Contract = /** @class */ (function () { // https://github.com/Microsoft/TypeScript/issues/5453 // Once this issue is resolved (there are open PR) we can do this nicer. :) function Contract(addressOrName, contractInterface, signerOrProvider) { - //if (!(this instanceof Contract)) { throw new Error('missing new'); } var _this = this; + errors.checkNew(this, Contract); // @TODO: Maybe still check the addressOrName looks like a valid address or name? //address = getAddress(address); if (contractInterface instanceof interface_1.Interface) { @@ -210,25 +214,25 @@ var Contract = /** @class */ (function () { if (!signerOrProvider) { throw new Error('missing signer or provider'); } - var signer = signerOrProvider; - var provider = null; - if (signerOrProvider.provider) { - provider = signerOrProvider.provider; + if (isSigner(signerOrProvider)) { + properties_1.defineReadOnly(this, 'provider', signerOrProvider.provider); + properties_1.defineReadOnly(this, 'signer', signerOrProvider); } else { - provider = signerOrProvider; - signer = null; + properties_1.defineReadOnly(this, 'provider', signerOrProvider); + properties_1.defineReadOnly(this, 'signer', null); } - properties_1.defineReadOnly(this, 'signer', signer); - properties_1.defineReadOnly(this, 'provider', provider); - if (!addressOrName) { - return; - } - properties_1.defineReadOnly(this, 'address', addressOrName); - properties_1.defineReadOnly(this, 'addressPromise', provider.resolveName(addressOrName)); properties_1.defineReadOnly(this, 'estimate', {}); properties_1.defineReadOnly(this, 'events', {}); properties_1.defineReadOnly(this, 'functions', {}); + // Not connected to an on-chain instance, so do not connect functions and events + if (!addressOrName) { + properties_1.defineReadOnly(this, 'address', null); + properties_1.defineReadOnly(this, 'addressPromise', Promise.resolve(null)); + return; + } + properties_1.defineReadOnly(this, 'address', addressOrName || null); + properties_1.defineReadOnly(this, 'addressPromise', this.provider.resolveName(addressOrName || null)); Object.keys(this.interface.functions).forEach(function (name) { var run = runMethod(_this, name, false); if (_this[name] == null) { @@ -245,9 +249,9 @@ var Contract = /** @class */ (function () { Object.keys(this.interface.events).forEach(function (eventName) { var eventInfo = _this.interface.events[eventName]; var eventCallback = null; - var addressPromise = _this.addressPromise; + var contract = _this; function handleEvent(log) { - addressPromise.then(function (address) { + contract.addressPromise.then(function (address) { // Not meant for us (the topics just has the same name) if (address != log.address) { return; @@ -259,11 +263,11 @@ var Contract = /** @class */ (function () { log.event = eventName; log.parse = eventInfo.parse; log.removeListener = function () { - provider.removeListener(eventInfo.topics, handleEvent); + contract.provider.removeListener(eventInfo.topics, handleEvent); }; - log.getBlock = function () { return provider.getBlock(log.blockHash); ; }; - log.getTransaction = function () { return provider.getTransaction(log.transactionHash); }; - log.getTransactionReceipt = function () { return provider.getTransactionReceipt(log.transactionHash); }; + log.getBlock = function () { return contract.provider.getBlock(log.blockHash); ; }; + log.getTransaction = function () { return contract.provider.getTransaction(log.transactionHash); }; + log.getTransactionReceipt = function () { return contract.provider.getTransactionReceipt(log.transactionHash); }; log.eventSignature = eventInfo.signature; eventCallback.apply(log, Array.prototype.slice.call(result)); } @@ -282,10 +286,10 @@ var Contract = /** @class */ (function () { value = null; } if (!value && eventCallback) { - provider.removeListener(eventInfo.topics, handleEvent); + contract.provider.removeListener(eventInfo.topics, handleEvent); } else if (value && !eventCallback) { - provider.on(eventInfo.topics, handleEvent); + contract.provider.on(eventInfo.topics, handleEvent); } eventCallback = value; } @@ -297,10 +301,15 @@ var Contract = /** @class */ (function () { Object.defineProperty(_this.events, eventName, property); }, this); } + // Reconnect to a different signer or provider Contract.prototype.connect = function (signerOrProvider) { return new Contract(this.address, this.interface, signerOrProvider); }; + // Deploy the contract with the bytecode, resolving to the deployed address. + // Use contract.deployTransaction.wait() to wait until the contract has + // been mined. Contract.prototype.deploy = function (bytecode) { + var _this = this; var args = []; for (var _i = 1; _i < arguments.length; _i++) { args[_i - 1] = arguments[_i]; @@ -311,6 +320,10 @@ var Contract = /** @class */ (function () { // @TODO: overrides of args.length = this.interface.deployFunction.inputs.length + 1 return this.signer.sendTransaction({ data: this.interface.deployFunction.encode(bytecode, args) + }).then(function (tx) { + var contract = new Contract(address_1.getContractAddress(tx), _this.interface, _this.provider); + properties_1.defineReadOnly(contract, 'deployTransaction', tx); + return contract; }); }; return Contract; diff --git a/src/contracts/interface.js b/src/contracts/interface.js index 00a7fab93..421d05fa2 100644 --- a/src/contracts/interface.js +++ b/src/contracts/interface.js @@ -303,9 +303,7 @@ function addMethod(method) { var Interface = /** @class */ (function () { function Interface(abi) { var _this = this; - if (!(this instanceof Interface)) { - throw new Error('missing new'); - } + errors.checkNew(this, Interface); if (typeof (abi) === 'string') { try { abi = JSON.parse(abi); diff --git a/src/providers/etherscan-provider.js b/src/providers/etherscan-provider.js index 30d1b2514..8c717d374 100644 --- a/src/providers/etherscan-provider.js +++ b/src/providers/etherscan-provider.js @@ -9,10 +9,18 @@ var __extends = (this && this.__extends) || (function () { d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); }; })(); +var __importStar = (this && this.__importStar) || function (mod) { + if (mod && mod.__esModule) return mod; + var result = {}; + if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k]; + result["default"] = mod; + return result; +}; Object.defineProperty(exports, "__esModule", { value: true }); var provider_1 = require("./provider"); var convert_1 = require("../utils/convert"); var web_1 = require("../utils/web"); +var errors = __importStar(require("../utils/errors")); // The transaction has already been sanitized by the calls in Provider function getTransactionString(transaction) { var result = []; @@ -75,6 +83,7 @@ var EtherscanProvider = /** @class */ (function (_super) { __extends(EtherscanProvider, _super); function EtherscanProvider(network, apiKey) { var _this = _super.call(this, network || 'homestead') || this; + errors.checkNew(_this, EtherscanProvider); var name = 'invalid'; if (_this.network) { name = _this.network.name; diff --git a/src/providers/fallback-provider.js b/src/providers/fallback-provider.js index 4966d47da..3020f2908 100644 --- a/src/providers/fallback-provider.js +++ b/src/providers/fallback-provider.js @@ -50,7 +50,6 @@ function checkNetworks(networks) { var FallbackProvider = /** @class */ (function (_super) { __extends(FallbackProvider, _super); function FallbackProvider(providers) { - //if (!(this instanceof FallbackProvider)) { throw new Error('missing new'); } var _this = this; if (providers.length === 0) { throw new Error('no providers'); @@ -58,9 +57,11 @@ var FallbackProvider = /** @class */ (function (_super) { var ready = checkNetworks(providers.map(function (p) { return p.network; })); if (ready) { _this = _super.call(this, providers[0].network) || this; + errors.checkNew(_this, FallbackProvider); } else { _this = _super.call(this, null) || this; + errors.checkNew(_this, FallbackProvider); // We re-assign the ready function to make sure all networks actually match _this.ready = Promise.all(providers.map(function (p) { return p.getNetwork(); })).then(function (networks) { if (!checkNetworks(networks)) { diff --git a/src/providers/infura-provider.js b/src/providers/infura-provider.js index f7a1bd97d..64011b2c6 100644 --- a/src/providers/infura-provider.js +++ b/src/providers/infura-provider.js @@ -23,7 +23,6 @@ var errors = __importStar(require("../utils/errors")); var InfuraProvider = /** @class */ (function (_super) { __extends(InfuraProvider, _super); function InfuraProvider(network, apiAccessToken) { - //errors.checkNew(this, InfuraProvider); var _this = this; network = networks_1.getNetwork(network || 'homestead'); var host = null; @@ -44,6 +43,7 @@ var InfuraProvider = /** @class */ (function (_super) { throw new Error('unsupported network'); } _this = _super.call(this, 'https://' + host + '/' + (apiAccessToken || ''), network) || this; + errors.checkNew(_this, InfuraProvider); _this.apiAccessToken = (apiAccessToken || null); return _this; } diff --git a/src/providers/ipc-provider.js b/src/providers/ipc-provider.js index 91db4ee46..3d2b7309c 100644 --- a/src/providers/ipc-provider.js +++ b/src/providers/ipc-provider.js @@ -26,12 +26,12 @@ var errors = __importStar(require("../utils/errors")); var IpcProvider = /** @class */ (function (_super) { __extends(IpcProvider, _super); function IpcProvider(path, network) { - //errors.checkNew(this, IpcProvider); var _this = this; if (path == null) { errors.throwError('missing path', errors.MISSING_ARGUMENT, { arg: 'path' }); } _this = _super.call(this, 'ipc://' + path, network) || this; + errors.checkNew(_this, IpcProvider); _this.path = path; return _this; } diff --git a/src/providers/json-rpc-provider.js b/src/providers/json-rpc-provider.js index 08ea8075e..3125080c3 100644 --- a/src/providers/json-rpc-provider.js +++ b/src/providers/json-rpc-provider.js @@ -77,7 +77,7 @@ function getLowerCase(value) { var JsonRpcSigner = /** @class */ (function () { // private _syncAddress: boolean; function JsonRpcSigner(provider, address) { - //errors.checkNew(this, JsonRpcSigner); + errors.checkNew(this, JsonRpcSigner); this.provider = provider; // Statically attach to a given address if (address) { @@ -165,7 +165,6 @@ exports.JsonRpcSigner = JsonRpcSigner; var JsonRpcProvider = /** @class */ (function (_super) { __extends(JsonRpcProvider, _super); function JsonRpcProvider(url, network) { - //errors.checkNew(this, JsonRpcProvider); var _this = this; // One parameter, but it is a network name, so swap it with the URL if (typeof (url) === 'string') { @@ -175,6 +174,7 @@ var JsonRpcProvider = /** @class */ (function (_super) { } } _this = _super.call(this, network) || this; + errors.checkNew(_this, JsonRpcProvider); // Default URL if (!url) { url = 'http://localhost:8545'; diff --git a/src/providers/provider.d.ts b/src/providers/provider.d.ts index a032d7390..67cdb75c1 100644 --- a/src/providers/provider.d.ts +++ b/src/providers/provider.d.ts @@ -98,10 +98,10 @@ export declare class Provider { waitForTransaction(transactionHash: string, timeout?: number): Promise; getBlockNumber(): Promise; getGasPrice(): Promise; - getBalance(addressOrName: string | Promise, blockTag: BlockTag | Promise): Promise; - getTransactionCount(addressOrName: string | Promise, blockTag: BlockTag | Promise): Promise; - getCode(addressOrName: string | Promise, blockTag: BlockTag | Promise): Promise; - getStorageAt(addressOrName: string | Promise, position: BigNumberish | Promise, blockTag: BlockTag | Promise): Promise; + getBalance(addressOrName: string | Promise, blockTag?: BlockTag | Promise): Promise; + getTransactionCount(addressOrName: string | Promise, blockTag?: BlockTag | Promise): Promise; + getCode(addressOrName: string | Promise, blockTag?: BlockTag | Promise): Promise; + getStorageAt(addressOrName: string | Promise, position: BigNumberish | Promise, blockTag?: BlockTag | Promise): Promise; sendTransaction(signedTransaction: string | Promise): Promise; call(transaction: TransactionRequest): Promise; estimateGas(transaction: TransactionRequest): Promise; diff --git a/src/providers/provider.js b/src/providers/provider.js index 19826bdfe..2f8aa1239 100644 --- a/src/providers/provider.js +++ b/src/providers/provider.js @@ -8,10 +8,8 @@ var __importStar = (this && this.__importStar) || function (mod) { }; Object.defineProperty(exports, "__esModule", { value: true }); //import inherits = require('inherits'); -//import networks = require('./networks.json'); var address_1 = require("../utils/address"); var bignumber_1 = require("../utils/bignumber"); -var contract_address_1 = require("../utils/contract-address"); var convert_1 = require("../utils/convert"); var utf8_1 = require("../utils/utf8"); var rlp_1 = require("../utils/rlp"); @@ -194,7 +192,7 @@ function checkTransactionResponse(transaction) { } // If to and creates are empty, populate the creates from the transaction if (transaction.to == null && transaction.creates == null) { - transaction.creates = contract_address_1.getContractAddress(transaction); + transaction.creates = address_1.getContractAddress(transaction); } if (!transaction.raw) { // Very loose providers (e.g. TestRPC) don't provide a signature or raw @@ -438,7 +436,7 @@ var Provider = /** @class */ (function () { * - Otherwise, the sub-class must assign a Promise to ready */ function Provider(network) { - //if (!(this instanceof Provider)) { throw new Error('missing new'); } + errors.checkNew(this, Provider); network = networks_1.getNetwork(network); if (network) { this._network = network; diff --git a/src/providers/web3-provider.js b/src/providers/web3-provider.js index 6b9bed2a5..d80e7bec1 100644 --- a/src/providers/web3-provider.js +++ b/src/providers/web3-provider.js @@ -22,7 +22,6 @@ var errors = __importStar(require("../utils/errors")); var Web3Provider = /** @class */ (function (_super) { __extends(Web3Provider, _super); function Web3Provider(web3Provider, network) { - //errors.checkNew(this, Web3Provider); var _this = this; if (!web3Provider || !web3Provider.sendAsync) { errors.throwError('invalid web3Provider', errors.INVALID_ARGUMENT, { arg: 'web3Provider', value: web3Provider }); @@ -30,6 +29,7 @@ var Web3Provider = /** @class */ (function (_super) { // HTTP has a host; IPC has a path. var url = web3Provider.host || web3Provider.path || 'unknown'; _this = _super.call(this, url, network) || this; + errors.checkNew(_this, Web3Provider); _this._web3Provider = web3Provider; return _this; } diff --git a/src/utils/address.d.ts b/src/utils/address.d.ts index 55cb0ea8f..8ac6ebd84 100644 --- a/src/utils/address.d.ts +++ b/src/utils/address.d.ts @@ -1 +1,7 @@ +import { BigNumber } from './bignumber'; +import { Arrayish } from './convert'; export declare function getAddress(address: string, icapFormat?: boolean): string; +export declare function getContractAddress(transaction: { + from: string; + nonce: Arrayish | BigNumber | number; +}): string; diff --git a/src/utils/address.js b/src/utils/address.js index 270ae5556..4aebad762 100644 --- a/src/utils/address.js +++ b/src/utils/address.js @@ -1,8 +1,10 @@ 'use strict'; Object.defineProperty(exports, "__esModule", { value: true }); +// We use this for base 36 maths var BN = require("bn.js"); var convert_1 = require("./convert"); var keccak256_1 = require("./keccak256"); +var rlp_1 = require("./rlp"); var errors = require("./errors"); function getChecksumAddress(address) { if (typeof (address) !== 'string' || !address.match(/^0x[0-9A-Fa-f]{40}$/)) { @@ -104,3 +106,15 @@ function getAddress(address, icapFormat) { return result; } exports.getAddress = getAddress; +// http://ethereum.stackexchange.com/questions/760/how-is-the-address-of-an-ethereum-contract-computed +function getContractAddress(transaction) { + if (!transaction.from) { + throw new Error('missing from address'); + } + var nonce = transaction.nonce; + return getAddress('0x' + keccak256_1.keccak256(rlp_1.encode([ + getAddress(transaction.from), + convert_1.stripZeros(convert_1.hexlify(nonce)) + ])).substring(26)); +} +exports.getContractAddress = getContractAddress; diff --git a/src/utils/index.d.ts b/src/utils/index.d.ts index e6485fb0c..a8cc86d7e 100644 --- a/src/utils/index.d.ts +++ b/src/utils/index.d.ts @@ -1,8 +1,7 @@ -import { getAddress } from './address'; +import { getAddress, getContractAddress } from './address'; import { AbiCoder, parseSignature } from './abi-coder'; import * as base64 from './base64'; import * as bigNumber from './bignumber'; -import { getContractAddress } from './contract-address'; import * as convert from './convert'; import { id } from './id'; import { keccak256 } from './keccak256'; diff --git a/src/utils/index.js b/src/utils/index.js index 6978009b2..5a463c4c9 100644 --- a/src/utils/index.js +++ b/src/utils/index.js @@ -13,7 +13,6 @@ var address_1 = require("./address"); var abi_coder_1 = require("./abi-coder"); var base64 = __importStar(require("./base64")); var bigNumber = __importStar(require("./bignumber")); -var contract_address_1 = require("./contract-address"); var convert = __importStar(require("./convert")); var id_1 = require("./id"); var keccak256_1 = require("./keccak256"); @@ -50,7 +49,7 @@ exports.default = { namehash: namehash_1.namehash, id: id_1.id, getAddress: address_1.getAddress, - getContractAddress: contract_address_1.getContractAddress, + getContractAddress: address_1.getContractAddress, formatEther: units.formatEther, parseEther: units.parseEther, formatUnits: units.formatUnits, diff --git a/src/wallet/hdnode.js b/src/wallet/hdnode.js index c1b96cf7d..6e65284bb 100644 --- a/src/wallet/hdnode.js +++ b/src/wallet/hdnode.js @@ -21,6 +21,7 @@ var utf8_1 = require("../utils/utf8"); var pbkdf2_1 = require("../utils/pbkdf2"); var hmac_1 = require("../utils/hmac"); var sha2_1 = require("../utils/sha2"); +var errors = __importStar(require("../utils/errors")); // "Bitcoin seed" var MasterSecret = utf8_1.toUtf8Bytes('Bitcoin seed'); var HardenedBit = 0x80000000; @@ -35,7 +36,7 @@ function getLowerMask(bits) { var HDNode = /** @class */ (function () { // @TODO: Private constructor? function HDNode(keyPair, chainCode, index, depth, mnemonic, path) { - //if (!(this instanceof HDNode)) { throw new Error('missing new'); } + errors.checkNew(this, HDNode); this.keyPair = keyPair; this.privateKey = convert_1.hexlify(keyPair.priv.toArray('be', 32)); this.publicKey = '0x' + keyPair.getPublic(true, 'hex'); diff --git a/src/wallet/signing-key.js b/src/wallet/signing-key.js index 0ba18b592..86fc84ad2 100644 --- a/src/wallet/signing-key.js +++ b/src/wallet/signing-key.js @@ -19,7 +19,7 @@ var keccak256_1 = require("../utils/keccak256"); var errors = require("../utils/errors"); var SigningKey = /** @class */ (function () { function SigningKey(privateKey) { - //errors.checkNew(this, SigningKey); + errors.checkNew(this, SigningKey); if (privateKey.privateKey) { this.mnemonic = privateKey.mnemonic; this.path = privateKey.path;