diff options
28 files changed, 400 insertions, 277 deletions
diff --git a/packages/0x.js/package.json b/packages/0x.js/package.json index db6e2363e..ce8ed859a 100644 --- a/packages/0x.js/package.json +++ b/packages/0x.js/package.json @@ -47,6 +47,7 @@ "@0xproject/monorepo-scripts": "^1.0.7", "@0xproject/tslint-config": "^1.0.6", "@types/lodash": "4.14.104", + "@types/web3-provider-engine": "^14.0.0", "@types/mocha": "^2.2.42", "@types/node": "^8.0.53", "@types/sinon": "^2.2.2", diff --git a/packages/contract-wrappers/package.json b/packages/contract-wrappers/package.json index 7da839a00..5d5c8069a 100644 --- a/packages/contract-wrappers/package.json +++ b/packages/contract-wrappers/package.json @@ -49,6 +49,7 @@ "@0xproject/subproviders": "^2.0.1", "@0xproject/tslint-config": "^1.0.6", "@types/lodash": "4.14.104", + "@types/web3-provider-engine": "^14.0.0", "@types/mocha": "^2.2.42", "@types/node": "^8.0.53", "@types/sinon": "^2.2.2", diff --git a/packages/contracts/src/2.0.0/extensions/Forwarder/MixinExchangeWrapper.sol b/packages/contracts/src/2.0.0/extensions/Forwarder/MixinExchangeWrapper.sol index 33326c2ab..fea9a53c2 100644 --- a/packages/contracts/src/2.0.0/extensions/Forwarder/MixinExchangeWrapper.sol +++ b/packages/contracts/src/2.0.0/extensions/Forwarder/MixinExchangeWrapper.sol @@ -60,7 +60,7 @@ contract MixinExchangeWrapper is // Call `fillOrder` and handle any exceptions gracefully assembly { let success := call( - gas, // forward all gas, TODO: look into gas consumption of assert/throw + gas, // forward all gas exchange, // call address of Exchange contract 0, // transfer 0 wei add(fillOrderCalldata, 32), // pointer to start of input (skip array length in first 32 bytes) diff --git a/packages/contracts/src/2.0.0/multisig/MultiSigWallet.sol b/packages/contracts/src/2.0.0/multisig/MultiSigWallet.sol index eb54fe047..516e7391c 100644 --- a/packages/contracts/src/2.0.0/multisig/MultiSigWallet.sol +++ b/packages/contracts/src/2.0.0/multisig/MultiSigWallet.sol @@ -1,13 +1,14 @@ // solhint-disable -pragma solidity ^0.4.10; +pragma solidity ^0.4.15; /// @title Multisignature wallet - Allows multiple parties to agree on transactions before execution. /// @author Stefan George - <stefan.george@consensys.net> contract MultiSigWallet { - uint constant public MAX_OWNER_COUNT = 50; - + /* + * Events + */ event Confirmation(address indexed sender, uint indexed transactionId); event Revocation(address indexed sender, uint indexed transactionId); event Submission(uint indexed transactionId); @@ -18,6 +19,14 @@ contract MultiSigWallet { event OwnerRemoval(address indexed owner); event RequirementChange(uint required); + /* + * Constants + */ + uint constant public MAX_OWNER_COUNT = 50; + + /* + * Storage + */ mapping (uint => Transaction) public transactions; mapping (uint => mapping (address => bool)) public confirmations; mapping (address => bool) public isOwner; @@ -32,60 +41,54 @@ contract MultiSigWallet { bool executed; } + /* + * Modifiers + */ modifier onlyWallet() { - if (msg.sender != address(this)) - throw; + require(msg.sender == address(this)); _; } modifier ownerDoesNotExist(address owner) { - if (isOwner[owner]) - throw; + require(!isOwner[owner]); _; } modifier ownerExists(address owner) { - if (!isOwner[owner]) - throw; + require(isOwner[owner]); _; } modifier transactionExists(uint transactionId) { - if (transactions[transactionId].destination == 0) - throw; + require(transactions[transactionId].destination != 0); _; } modifier confirmed(uint transactionId, address owner) { - if (!confirmations[transactionId][owner]) - throw; + require(confirmations[transactionId][owner]); _; } modifier notConfirmed(uint transactionId, address owner) { - if (confirmations[transactionId][owner]) - throw; + require(!confirmations[transactionId][owner]); _; } modifier notExecuted(uint transactionId) { - if (transactions[transactionId].executed) - throw; + require(!transactions[transactionId].executed); _; } modifier notNull(address _address) { - if (_address == 0) - throw; + require(_address != 0); _; } modifier validRequirement(uint ownerCount, uint _required) { - if ( ownerCount > MAX_OWNER_COUNT - || _required > ownerCount - || _required == 0 - || ownerCount == 0) - throw; + require(ownerCount <= MAX_OWNER_COUNT + && _required <= ownerCount + && _required != 0 + && ownerCount != 0); _; } @@ -108,8 +111,7 @@ contract MultiSigWallet { validRequirement(_owners.length, _required) { for (uint i=0; i<_owners.length; i++) { - if (isOwner[_owners[i]] || _owners[i] == 0) - throw; + require(!isOwner[_owners[i]] && _owners[i] != 0); isOwner[_owners[i]] = true; } owners = _owners; @@ -151,7 +153,7 @@ contract MultiSigWallet { /// @dev Allows to replace an owner with a new owner. Transaction has to be sent by wallet. /// @param owner Address of owner to be replaced. - /// @param owner Address of new owner. + /// @param newOwner Address of new owner. function replaceOwner(address owner, address newOwner) public onlyWallet @@ -222,20 +224,44 @@ contract MultiSigWallet { /// @param transactionId Transaction ID. function executeTransaction(uint transactionId) public + ownerExists(msg.sender) + confirmed(transactionId, msg.sender) notExecuted(transactionId) { if (isConfirmed(transactionId)) { - Transaction tx = transactions[transactionId]; - tx.executed = true; - if (tx.destination.call.value(tx.value)(tx.data)) + Transaction storage txn = transactions[transactionId]; + txn.executed = true; + if (external_call(txn.destination, txn.value, txn.data.length, txn.data)) Execution(transactionId); else { ExecutionFailure(transactionId); - tx.executed = false; + txn.executed = false; } } } + // call has been separated into its own function in order to take advantage + // of the Solidity's code generator to produce a loop that copies tx.data into memory. + function external_call(address destination, uint value, uint dataLength, bytes data) internal returns (bool) { + bool result; + assembly { + let x := mload(0x40) // "Allocate" memory for output (0x40 is where "free memory" pointer is stored by convention) + let d := add(data, 32) // First 32 bytes are the padded length of data, so exclude that + result := call( + sub(gas, 34710), // 34710 is the value that solidity is currently emitting + // It includes callGas (700) + callVeryLow (3, to pay for SUB) + callValueTransferGas (9000) + + // callNewAccountGas (25000, in case the destination address does not exist and needs creating) + destination, + value, + d, + dataLength, // Size of the input (in bytes) - this is what fixes the padding problem + x, + 0 // Output is ignored, therefore the output size is zero + ) + } + return result; + } + /// @dev Returns the confirmation status of a transaction. /// @param transactionId Transaction ID. /// @return Confirmation status. @@ -364,4 +390,4 @@ contract MultiSigWallet { for (i=from; i<to; i++) _transactionIds[i - from] = transactionIdsTemp[i]; } -} +}
\ No newline at end of file diff --git a/packages/contracts/src/2.0.0/multisig/MultiSigWalletWithTimeLock.sol b/packages/contracts/src/2.0.0/multisig/MultiSigWalletWithTimeLock.sol index 8c5e6e1e6..9513d3b30 100644 --- a/packages/contracts/src/2.0.0/multisig/MultiSigWalletWithTimeLock.sol +++ b/packages/contracts/src/2.0.0/multisig/MultiSigWalletWithTimeLock.sol @@ -16,47 +16,57 @@ */ -// solhint-disable -pragma solidity ^0.4.10; +pragma solidity 0.4.24; import "./MultiSigWallet.sol"; /// @title Multisignature wallet with time lock- Allows multiple parties to execute a transaction after a time lock has passed. /// @author Amir Bandeali - <amir@0xProject.com> -contract MultiSigWalletWithTimeLock is MultiSigWallet { - - event ConfirmationTimeSet(uint indexed transactionId, uint confirmationTime); - event TimeLockChange(uint secondsTimeLocked); - - uint public secondsTimeLocked; - - mapping (uint => uint) public confirmationTimes; - - modifier notFullyConfirmed(uint transactionId) { - require(!isConfirmed(transactionId)); +// solhint-disable not-rely-on-time +contract MultiSigWalletWithTimeLock is + MultiSigWallet +{ + event ConfirmationTimeSet(uint256 indexed transactionId, uint256 confirmationTime); + event TimeLockChange(uint256 secondsTimeLocked); + + uint256 public secondsTimeLocked; + + mapping (uint256 => uint256) public confirmationTimes; + + modifier notFullyConfirmed(uint256 transactionId) { + require( + !isConfirmed(transactionId), + "TX_FULLY_CONFIRMED" + ); _; } - modifier fullyConfirmed(uint transactionId) { - require(isConfirmed(transactionId)); + modifier fullyConfirmed(uint256 transactionId) { + require( + isConfirmed(transactionId), + "TX_NOT_FULLY_CONFIRMED" + ); _; } - modifier pastTimeLock(uint transactionId) { - require(block.timestamp >= confirmationTimes[transactionId] + secondsTimeLocked); + modifier pastTimeLock(uint256 transactionId) { + require( + block.timestamp >= confirmationTimes[transactionId] + secondsTimeLocked, + "TIME_LOCK_INCOMPLETE" + ); _; } - /* - * Public functions - */ - /// @dev Contract constructor sets initial owners, required number of confirmations, and time lock. /// @param _owners List of initial owners. /// @param _required Number of required confirmations. /// @param _secondsTimeLocked Duration needed after a transaction is confirmed and before it becomes executable, in seconds. - function MultiSigWalletWithTimeLock(address[] _owners, uint _required, uint _secondsTimeLocked) + constructor ( + address[] _owners, + uint256 _required, + uint256 _secondsTimeLocked + ) public MultiSigWallet(_owners, _required) { @@ -65,17 +75,17 @@ contract MultiSigWalletWithTimeLock is MultiSigWallet { /// @dev Changes the duration of the time lock for transactions. /// @param _secondsTimeLocked Duration needed after a transaction is confirmed and before it becomes executable, in seconds. - function changeTimeLock(uint _secondsTimeLocked) + function changeTimeLock(uint256 _secondsTimeLocked) public onlyWallet { secondsTimeLocked = _secondsTimeLocked; - TimeLockChange(_secondsTimeLocked); + emit TimeLockChange(_secondsTimeLocked); } /// @dev Allows an owner to confirm a transaction. /// @param transactionId Transaction ID. - function confirmTransaction(uint transactionId) + function confirmTransaction(uint256 transactionId) public ownerExists(msg.sender) transactionExists(transactionId) @@ -83,52 +93,35 @@ contract MultiSigWalletWithTimeLock is MultiSigWallet { notFullyConfirmed(transactionId) { confirmations[transactionId][msg.sender] = true; - Confirmation(msg.sender, transactionId); + emit Confirmation(msg.sender, transactionId); if (isConfirmed(transactionId)) { setConfirmationTime(transactionId, block.timestamp); } } - /// @dev Allows an owner to revoke a confirmation for a transaction. - /// @param transactionId Transaction ID. - function revokeConfirmation(uint transactionId) - public - ownerExists(msg.sender) - confirmed(transactionId, msg.sender) - notExecuted(transactionId) - notFullyConfirmed(transactionId) - { - confirmations[transactionId][msg.sender] = false; - Revocation(msg.sender, transactionId); - } - /// @dev Allows anyone to execute a confirmed transaction. /// @param transactionId Transaction ID. - function executeTransaction(uint transactionId) + function executeTransaction(uint256 transactionId) public notExecuted(transactionId) fullyConfirmed(transactionId) pastTimeLock(transactionId) { - Transaction storage tx = transactions[transactionId]; - tx.executed = true; - if (tx.destination.call.value(tx.value)(tx.data)) - Execution(transactionId); - else { - ExecutionFailure(transactionId); - tx.executed = false; + Transaction storage txn = transactions[transactionId]; + txn.executed = true; + if (external_call(txn.destination, txn.value, txn.data.length, txn.data)) { + emit Execution(transactionId); + } else { + emit ExecutionFailure(transactionId); + txn.executed = false; } } - /* - * Internal functions - */ - /// @dev Sets the time of when a submission first passed. - function setConfirmationTime(uint transactionId, uint confirmationTime) + function setConfirmationTime(uint256 transactionId, uint256 confirmationTime) internal { confirmationTimes[transactionId] = confirmationTime; - ConfirmationTimeSet(transactionId, confirmationTime); + emit ConfirmationTimeSet(transactionId, confirmationTime); } } diff --git a/packages/contracts/src/2.0.0/protocol/AssetProxyOwner/AssetProxyOwner.sol b/packages/contracts/src/2.0.0/protocol/AssetProxyOwner/AssetProxyOwner.sol index 5f69198e9..edb788fab 100644 --- a/packages/contracts/src/2.0.0/protocol/AssetProxyOwner/AssetProxyOwner.sol +++ b/packages/contracts/src/2.0.0/protocol/AssetProxyOwner/AssetProxyOwner.sol @@ -38,13 +38,13 @@ contract AssetProxyOwner is /// @dev Function will revert if the transaction does not call `removeAuthorizedAddressAtIndex` /// on an approved AssetProxy contract. modifier validRemoveAuthorizedAddressAtIndexTx(uint256 transactionId) { - Transaction storage tx = transactions[transactionId]; + Transaction storage txn = transactions[transactionId]; require( - isAssetProxyRegistered[tx.destination], + isAssetProxyRegistered[txn.destination], "UNREGISTERED_ASSET_PROXY" ); require( - tx.data.readBytes4(0) == REMOVE_AUTHORIZED_ADDRESS_AT_INDEX_SELECTOR, + txn.data.readBytes4(0) == REMOVE_AUTHORIZED_ADDRESS_AT_INDEX_SELECTOR, "INVALID_FUNCTION_SELECTOR" ); _; @@ -96,14 +96,13 @@ contract AssetProxyOwner is fullyConfirmed(transactionId) validRemoveAuthorizedAddressAtIndexTx(transactionId) { - Transaction storage tx = transactions[transactionId]; - tx.executed = true; - // solhint-disable-next-line avoid-call-value - if (tx.destination.call.value(tx.value)(tx.data)) + Transaction storage txn = transactions[transactionId]; + txn.executed = true; + if (external_call(txn.destination, txn.value, txn.data.length, txn.data)) { emit Execution(transactionId); - else { + } else { emit ExecutionFailure(transactionId); - tx.executed = false; + txn.executed = false; } } } diff --git a/packages/contracts/src/2.0.0/protocol/Exchange/MixinWrapperFunctions.sol b/packages/contracts/src/2.0.0/protocol/Exchange/MixinWrapperFunctions.sol index fc3210d90..cddff0e5f 100644 --- a/packages/contracts/src/2.0.0/protocol/Exchange/MixinWrapperFunctions.sol +++ b/packages/contracts/src/2.0.0/protocol/Exchange/MixinWrapperFunctions.sol @@ -81,7 +81,7 @@ contract MixinWrapperFunctions is // Delegate to `fillOrder` and handle any exceptions gracefully assembly { let success := delegatecall( - gas, // forward all gas, TODO: look into gas consumption of assert/throw + gas, // forward all gas address, // call address of this contract add(fillOrderCalldata, 32), // pointer to start of input (skip array length in first 32 bytes) mload(fillOrderCalldata), // length of input diff --git a/packages/contracts/src/2.0.0/protocol/Exchange/libs/LibMath.sol b/packages/contracts/src/2.0.0/protocol/Exchange/libs/LibMath.sol index d8fed176c..c0b85ea10 100644 --- a/packages/contracts/src/2.0.0/protocol/Exchange/libs/LibMath.sol +++ b/packages/contracts/src/2.0.0/protocol/Exchange/libs/LibMath.sol @@ -246,7 +246,6 @@ contract LibMath is numerator, denominator ); - // TODO: safeMod remainder = safeSub(denominator, remainder) % denominator; isError = safeMul(1000, remainder) >= safeMul(numerator, target); return isError; diff --git a/packages/contracts/test/multisig/asset_proxy_owner.ts b/packages/contracts/test/multisig/asset_proxy_owner.ts index bb2b3b1a3..299707512 100644 --- a/packages/contracts/test/multisig/asset_proxy_owner.ts +++ b/packages/contracts/test/multisig/asset_proxy_owner.ts @@ -34,6 +34,7 @@ const blockchainLifecycle = new BlockchainLifecycle(web3Wrapper); describe('AssetProxyOwner', () => { let owners: string[]; let authorized: string; + let notOwner: string; const REQUIRED_APPROVALS = new BigNumber(2); const SECONDS_TIME_LOCKED = new BigNumber(1000000); @@ -51,7 +52,9 @@ describe('AssetProxyOwner', () => { before(async () => { const accounts = await web3Wrapper.getAvailableAddressesAsync(); owners = [accounts[0], accounts[1]]; - const initialOwner = (authorized = accounts[0]); + authorized = accounts[2]; + notOwner = accounts[3]; + const initialOwner = accounts[0]; erc20Proxy = await MixinAuthorizableContract.deployFrom0xArtifactAsync( artifacts.MixinAuthorizable, provider, @@ -269,8 +272,12 @@ describe('AssetProxyOwner', () => { await multiSigWrapper.confirmTransactionAsync(erc721AddAuthorizedAddressTxId, owners[1]); await increaseTimeAndMineBlockAsync(SECONDS_TIME_LOCKED.toNumber()); await multiSigWrapper.executeTransactionAsync(registerAssetProxyTxId, owners[0]); - await multiSigWrapper.executeTransactionAsync(erc20AddAuthorizedAddressTxId, owners[0]); - await multiSigWrapper.executeTransactionAsync(erc721AddAuthorizedAddressTxId, owners[0]); + await multiSigWrapper.executeTransactionAsync(erc20AddAuthorizedAddressTxId, owners[0], { + gas: constants.MAX_EXECUTE_TRANSACTION_GAS, + }); + await multiSigWrapper.executeTransactionAsync(erc721AddAuthorizedAddressTxId, owners[0], { + gas: constants.MAX_EXECUTE_TRANSACTION_GAS, + }); }); describe('validRemoveAuthorizedAddressAtIndexTx', () => { @@ -342,10 +349,11 @@ describe('AssetProxyOwner', () => { const log = res.logs[0] as LogWithDecodedArgs<AssetProxyOwnerSubmissionEventArgs>; const txId = log.args.transactionId; - return expectTransactionFailedWithoutReasonAsync( + return expectTransactionFailedAsync( testAssetProxyOwner.executeRemoveAuthorizedAddressAtIndex.sendTransactionAsync(txId, { from: owners[1], }), + RevertReason.TxNotFullyConfirmed, ); }); @@ -395,7 +403,10 @@ describe('AssetProxyOwner', () => { ); }); - it('should execute removeAuthorizedAddressAtIndex for registered address if fully confirmed', async () => { + it('should execute removeAuthorizedAddressAtIndex for registered address if fully confirmed and called by owner', async () => { + const isAuthorizedBefore = await erc20Proxy.authorized.callAsync(authorized); + expect(isAuthorizedBefore).to.equal(true); + const removeAuthorizedAddressAtIndexData = erc20Proxy.removeAuthorizedAddressAtIndex.getABIEncodedTransactionData( authorized, erc20Index, @@ -418,8 +429,38 @@ describe('AssetProxyOwner', () => { const isExecuted = tx[3]; expect(isExecuted).to.equal(true); - const isAuthorized = await erc20Proxy.authorized.callAsync(authorized); - expect(isAuthorized).to.equal(false); + const isAuthorizedAfter = await erc20Proxy.authorized.callAsync(authorized); + expect(isAuthorizedAfter).to.equal(false); + }); + + it('should execute removeAuthorizedAddressAtIndex for registered address if fully confirmed and called by non-owner', async () => { + const isAuthorizedBefore = await erc20Proxy.authorized.callAsync(authorized); + expect(isAuthorizedBefore).to.equal(true); + + const removeAuthorizedAddressAtIndexData = erc20Proxy.removeAuthorizedAddressAtIndex.getABIEncodedTransactionData( + authorized, + erc20Index, + ); + const submitRes = await multiSigWrapper.submitTransactionAsync( + erc20Proxy.address, + removeAuthorizedAddressAtIndexData, + owners[0], + ); + const submitLog = submitRes.logs[0] as LogWithDecodedArgs<AssetProxyOwnerSubmissionEventArgs>; + const txId = submitLog.args.transactionId; + + await multiSigWrapper.confirmTransactionAsync(txId, owners[1]); + + const execRes = await multiSigWrapper.executeRemoveAuthorizedAddressAtIndexAsync(txId, notOwner); + const execLog = execRes.logs[1] as LogWithDecodedArgs<AssetProxyOwnerExecutionEventArgs>; + expect(execLog.args.transactionId).to.be.bignumber.equal(txId); + + const tx = await testAssetProxyOwner.transactions.callAsync(txId); + const isExecuted = tx[3]; + expect(isExecuted).to.equal(true); + + const isAuthorizedAfter = await erc20Proxy.authorized.callAsync(authorized); + expect(isAuthorizedAfter).to.equal(false); }); it('should throw if already executed', async () => { diff --git a/packages/contracts/test/multisig/multi_sig_with_time_lock.ts b/packages/contracts/test/multisig/multi_sig_with_time_lock.ts index 8eeeeca6b..bc1de7ed4 100644 --- a/packages/contracts/test/multisig/multi_sig_with_time_lock.ts +++ b/packages/contracts/test/multisig/multi_sig_with_time_lock.ts @@ -1,14 +1,21 @@ import { BlockchainLifecycle } from '@0xproject/dev-utils'; +import { RevertReason } from '@0xproject/types'; import { BigNumber } from '@0xproject/utils'; import * as chai from 'chai'; import { LogWithDecodedArgs } from 'ethereum-types'; +import * as _ from 'lodash'; +import { DummyERC20TokenContract } from '../../generated_contract_wrappers/dummy_erc20_token'; import { + MultiSigWalletWithTimeLockConfirmationEventArgs, + MultiSigWalletWithTimeLockConfirmationTimeSetEventArgs, MultiSigWalletWithTimeLockContract, + MultiSigWalletWithTimeLockExecutionEventArgs, + MultiSigWalletWithTimeLockExecutionFailureEventArgs, MultiSigWalletWithTimeLockSubmissionEventArgs, } from '../../generated_contract_wrappers/multi_sig_wallet_with_time_lock'; import { artifacts } from '../utils/artifacts'; -import { expectTransactionFailedWithoutReasonAsync } from '../utils/assertions'; +import { expectTransactionFailedAsync, expectTransactionFailedWithoutReasonAsync } from '../utils/assertions'; import { increaseTimeAndMineBlockAsync } from '../utils/block_timestamp'; import { chaiSetup } from '../utils/chai_setup'; import { constants } from '../utils/constants'; @@ -21,6 +28,7 @@ const blockchainLifecycle = new BlockchainLifecycle(web3Wrapper); // tslint:disable:no-unnecessary-type-assertion describe('MultiSigWalletWithTimeLock', () => { let owners: string[]; + let notOwner: string; const REQUIRED_APPROVALS = new BigNumber(2); const SECONDS_TIME_LOCKED = new BigNumber(1000000); @@ -32,7 +40,8 @@ describe('MultiSigWalletWithTimeLock', () => { }); before(async () => { const accounts = await web3Wrapper.getAvailableAddressesAsync(); - owners = [accounts[0], accounts[1]]; + owners = [accounts[0], accounts[1], accounts[2]]; + notOwner = accounts[3]; }); let multiSig: MultiSigWalletWithTimeLockContract; @@ -45,6 +54,171 @@ describe('MultiSigWalletWithTimeLock', () => { await blockchainLifecycle.revertAsync(); }); + describe('external_call', () => { + it('should be internal', async () => { + const secondsTimeLocked = new BigNumber(0); + multiSig = await MultiSigWalletWithTimeLockContract.deployFrom0xArtifactAsync( + artifacts.MultiSigWalletWithTimeLock, + provider, + txDefaults, + owners, + REQUIRED_APPROVALS, + secondsTimeLocked, + ); + expect(_.isUndefined((multiSig as any).external_call)).to.be.equal(true); + }); + }); + describe('confirmTransaction', () => { + let txId: BigNumber; + beforeEach(async () => { + const secondsTimeLocked = new BigNumber(0); + multiSig = await MultiSigWalletWithTimeLockContract.deployFrom0xArtifactAsync( + artifacts.MultiSigWalletWithTimeLock, + provider, + txDefaults, + owners, + REQUIRED_APPROVALS, + secondsTimeLocked, + ); + multiSigWrapper = new MultiSigWrapper(multiSig, provider); + const destination = notOwner; + const data = constants.NULL_BYTES; + const txReceipt = await multiSigWrapper.submitTransactionAsync(destination, data, owners[0]); + txId = (txReceipt.logs[0] as LogWithDecodedArgs<MultiSigWalletWithTimeLockSubmissionEventArgs>).args + .transactionId; + }); + it('should revert if called by a non-owner', async () => { + await expectTransactionFailedWithoutReasonAsync(multiSigWrapper.confirmTransactionAsync(txId, notOwner)); + }); + it('should revert if transaction does not exist', async () => { + const nonexistentTxId = new BigNumber(123456789); + await expectTransactionFailedWithoutReasonAsync( + multiSigWrapper.confirmTransactionAsync(nonexistentTxId, owners[1]), + ); + }); + it('should revert if transaction is already confirmed by caller', async () => { + await expectTransactionFailedWithoutReasonAsync(multiSigWrapper.confirmTransactionAsync(txId, owners[0])); + }); + it('should confirm transaction for caller and log a Confirmation event', async () => { + const txReceipt = await multiSigWrapper.confirmTransactionAsync(txId, owners[1]); + const log = txReceipt.logs[0] as LogWithDecodedArgs<MultiSigWalletWithTimeLockConfirmationEventArgs>; + expect(log.event).to.be.equal('Confirmation'); + expect(log.args.sender).to.be.equal(owners[1]); + expect(log.args.transactionId).to.be.bignumber.equal(txId); + }); + it('should revert if fully confirmed', async () => { + await multiSigWrapper.confirmTransactionAsync(txId, owners[1]); + await expectTransactionFailedAsync( + multiSigWrapper.confirmTransactionAsync(txId, owners[2]), + RevertReason.TxFullyConfirmed, + ); + }); + it('should set the confirmation time of the transaction if it becomes fully confirmed', async () => { + const blockNum = await web3Wrapper.getBlockNumberAsync(); + const timestamp = new BigNumber(await web3Wrapper.getBlockTimestampAsync(blockNum)); + const txReceipt = await multiSigWrapper.confirmTransactionAsync(txId, owners[1]); + const log = txReceipt.logs[1] as LogWithDecodedArgs<MultiSigWalletWithTimeLockConfirmationTimeSetEventArgs>; + expect(log.args.confirmationTime).to.be.bignumber.equal(timestamp); + expect(log.args.transactionId).to.be.bignumber.equal(txId); + }); + }); + describe('executeTransaction', () => { + let txId: BigNumber; + const secondsTimeLocked = new BigNumber(1000000); + beforeEach(async () => { + multiSig = await MultiSigWalletWithTimeLockContract.deployFrom0xArtifactAsync( + artifacts.MultiSigWalletWithTimeLock, + provider, + txDefaults, + owners, + REQUIRED_APPROVALS, + secondsTimeLocked, + ); + multiSigWrapper = new MultiSigWrapper(multiSig, provider); + const destination = notOwner; + const data = constants.NULL_BYTES; + const txReceipt = await multiSigWrapper.submitTransactionAsync(destination, data, owners[0]); + txId = (txReceipt.logs[0] as LogWithDecodedArgs<MultiSigWalletWithTimeLockSubmissionEventArgs>).args + .transactionId; + }); + it('should revert if transaction has not been fully confirmed', async () => { + await increaseTimeAndMineBlockAsync(secondsTimeLocked.toNumber()); + await expectTransactionFailedAsync( + multiSigWrapper.executeTransactionAsync(txId, owners[1]), + RevertReason.TxNotFullyConfirmed, + ); + }); + it('should revert if time lock has not passed', async () => { + await multiSigWrapper.confirmTransactionAsync(txId, owners[1]); + await expectTransactionFailedAsync( + multiSigWrapper.executeTransactionAsync(txId, owners[1]), + RevertReason.TimeLockIncomplete, + ); + }); + it('should execute a transaction and log an Execution event if successful and called by owner', async () => { + await multiSigWrapper.confirmTransactionAsync(txId, owners[1]); + await increaseTimeAndMineBlockAsync(secondsTimeLocked.toNumber()); + const txReceipt = await multiSigWrapper.executeTransactionAsync(txId, owners[1]); + const log = txReceipt.logs[0] as LogWithDecodedArgs<MultiSigWalletWithTimeLockExecutionEventArgs>; + expect(log.event).to.be.equal('Execution'); + expect(log.args.transactionId).to.be.bignumber.equal(txId); + }); + it('should execute a transaction and log an Execution event if successful and called by non-owner', async () => { + await multiSigWrapper.confirmTransactionAsync(txId, owners[1]); + await increaseTimeAndMineBlockAsync(secondsTimeLocked.toNumber()); + const txReceipt = await multiSigWrapper.executeTransactionAsync(txId, notOwner); + const log = txReceipt.logs[0] as LogWithDecodedArgs<MultiSigWalletWithTimeLockExecutionEventArgs>; + expect(log.event).to.be.equal('Execution'); + expect(log.args.transactionId).to.be.bignumber.equal(txId); + }); + it('should revert if a required confirmation is revoked before executeTransaction is called', async () => { + await multiSigWrapper.confirmTransactionAsync(txId, owners[1]); + await increaseTimeAndMineBlockAsync(secondsTimeLocked.toNumber()); + await multiSigWrapper.revokeConfirmationAsync(txId, owners[0]); + await expectTransactionFailedAsync( + multiSigWrapper.executeTransactionAsync(txId, owners[1]), + RevertReason.TxNotFullyConfirmed, + ); + }); + it('should revert if transaction has been executed', async () => { + await multiSigWrapper.confirmTransactionAsync(txId, owners[1]); + await increaseTimeAndMineBlockAsync(secondsTimeLocked.toNumber()); + const txReceipt = await multiSigWrapper.executeTransactionAsync(txId, owners[1]); + const log = txReceipt.logs[0] as LogWithDecodedArgs<MultiSigWalletWithTimeLockExecutionEventArgs>; + expect(log.args.transactionId).to.be.bignumber.equal(txId); + await expectTransactionFailedWithoutReasonAsync(multiSigWrapper.executeTransactionAsync(txId, owners[1])); + }); + it("should log an ExecutionFailure event and not update the transaction's execution state if unsuccessful", async () => { + const contractWithoutFallback = await DummyERC20TokenContract.deployFrom0xArtifactAsync( + artifacts.DummyERC20Token, + provider, + txDefaults, + constants.DUMMY_TOKEN_NAME, + constants.DUMMY_TOKEN_SYMBOL, + constants.DUMMY_TOKEN_DECIMALS, + constants.DUMMY_TOKEN_TOTAL_SUPPLY, + ); + const data = constants.NULL_BYTES; + const value = new BigNumber(10); + const submissionTxReceipt = await multiSigWrapper.submitTransactionAsync( + contractWithoutFallback.address, + data, + owners[0], + { value }, + ); + const newTxId = (submissionTxReceipt.logs[0] as LogWithDecodedArgs< + MultiSigWalletWithTimeLockSubmissionEventArgs + >).args.transactionId; + await multiSigWrapper.confirmTransactionAsync(newTxId, owners[1]); + await increaseTimeAndMineBlockAsync(secondsTimeLocked.toNumber()); + const txReceipt = await multiSigWrapper.executeTransactionAsync(newTxId, owners[1]); + const executionFailureLog = txReceipt.logs[0] as LogWithDecodedArgs< + MultiSigWalletWithTimeLockExecutionFailureEventArgs + >; + expect(executionFailureLog.event).to.be.equal('ExecutionFailure'); + expect(executionFailureLog.args.transactionId).to.be.bignumber.equal(newTxId); + }); + }); describe('changeTimeLock', () => { describe('initially non-time-locked', async () => { before(async () => { @@ -78,8 +252,9 @@ describe('MultiSigWalletWithTimeLock', () => { const res = await multiSigWrapper.submitTransactionAsync(destination, changeTimeLockData, owners[0]); const log = res.logs[0] as LogWithDecodedArgs<MultiSigWalletWithTimeLockSubmissionEventArgs>; const txId = log.args.transactionId; - return expectTransactionFailedWithoutReasonAsync( + return expectTransactionFailedAsync( multiSig.executeTransaction.sendTransactionAsync(txId, { from: owners[0] }), + RevertReason.TxNotFullyConfirmed, ); }); @@ -147,8 +322,9 @@ describe('MultiSigWalletWithTimeLock', () => { }); it('should throw if it has enough confirmations but is not past the time lock', async () => { - return expectTransactionFailedWithoutReasonAsync( + return expectTransactionFailedAsync( multiSig.executeTransaction.sendTransactionAsync(txId, { from: owners[0] }), + RevertReason.TimeLockIncomplete, ); }); diff --git a/packages/contracts/test/utils/multi_sig_wrapper.ts b/packages/contracts/test/utils/multi_sig_wrapper.ts index e0c27b839..e12a58695 100644 --- a/packages/contracts/test/utils/multi_sig_wrapper.ts +++ b/packages/contracts/test/utils/multi_sig_wrapper.ts @@ -6,7 +6,6 @@ import * as _ from 'lodash'; import { AssetProxyOwnerContract } from '../../generated_contract_wrappers/asset_proxy_owner'; import { MultiSigWalletContract } from '../../generated_contract_wrappers/multi_sig_wallet'; -import { constants } from './constants'; import { LogDecoder } from './log_decoder'; export class MultiSigWrapper { @@ -36,10 +35,19 @@ export class MultiSigWrapper { const tx = await this._logDecoder.getTxWithDecodedLogsAsync(txHash); return tx; } - public async executeTransactionAsync(txId: BigNumber, from: string): Promise<TransactionReceiptWithDecodedLogs> { + public async revokeConfirmationAsync(txId: BigNumber, from: string): Promise<TransactionReceiptWithDecodedLogs> { + const txHash = await this._multiSig.revokeConfirmation.sendTransactionAsync(txId, { from }); + const tx = await this._logDecoder.getTxWithDecodedLogsAsync(txHash); + return tx; + } + public async executeTransactionAsync( + txId: BigNumber, + from: string, + opts: { gas?: number } = {}, + ): Promise<TransactionReceiptWithDecodedLogs> { const txHash = await this._multiSig.executeTransaction.sendTransactionAsync(txId, { from, - gas: constants.MAX_EXECUTE_TRANSACTION_GAS, + gas: opts.gas, }); const tx = await this._logDecoder.getTxWithDecodedLogsAsync(txHash); return tx; @@ -52,7 +60,6 @@ export class MultiSigWrapper { const txHash = await (this ._multiSig as AssetProxyOwnerContract).executeRemoveAuthorizedAddressAtIndex.sendTransactionAsync(txId, { from, - gas: constants.MAX_EXECUTE_TRANSACTION_GAS, }); const tx = await this._logDecoder.getTxWithDecodedLogsAsync(txHash); return tx; diff --git a/packages/monorepo-scripts/src/doc_gen_configs.ts b/packages/monorepo-scripts/src/doc_gen_configs.ts index 102b5d7ca..e3ddeddc9 100644 --- a/packages/monorepo-scripts/src/doc_gen_configs.ts +++ b/packages/monorepo-scripts/src/doc_gen_configs.ts @@ -16,10 +16,8 @@ export const docGenConfigs: DocGenConfigs = { Schema: 'https://github.com/tdegrunt/jsonschema/blob/5c2edd4baba149964aec0f23c87ad12c25a50dfb/lib/index.d.ts#L49', Uint8Array: 'https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Uint8Array', - 'Ganache.GanacheOpts': - 'https://github.com/0xProject/0x-monorepo/blob/ddf85112d7e4eb1581e0d82ce6eedad429641106/packages/typescript-typings/types/ganache-core/index.d.ts#L3', - 'lightwallet.keystore': - 'https://github.com/0xProject/0x-monorepo/blob/ddf85112d7e4eb1581e0d82ce6eedad429641106/packages/typescript-typings/types/eth-lightwallet/index.d.ts#L32', + GanacheOpts: 'https://github.com/DefinitelyTyped/DefinitelyTyped/blob/master/types/ganache-core/index.d.ts#L8', + keystore: 'https://github.com/DefinitelyTyped/DefinitelyTyped/blob/master/types/eth-lightwallet/index.d.ts#L36', }, // If a 0x package re-exports an external package, we should add a link to it's exported items here EXTERNAL_EXPORT_TO_LINK: { diff --git a/packages/sol-cov/package.json b/packages/sol-cov/package.json index 3772d02c5..9cc809955 100644 --- a/packages/sol-cov/package.json +++ b/packages/sol-cov/package.json @@ -47,7 +47,6 @@ "@0xproject/typescript-typings": "^1.0.5", "@0xproject/utils": "^1.0.7", "@0xproject/web3-wrapper": "^2.0.1", - "@types/solidity-parser-antlr": "^0.2.1", "ethereum-types": "^1.0.5", "ethereumjs-util": "^5.1.1", "glob": "^7.1.2", @@ -67,7 +66,7 @@ "@types/mocha": "^2.2.42", "@types/node": "^8.0.53", "@types/rimraf": "^2.0.2", - "@types/solidity-parser-antlr": "^0.2.1", + "@types/solidity-parser-antlr": "^0.2.0", "chai": "^4.0.1", "copyfiles": "^2.0.0", "dirty-chai": "^2.0.1", diff --git a/packages/sra-report/package.json b/packages/sra-report/package.json index 481794d5d..31453969c 100644 --- a/packages/sra-report/package.json +++ b/packages/sra-report/package.json @@ -51,6 +51,7 @@ "@types/mocha": "^2.2.48", "@types/nock": "^9.1.2", "@types/node": "^8.0.53", + "@types/newman": "^3.9.0", "@types/yargs": "^10.0.0", "chai": "^4.0.1", "chai-as-promised": "^7.1.0", diff --git a/packages/subproviders/package.json b/packages/subproviders/package.json index d9583b357..100e99449 100644 --- a/packages/subproviders/package.json +++ b/packages/subproviders/package.json @@ -36,6 +36,8 @@ "@ledgerhq/hw-app-eth": "^4.3.0", "@ledgerhq/hw-transport-u2f": "^4.3.0", "@types/hdkey": "^0.7.0", + "@types/ganache-core": "^2.1.0", + "@types/eth-lightwallet": "^3.0.0", "bip39": "^2.5.0", "bn.js": "^4.11.8", "eth-lightwallet": "^3.0.1", @@ -55,6 +57,7 @@ "@types/bn.js": "^4.11.0", "@types/ethereumjs-tx": "^1.0.0", "@types/hdkey": "^0.7.0", + "@types/web3-provider-engine": "^14.0.0", "@types/lodash": "4.14.104", "@types/mocha": "^2.2.42", "@types/node": "^8.0.53", diff --git a/packages/subproviders/src/globals.d.ts b/packages/subproviders/src/globals.d.ts index 94e63a32d..3cbf84e37 100644 --- a/packages/subproviders/src/globals.d.ts +++ b/packages/subproviders/src/globals.d.ts @@ -4,3 +4,21 @@ declare module '*.json' { export default json; /* tslint:enable */ } +declare module 'web3-provider-engine/util/rpc-cache-utils' { + class ProviderEngineRpcUtils { + public static blockTagForPayload(payload: any): string | null; + } + export = ProviderEngineRpcUtils; +} +declare module 'web3-provider-engine/subproviders/fixture' { + import { JSONRPCRequestPayload, JSONRPCResponsePayload } from 'ethereum-types'; + class FixtureSubprovider { + constructor(staticResponses: any); + public handleRequest( + payload: JSONRPCRequestPayload, + next: () => void, + end: (err: Error | null, data?: JSONRPCResponsePayload) => void, + ): void; + } + export = FixtureSubprovider; +} diff --git a/packages/types/CHANGELOG.json b/packages/types/CHANGELOG.json index 0bd9e3b37..efbc78cbc 100644 --- a/packages/types/CHANGELOG.json +++ b/packages/types/CHANGELOG.json @@ -5,6 +5,10 @@ { "note": "Add AssetProxyOwner revert reasons", "pr": 1041 + }, + { + "note": "Add MultiSigWalletWithTimeLock revert reasons", + "pr": 1050 } ] }, diff --git a/packages/types/src/index.ts b/packages/types/src/index.ts index 3011dd87f..48a9e23d1 100644 --- a/packages/types/src/index.ts +++ b/packages/types/src/index.ts @@ -227,6 +227,9 @@ export enum RevertReason { InvalidFunctionSelector = 'INVALID_FUNCTION_SELECTOR', InvalidAssetProxy = 'INVALID_ASSET_PROXY', UnregisteredAssetProxy = 'UNREGISTERED_ASSET_PROXY', + TxFullyConfirmed = 'TX_FULLY_CONFIRMED', + TxNotFullyConfirmed = 'TX_NOT_FULLY_CONFIRMED', + TimeLockIncomplete = 'TIME_LOCK_INCOMPLETE', } export enum StatusCodes { diff --git a/packages/typescript-typings/CHANGELOG.json b/packages/typescript-typings/CHANGELOG.json index 8e6b2b3c8..c3f7b1ac2 100644 --- a/packages/typescript-typings/CHANGELOG.json +++ b/packages/typescript-typings/CHANGELOG.json @@ -1,5 +1,14 @@ [ { + "version": "2.0.0", + "changes": [ + { + "note": "Remove types for web3-provider-engine, newman, ganache-core, detect-node, eth-lightwallet", + "pr": "1052" + } + ] + }, + { "timestamp": 1535133899, "version": "1.0.5", "changes": [ diff --git a/packages/typescript-typings/types/detect-node/index.d.ts b/packages/typescript-typings/types/detect-node/index.d.ts deleted file mode 100644 index 4c58b8450..000000000 --- a/packages/typescript-typings/types/detect-node/index.d.ts +++ /dev/null @@ -1,3 +0,0 @@ -declare module 'detect-node' { - export const isNode: boolean; -} diff --git a/packages/typescript-typings/types/eth-lightwallet/index.d.ts b/packages/typescript-typings/types/eth-lightwallet/index.d.ts deleted file mode 100644 index 93daa5041..000000000 --- a/packages/typescript-typings/types/eth-lightwallet/index.d.ts +++ /dev/null @@ -1,54 +0,0 @@ -// eth-lightwallet declarations - -interface ECSignatureBuffer { - v: number; - r: Buffer; - s: Buffer; -} -declare module 'eth-lightwallet' { - // tslint:disable-next-line:class-name - export class signing { - public static signTx( - keystore: keystore, - pwDerivedKey: Uint8Array, - rawTx: string, - signingAddress: string, - ): string; - public static signMsg( - keystore: keystore, - pwDerivedKey: Uint8Array, - rawMsg: string, - signingAddress: string, - ): ECSignatureBuffer; - public static signMsgHash( - keystore: keystore, - pwDerivedKey: Uint8Array, - msgHash: string, - signingAddress: string, - ): ECSignatureBuffer; - public static concatSig(signature: any): string; - } - // tslint:disable-next-line:class-name - export class keystore { - public static createVault(options: any, callback?: (error: Error, keystore: keystore) => void): keystore; - public static generateRandomSeed(): string; - public static isSeedValid(seed: string): boolean; - public static deserialize(keystore: string): keystore; - public serialize(): string; - public keyFromPassword( - password: string, - callback?: (error: Error, pwDerivedKey: Uint8Array) => void, - ): Uint8Array; - public isDerivedKeyCorrect(pwDerivedKey: Uint8Array): boolean; - public generateNewAddress(pwDerivedKey: Uint8Array, numberOfAddresses: number): void; - public getSeed(pwDerivedKey: Uint8Array): string; - public exportPrivateKey(address: string, pwDerivedKey: Uint8Array): string; - public getAddresses(): string[]; - } - interface VaultOptions { - password: string; - seedPhrase: string; - salt?: string; - hdPathString: string; - } -} diff --git a/packages/typescript-typings/types/ganache-core/index.d.ts b/packages/typescript-typings/types/ganache-core/index.d.ts deleted file mode 100644 index c07e6a78e..000000000 --- a/packages/typescript-typings/types/ganache-core/index.d.ts +++ /dev/null @@ -1,16 +0,0 @@ -declare module 'ganache-core' { - import { Provider } from 'ethereum-types'; - export interface GanacheOpts { - verbose?: boolean; - logger?: { - log(msg: string): void; - }; - port?: number; - network_id?: number; - networkId?: number; - mnemonic?: string; - gasLimit?: number; - } - // tslint:disable-next-line:completed-docs - export function provider(opts: GanacheOpts): Provider; -} diff --git a/packages/typescript-typings/types/newman/index.d.ts b/packages/typescript-typings/types/newman/index.d.ts deleted file mode 100644 index bea9ac160..000000000 --- a/packages/typescript-typings/types/newman/index.d.ts +++ /dev/null @@ -1,24 +0,0 @@ -declare module 'newman' { - export interface NewmanRunSummary { - run: NewmanRun; - } - export interface NewmanRun { - executions: NewmanRunExecution[]; - } - export interface NewmanRunExecution { - item: NewmanRunExecutionItem; - assertions: NewmanRunExecutionAssertion[]; - } - export interface NewmanRunExecutionItem { - name: string; - } - export interface NewmanRunExecutionAssertion { - assertion: string; - error: NewmanRunExecutionAssertionError; - } - export interface NewmanRunExecutionAssertionError { - message: string; - } - // tslint:disable-next-line:completed-docs - export function run(options: any, callback?: (err: Error | null, summary: NewmanRunSummary) => void): void; -} diff --git a/packages/typescript-typings/types/web3-provider-engine/index.d.ts b/packages/typescript-typings/types/web3-provider-engine/index.d.ts deleted file mode 100644 index 72ef434a7..000000000 --- a/packages/typescript-typings/types/web3-provider-engine/index.d.ts +++ /dev/null @@ -1,62 +0,0 @@ -declare module 'web3-provider-engine' { - import { Provider, JSONRPCRequestPayload, JSONRPCResponsePayload } from 'ethereum-types'; - interface Web3ProviderEngineOptions { - pollingInterval?: number; - blockTracker?: any; - blockTrackerProvider?: any; - } - class Web3ProviderEngine implements Provider { - constructor(options?: Web3ProviderEngineOptions); - public on(event: string, handler: () => void): void; - public send(payload: JSONRPCRequestPayload): void; - public sendAsync( - payload: JSONRPCRequestPayload, - callback: (error: null | Error, response: JSONRPCResponsePayload) => void, - ): void; - public addProvider(provider: any): void; - // start block polling - public start(callback?: () => void): void; - // stop block polling - public stop(): void; - } - export = Web3ProviderEngine; -} - -declare module 'web3-provider-engine/subproviders/nonce-tracker'; -declare module 'web3-provider-engine/subproviders/hooked-wallet'; -declare module 'web3-provider-engine/subproviders/filters'; -// web3-provider-engine declarations -declare module 'web3-provider-engine/subproviders/subprovider' { - class Subprovider {} - export = Subprovider; -} -declare module 'web3-provider-engine/subproviders/rpc' { - import { JSONRPCRequestPayload, JSONRPCResponsePayload } from 'ethereum-types'; - class RpcSubprovider { - constructor(options: { rpcUrl: string }); - public handleRequest( - payload: JSONRPCRequestPayload, - next: () => void, - end: (err: Error | null, data?: JSONRPCResponsePayload) => void, - ): void; - } - export = RpcSubprovider; -} -declare module 'web3-provider-engine/util/rpc-cache-utils' { - class ProviderEngineRpcUtils { - public static blockTagForPayload(payload: any): string | null; - } - export = ProviderEngineRpcUtils; -} -declare module 'web3-provider-engine/subproviders/fixture' { - import { JSONRPCRequestPayload, JSONRPCResponsePayload } from 'ethereum-types'; - class FixtureSubprovider { - constructor(staticResponses: any); - public handleRequest( - payload: JSONRPCRequestPayload, - next: () => void, - end: (err: Error | null, data?: JSONRPCResponsePayload) => void, - ): void; - } - export = FixtureSubprovider; -} diff --git a/packages/utils/package.json b/packages/utils/package.json index d93163e81..15a8516a7 100644 --- a/packages/utils/package.json +++ b/packages/utils/package.json @@ -28,6 +28,7 @@ "homepage": "https://github.com/0xProject/0x-monorepo/packages/utils/README.md", "devDependencies": { "@0xproject/tslint-config": "^1.0.6", + "@types/detect-node": "2.0.0", "@types/lodash": "4.14.104", "@types/mocha": "^2.2.42", "chai": "^4.0.1", diff --git a/packages/web3-wrapper/package.json b/packages/web3-wrapper/package.json index 6361fbde7..e2770bcc5 100644 --- a/packages/web3-wrapper/package.json +++ b/packages/web3-wrapper/package.json @@ -35,6 +35,7 @@ "homepage": "https://github.com/0xProject/0x-monorepo/packages/web3-wrapper/README.md", "devDependencies": { "@0xproject/tslint-config": "^1.0.6", + "@types/ganache-core": "^2.1.0", "@types/lodash": "4.14.104", "chai": "^4.0.1", "chai-as-promised": "^7.1.0", diff --git a/packages/website/package.json b/packages/website/package.json index a7da19c9e..878544d27 100644 --- a/packages/website/package.json +++ b/packages/website/package.json @@ -66,6 +66,7 @@ "@types/accounting": "^0.4.1", "@types/blockies": "^0.0.0", "@types/deep-equal": "^1.0.0", + "@types/web3-provider-engine": "^14.0.0", "@types/find-versions": "^2.0.0", "@types/jsonschema": "^1.1.1", "@types/lodash": "4.14.104", diff --git a/packages/website/ts/globals.d.ts b/packages/website/ts/globals.d.ts index 719c2708a..eb8892aea 100644 --- a/packages/website/ts/globals.d.ts +++ b/packages/website/ts/globals.d.ts @@ -10,6 +10,7 @@ declare module '*.json' { export default json; /* tslint:enable */ } +declare module 'web3-provider-engine/subproviders/filters'; // This will be defined by default in TS 2.4 // Source: https://github.com/Microsoft/TypeScript/issues/12364 |