From f47feabb4abcdd95091cc05ec185670d6a984176 Mon Sep 17 00:00:00 2001 From: Amir Bandeali Date: Fri, 13 Sep 2019 12:45:57 -0400 Subject: [PATCH] Update AssetProxyOwner tests --- contracts/multisig/package.json | 4 +- contracts/multisig/src/artifacts.ts | 2 + contracts/multisig/src/wrappers.ts | 1 + contracts/multisig/test/asset_proxy_owner.ts | 1011 +++++++++-------- .../test/utils/asset_proxy_owner_wrapper.ts | 98 +- contracts/multisig/test/utils/index.ts | 2 +- contracts/multisig/tsconfig.json | 1 + contracts/test-utils/src/constants.ts | 2 +- yarn.lock | 90 -- 9 files changed, 595 insertions(+), 616 deletions(-) diff --git a/contracts/multisig/package.json b/contracts/multisig/package.json index 804f33b03b..8d7323c39f 100644 --- a/contracts/multisig/package.json +++ b/contracts/multisig/package.json @@ -34,7 +34,7 @@ "lint-contracts": "solhint -c ../.solhint.json contracts/**/**/**/**/*.sol" }, "config": { - "abis": "./generated-artifacts/@(AssetProxyOwner|MultiSigWallet|MultiSigWalletWithTimeLock|TestAssetProxyOwner|TestRejectEther).json", + "abis": "./generated-artifacts/@(AssetProxyOwner|ContractCallReceiver|MultiSigWallet|MultiSigWalletWithTimeLock|TestAssetProxyOwner|TestRejectEther).json", "abis:comment": "This list is auto-generated by contracts-gen. Don't edit manually." }, "repository": { @@ -72,7 +72,7 @@ "@0x/base-contract": "^5.3.1", "@0x/contracts-asset-proxy": "^2.2.5", "@0x/contracts-erc20": "^2.2.11", - "@0x/contracts-utils": "2.0.1", + "@0x/contracts-utils": "^3.2.1", "@0x/types": "^2.4.1", "@0x/typescript-typings": "^4.2.4", "@0x/utils": "^4.5.0", diff --git a/contracts/multisig/src/artifacts.ts b/contracts/multisig/src/artifacts.ts index 19cee447b0..f2a986632b 100644 --- a/contracts/multisig/src/artifacts.ts +++ b/contracts/multisig/src/artifacts.ts @@ -6,6 +6,7 @@ import { ContractArtifact } from 'ethereum-types'; import * as AssetProxyOwner from '../generated-artifacts/AssetProxyOwner.json'; +import * as ContractCallReceiver from '../generated-artifacts/ContractCallReceiver.json'; import * as MultiSigWallet from '../generated-artifacts/MultiSigWallet.json'; import * as MultiSigWalletWithTimeLock from '../generated-artifacts/MultiSigWalletWithTimeLock.json'; import * as TestAssetProxyOwner from '../generated-artifacts/TestAssetProxyOwner.json'; @@ -14,6 +15,7 @@ export const artifacts = { AssetProxyOwner: AssetProxyOwner as ContractArtifact, MultiSigWallet: MultiSigWallet as ContractArtifact, MultiSigWalletWithTimeLock: MultiSigWalletWithTimeLock as ContractArtifact, + ContractCallReceiver: ContractCallReceiver as ContractArtifact, TestAssetProxyOwner: TestAssetProxyOwner as ContractArtifact, TestRejectEther: TestRejectEther as ContractArtifact, }; diff --git a/contracts/multisig/src/wrappers.ts b/contracts/multisig/src/wrappers.ts index 68874699e3..0e54c14150 100644 --- a/contracts/multisig/src/wrappers.ts +++ b/contracts/multisig/src/wrappers.ts @@ -4,6 +4,7 @@ * ----------------------------------------------------------------------------- */ export * from '../generated-wrappers/asset_proxy_owner'; +export * from '../generated-wrappers/contract_call_receiver'; export * from '../generated-wrappers/multi_sig_wallet'; export * from '../generated-wrappers/multi_sig_wallet_with_time_lock'; export * from '../generated-wrappers/test_asset_proxy_owner'; diff --git a/contracts/multisig/test/asset_proxy_owner.ts b/contracts/multisig/test/asset_proxy_owner.ts index 94b0849bfd..9e25fa2517 100644 --- a/contracts/multisig/test/asset_proxy_owner.ts +++ b/contracts/multisig/test/asset_proxy_owner.ts @@ -1,511 +1,600 @@ -import { artifacts as proxyArtifacts, MixinAuthorizableContract } from '@0x/contracts-asset-proxy'; -import { - chaiSetup, - constants, - expectContractCallFailedAsync, - expectContractCreationFailedAsync, - expectTransactionFailedAsync, - expectTransactionFailedWithoutReasonAsync, - increaseTimeAndMineBlockAsync, - provider, - sendTransactionResult, - txDefaults, - web3Wrapper, -} from '@0x/contracts-test-utils'; -import { BlockchainLifecycle } from '@0x/dev-utils'; +import { blockchainTests, constants, expect, getLatestBlockTimestampAsync, hexRandom } from '@0x/contracts-test-utils'; import { RevertReason } from '@0x/types'; -import { BigNumber } from '@0x/utils'; -import * as chai from 'chai'; -import { LogWithDecodedArgs } from 'ethereum-types'; +import { BigNumber, LibBytesRevertErrors } from '@0x/utils'; +import { LogEntry, LogWithDecodedArgs } from 'ethereum-types'; +import * as _ from 'lodash'; import { artifacts, - AssetProxyOwnerAssetProxyRegistrationEventArgs, - AssetProxyOwnerContract, AssetProxyOwnerExecutionEventArgs, - AssetProxyOwnerExecutionFailureEventArgs, - AssetProxyOwnerSubmissionEventArgs, + AssetProxyOwnerFunctionCallTimeLockRegistrationEventArgs, AssetProxyOwnerWrapper, + ContractCallReceiverContract, + ContractCallReceiverEventArgs, TestAssetProxyOwnerContract, } from '../src'; -chaiSetup.configure(); -const expect = chai.expect; -const blockchainLifecycle = new BlockchainLifecycle(web3Wrapper); -// tslint:disable:no-unnecessary-type-assertion -describe('AssetProxyOwner', () => { - let owners: string[]; - let authorized: string; - let notOwner: string; - const REQUIRED_APPROVALS = new BigNumber(2); - const SECONDS_TIME_LOCKED = new BigNumber(1000000); - - let erc20Proxy: MixinAuthorizableContract; - let erc721Proxy: MixinAuthorizableContract; - let testAssetProxyOwner: TestAssetProxyOwnerContract; +blockchainTests.resets('AssetProxyOwner', env => { + let assetProxyOwner: TestAssetProxyOwnerContract; let assetProxyOwnerWrapper: AssetProxyOwnerWrapper; + let receiver: ContractCallReceiverContract; + let signerAddresses: string[]; + let notSignerAddress: string; + + const TOTAL_SIGNERS = 3; + const REQUIRED_SIGNERS = 2; + const DEFAULT_TIME_LOCK = 1000; + const INITIAL_BALANCE = new BigNumber(1000); before(async () => { - await blockchainLifecycle.startAsync(); - }); - after(async () => { - await blockchainLifecycle.revertAsync(); - }); - before(async () => { - const accounts = await web3Wrapper.getAvailableAddressesAsync(); - owners = [accounts[0], accounts[1]]; - authorized = accounts[2]; - notOwner = accounts[3]; - const initialOwner = accounts[0]; - erc20Proxy = await MixinAuthorizableContract.deployFrom0xArtifactAsync( - proxyArtifacts.MixinAuthorizable, - provider, - txDefaults, - artifacts, - ); - erc721Proxy = await MixinAuthorizableContract.deployFrom0xArtifactAsync( - proxyArtifacts.MixinAuthorizable, - provider, - txDefaults, - artifacts, - ); - const defaultAssetProxyContractAddresses: string[] = []; - testAssetProxyOwner = await TestAssetProxyOwnerContract.deployFrom0xArtifactAsync( + const accounts = await env.web3Wrapper.getAvailableAddressesAsync(); + signerAddresses = accounts.slice(0, TOTAL_SIGNERS); + notSignerAddress = accounts[TOTAL_SIGNERS]; + assetProxyOwner = await TestAssetProxyOwnerContract.deployFrom0xArtifactAsync( artifacts.TestAssetProxyOwner, - provider, - txDefaults, + env.provider, + env.txDefaults, artifacts, - owners, - defaultAssetProxyContractAddresses, - REQUIRED_APPROVALS, - SECONDS_TIME_LOCKED, + [], + [], + [], + signerAddresses, + new BigNumber(REQUIRED_SIGNERS), + new BigNumber(DEFAULT_TIME_LOCK), ); - assetProxyOwnerWrapper = new AssetProxyOwnerWrapper(testAssetProxyOwner, provider); - await web3Wrapper.awaitTransactionSuccessAsync( - await erc20Proxy.transferOwnership.sendTransactionAsync(testAssetProxyOwner.address, { - from: initialOwner, + await env.web3Wrapper.awaitTransactionMinedAsync( + await env.web3Wrapper.sendTransactionAsync({ + from: signerAddresses[0], + to: assetProxyOwner.address, + value: INITIAL_BALANCE, }), - constants.AWAIT_TRANSACTION_MINED_MS, ); - await web3Wrapper.awaitTransactionSuccessAsync( - await erc721Proxy.transferOwnership.sendTransactionAsync(testAssetProxyOwner.address, { - from: initialOwner, - }), - constants.AWAIT_TRANSACTION_MINED_MS, + assetProxyOwnerWrapper = new AssetProxyOwnerWrapper(assetProxyOwner); + receiver = await ContractCallReceiverContract.deployFrom0xArtifactAsync( + artifacts.ContractCallReceiver, + env.provider, + env.txDefaults, + {}, ); }); - beforeEach(async () => { - await blockchainLifecycle.startAsync(); - }); - afterEach(async () => { - await blockchainLifecycle.revertAsync(); - }); - describe('constructor', () => { - it('should register passed in assetProxyContracts', async () => { - const assetProxyContractAddresses = [erc20Proxy.address, erc721Proxy.address]; - const newMultiSig = await AssetProxyOwnerContract.deployFrom0xArtifactAsync( - artifacts.AssetProxyOwner, - provider, - txDefaults, + function createFunctionRegistration( + functionSelectorLength: number = 0, + destinationsLength?: number, + functionCallTimeLockSecondsLength?: number, + ): { functionSelectors: string[]; destinations: string[]; functionCallTimeLockSeconds: BigNumber[] } { + const _destinationsLength = destinationsLength === undefined ? functionSelectorLength : destinationsLength; + const _functionCallTimeLockSecondsLength = + functionCallTimeLockSecondsLength === undefined + ? functionSelectorLength + : functionCallTimeLockSecondsLength; + const functionSelectors = _.times(functionSelectorLength, () => hexRandom(4)); + const destinations = _.times(_destinationsLength, () => hexRandom(20)); + const functionCallTimeLockSeconds = _.times(_functionCallTimeLockSecondsLength, () => + BigNumber.random() // random int > 0 and < 1000 + .times(10000000) + .integerValue() + .mod(1000), + ); + return { functionSelectors, destinations, functionCallTimeLockSeconds }; + } + blockchainTests.resets('constructor', () => { + it('should fail if destinations.length != functionSelectors.length', async () => { + const reg = createFunctionRegistration(1, 2, 1); + const tx = TestAssetProxyOwnerContract.deployFrom0xArtifactAsync( + artifacts.TestAssetProxyOwner, + env.provider, + env.txDefaults, artifacts, - owners, - assetProxyContractAddresses, - REQUIRED_APPROVALS, - SECONDS_TIME_LOCKED, + reg.functionSelectors, + reg.destinations, + reg.functionCallTimeLockSeconds, + signerAddresses, + new BigNumber(REQUIRED_SIGNERS), + new BigNumber(DEFAULT_TIME_LOCK), ); - const isErc20ProxyRegistered = await newMultiSig.isAssetProxyRegistered.callAsync(erc20Proxy.address); - const isErc721ProxyRegistered = await newMultiSig.isAssetProxyRegistered.callAsync(erc721Proxy.address); - expect(isErc20ProxyRegistered).to.equal(true); - expect(isErc721ProxyRegistered).to.equal(true); + await expect(tx).to.revertWith(RevertReason.EqualLengthsRequired); }); - it('should revert if a null address is included in assetProxyContracts', async () => { - const assetProxyContractAddresses = [erc20Proxy.address, constants.NULL_ADDRESS]; - return expectContractCreationFailedAsync( - (AssetProxyOwnerContract.deployFrom0xArtifactAsync( - artifacts.AssetProxyOwner, - provider, - txDefaults, - artifacts, - owners, - assetProxyContractAddresses, - REQUIRED_APPROVALS, - SECONDS_TIME_LOCKED, - ) as any) as sendTransactionResult, - RevertReason.InvalidAssetProxy, + it('should fail if functionCallTimeLockSeconds.length != functionSelectors.length', async () => { + const reg = createFunctionRegistration(1, 1, 2); + const tx = TestAssetProxyOwnerContract.deployFrom0xArtifactAsync( + artifacts.TestAssetProxyOwner, + env.provider, + env.txDefaults, + artifacts, + reg.functionSelectors, + reg.destinations, + reg.functionCallTimeLockSeconds, + signerAddresses, + new BigNumber(REQUIRED_SIGNERS), + new BigNumber(DEFAULT_TIME_LOCK), ); + await expect(tx).to.revertWith(RevertReason.EqualLengthsRequired); + }); + it('should allow no function calls to be registered', async () => { + const tx = TestAssetProxyOwnerContract.deployFrom0xArtifactAsync( + artifacts.TestAssetProxyOwner, + env.provider, + env.txDefaults, + artifacts, + [], + [], + [], + signerAddresses, + new BigNumber(REQUIRED_SIGNERS), + new BigNumber(DEFAULT_TIME_LOCK), + ); + expect(tx).to.be.fulfilled(''); + }); + it('should register a single functon call', async () => { + const reg = createFunctionRegistration(1, 1, 1); + const apOwner = await TestAssetProxyOwnerContract.deployFrom0xArtifactAsync( + artifacts.TestAssetProxyOwner, + env.provider, + env.txDefaults, + artifacts, + reg.functionSelectors, + reg.destinations, + reg.functionCallTimeLockSeconds, + signerAddresses, + new BigNumber(REQUIRED_SIGNERS), + new BigNumber(DEFAULT_TIME_LOCK), + ); + const timelock = await apOwner.functionCallTimeLocks.callAsync( + reg.functionSelectors[0], + reg.destinations[0], + ); + expect(timelock[0]).to.equal(true); + expect(timelock[1]).to.bignumber.equal(reg.functionCallTimeLockSeconds[0]); + }); + it('should register multiple function calls', async () => { + const reg = createFunctionRegistration(2, 2, 2); + const apOwner = await TestAssetProxyOwnerContract.deployFrom0xArtifactAsync( + artifacts.TestAssetProxyOwner, + env.provider, + env.txDefaults, + artifacts, + reg.functionSelectors, + reg.destinations, + reg.functionCallTimeLockSeconds, + signerAddresses, + new BigNumber(REQUIRED_SIGNERS), + new BigNumber(DEFAULT_TIME_LOCK), + ); + for (const [index, selector] of reg.functionSelectors.entries()) { + const timelock = await apOwner.functionCallTimeLocks.callAsync(selector, reg.destinations[index]); + expect(timelock[0]).to.equal(true); + expect(timelock[1]).to.bignumber.equal(reg.functionCallTimeLockSeconds[index]); + } }); }); - describe('isFunctionRemoveAuthorizedAddressAtIndex', () => { - it('should return false if data is not for removeAuthorizedAddressAtIndex', async () => { - const notRemoveAuthorizedAddressData = erc20Proxy.addAuthorizedAddress.getABIEncodedTransactionData( - owners[0], + blockchainTests.resets('registerFunctionCall', () => { + it('should revert if not called by wallet', async () => { + const reg = createFunctionRegistration(1, 1, 1); + const tx = assetProxyOwner.registerFunctionCall.awaitTransactionSuccessAsync( + true, + reg.functionSelectors[0], + reg.destinations[0], + reg.functionCallTimeLockSeconds[0], ); - - const isFunctionRemoveAuthorizedAddressAtIndex = await testAssetProxyOwner.isFunctionRemoveAuthorizedAddressAtIndex.callAsync( - notRemoveAuthorizedAddressData, - ); - expect(isFunctionRemoveAuthorizedAddressAtIndex).to.be.false(); + expect(tx).to.revertWith(RevertReason.OnlyCallableByWallet); }); - - it('should return true if data is for removeAuthorizedAddressAtIndex', async () => { - const index = new BigNumber(0); - const removeAuthorizedAddressAtIndexData = erc20Proxy.removeAuthorizedAddressAtIndex.getABIEncodedTransactionData( - owners[0], - index, + it('should register a function call', async () => { + const reg = createFunctionRegistration(1, 1, 1); + const txReceipt = await assetProxyOwner.registerFunctionCallBypassWalet.awaitTransactionSuccessAsync( + true, + reg.functionSelectors[0], + reg.destinations[0], + reg.functionCallTimeLockSeconds[0], ); - const isFunctionRemoveAuthorizedAddressAtIndex = await testAssetProxyOwner.isFunctionRemoveAuthorizedAddressAtIndex.callAsync( - removeAuthorizedAddressAtIndexData, + expect(txReceipt.logs.length).to.eq(1); + const logArgs = (txReceipt.logs[0] as LogWithDecodedArgs< + AssetProxyOwnerFunctionCallTimeLockRegistrationEventArgs + >).args; + expect(logArgs.functionSelector).to.eq(reg.functionSelectors[0]); + expect(logArgs.destination).to.eq(reg.destinations[0]); + expect(logArgs.hasCustomTimeLock).to.eq(true); + expect(logArgs.newSecondsTimeLocked).to.bignumber.eq(reg.functionCallTimeLockSeconds[0]); + const timelock = await assetProxyOwner.functionCallTimeLocks.callAsync( + reg.functionSelectors[0], + reg.destinations[0], ); - expect(isFunctionRemoveAuthorizedAddressAtIndex).to.be.true(); + expect(timelock[0]).to.equal(true); + expect(timelock[1]).to.bignumber.equal(reg.functionCallTimeLockSeconds[0]); + }); + it('should be able to overwrite existing function calls', async () => { + const reg = createFunctionRegistration(1, 1, 1); + await assetProxyOwner.registerFunctionCallBypassWalet.awaitTransactionSuccessAsync( + true, + reg.functionSelectors[0], + reg.destinations[0], + reg.functionCallTimeLockSeconds[0], + ); + const newTimeLock = reg.functionCallTimeLockSeconds[0].plus(1000); + await assetProxyOwner.registerFunctionCallBypassWalet.awaitTransactionSuccessAsync( + true, + reg.functionSelectors[0], + reg.destinations[0], + newTimeLock, + ); + const timelock = await assetProxyOwner.functionCallTimeLocks.callAsync( + reg.functionSelectors[0], + reg.destinations[0], + ); + expect(timelock[0]).to.equal(true); + expect(timelock[1]).to.bignumber.equal(newTimeLock); + }); + it('should clear the function timelock if hasCustomTimeLock is set to false', async () => { + const reg = createFunctionRegistration(1, 1, 1); + await assetProxyOwner.registerFunctionCallBypassWalet.awaitTransactionSuccessAsync( + true, + reg.functionSelectors[0], + reg.destinations[0], + reg.functionCallTimeLockSeconds[0], + ); + await assetProxyOwner.registerFunctionCallBypassWalet.awaitTransactionSuccessAsync( + false, + reg.functionSelectors[0], + reg.destinations[0], + reg.functionCallTimeLockSeconds[0], + ); + const timelock = await assetProxyOwner.functionCallTimeLocks.callAsync( + reg.functionSelectors[0], + reg.destinations[0], + ); + expect(timelock[0]).to.equal(false); + expect(timelock[1]).to.bignumber.equal(constants.ZERO_AMOUNT); }); }); - describe('registerAssetProxy', () => { - it('should revert if not called by multisig', async () => { - const isRegistered = true; - return expectTransactionFailedWithoutReasonAsync( - testAssetProxyOwner.registerAssetProxy.sendTransactionAsync(erc20Proxy.address, isRegistered, { - from: owners[0], - }), + describe('assertValidFunctionCall', () => { + it('should revert if the data is less than 4 bytes long', async () => { + const result = assetProxyOwner.assertValidFunctionCall.callAsync( + constants.ZERO_AMOUNT, + constants.NULL_BYTES, + constants.NULL_ADDRESS, ); + const expectedError = new LibBytesRevertErrors.InvalidByteOperationError( + LibBytesRevertErrors.InvalidByteOperationErrorCodes.LengthGreaterThanOrEqualsFourRequired, + constants.ZERO_AMOUNT, + new BigNumber(4), + ); + expect(result).to.revertWith(expectedError); }); - - it('should register an address if called by multisig after timelock', async () => { - const addressToRegister = erc20Proxy.address; - const isRegistered = true; - const registerAssetProxyData = testAssetProxyOwner.registerAssetProxy.getABIEncodedTransactionData( - addressToRegister, - isRegistered, + it('should revert if an unregistered function is called before the default timelock', async () => { + const latestTimestamp = await getLatestBlockTimestampAsync(); + const transactionConfirmationTime = new BigNumber(latestTimestamp); + const reg = createFunctionRegistration(1, 1, 1); + const result = assetProxyOwner.assertValidFunctionCall.callAsync( + transactionConfirmationTime, + reg.functionSelectors[0], + reg.destinations[0], ); - const submitTxRes = await assetProxyOwnerWrapper.submitTransactionAsync( - testAssetProxyOwner.address, - registerAssetProxyData, - owners[0], - ); - - const log = submitTxRes.logs[0] as LogWithDecodedArgs; - const txId = log.args.transactionId; - - await assetProxyOwnerWrapper.confirmTransactionAsync(txId, owners[1]); - await increaseTimeAndMineBlockAsync(SECONDS_TIME_LOCKED.toNumber()); - - const executeTxRes = await assetProxyOwnerWrapper.executeTransactionAsync(txId, owners[0]); - const registerLog = executeTxRes.logs[0] as LogWithDecodedArgs< - AssetProxyOwnerAssetProxyRegistrationEventArgs - >; - expect(registerLog.args.assetProxyContract).to.equal(addressToRegister); - expect(registerLog.args.isRegistered).to.equal(isRegistered); - - const isAssetProxyRegistered = await testAssetProxyOwner.isAssetProxyRegistered.callAsync( - addressToRegister, - ); - expect(isAssetProxyRegistered).to.equal(isRegistered); + expect(result).to.revertWith(RevertReason.DefaultTimeLockIncomplete); }); - - it('should fail if registering a null address', async () => { - const addressToRegister = constants.NULL_ADDRESS; - const isRegistered = true; - const registerAssetProxyData = testAssetProxyOwner.registerAssetProxy.getABIEncodedTransactionData( - addressToRegister, - isRegistered, + it('should revert if a registered function is called before the custom timelock', async () => { + const reg = createFunctionRegistration(1, 1, 1); + await assetProxyOwner.registerFunctionCallBypassWalet.awaitTransactionSuccessAsync( + true, + reg.functionSelectors[0], + reg.destinations[0], + reg.functionCallTimeLockSeconds[0], ); - const submitTxRes = await assetProxyOwnerWrapper.submitTransactionAsync( - testAssetProxyOwner.address, - registerAssetProxyData, - owners[0], + const latestTimestamp = await getLatestBlockTimestampAsync(); + const transactionConfirmationTime = new BigNumber(latestTimestamp); + const result = assetProxyOwner.assertValidFunctionCall.callAsync( + transactionConfirmationTime, + reg.functionSelectors[0], + reg.destinations[0], ); - const log = submitTxRes.logs[0] as LogWithDecodedArgs; - const txId = log.args.transactionId; - - await assetProxyOwnerWrapper.confirmTransactionAsync(txId, owners[1]); - await increaseTimeAndMineBlockAsync(SECONDS_TIME_LOCKED.toNumber()); - - const executeTxRes = await assetProxyOwnerWrapper.executeTransactionAsync(txId, owners[0]); - const failureLog = executeTxRes.logs[0] as LogWithDecodedArgs; - expect(failureLog.args.transactionId).to.be.bignumber.equal(txId); - - const isAssetProxyRegistered = await testAssetProxyOwner.isAssetProxyRegistered.callAsync( - addressToRegister, - ); - expect(isAssetProxyRegistered).to.equal(false); + expect(result).to.revertWith(RevertReason.CustomTimeLockIncomplete); }); + it('should be successful if an unregistered function is called after the default timelock', async () => { + const latestTimestamp = await getLatestBlockTimestampAsync(); + const transactionConfirmationTime = new BigNumber(latestTimestamp).minus(DEFAULT_TIME_LOCK); + const reg = createFunctionRegistration(1, 1, 1); + const result = assetProxyOwner.assertValidFunctionCall.callAsync( + transactionConfirmationTime, + reg.functionSelectors[0], + reg.destinations[0], + ); + expect(result).to.be.fulfilled(''); + }); + it('should be successful if a registered function is called after the custom timelock', async () => { + const reg = createFunctionRegistration(1, 1, 1); + await assetProxyOwner.registerFunctionCallBypassWalet.awaitTransactionSuccessAsync( + true, + reg.functionSelectors[0], + reg.destinations[0], + reg.functionCallTimeLockSeconds[0], + ); + const latestTimestamp = await getLatestBlockTimestampAsync(); + const transactionConfirmationTime = new BigNumber(latestTimestamp).minus( + reg.functionCallTimeLockSeconds[0], + ); + const result = assetProxyOwner.assertValidFunctionCall.callAsync( + transactionConfirmationTime, + reg.functionSelectors[0], + reg.destinations[0], + ); + expect(result).to.be.fulfilled(''); + }); + it('should allow a custom timelock to be set to 0', async () => {}); }); - describe('Calling removeAuthorizedAddressAtIndex', () => { - const erc20Index = new BigNumber(0); - const erc721Index = new BigNumber(1); - before('authorize both proxies and register erc20 proxy', async () => { - // Only register ERC20 proxy - const addressToRegister = erc20Proxy.address; - const isRegistered = true; - const registerAssetProxyData = testAssetProxyOwner.registerAssetProxy.getABIEncodedTransactionData( - addressToRegister, - isRegistered, - ); - const registerAssetProxySubmitRes = await assetProxyOwnerWrapper.submitTransactionAsync( - testAssetProxyOwner.address, - registerAssetProxyData, - owners[0], - ); - const registerAssetProxySubmitLog = registerAssetProxySubmitRes.logs[0] as LogWithDecodedArgs< - AssetProxyOwnerSubmissionEventArgs - >; - - const addAuthorizedAddressData = erc20Proxy.addAuthorizedAddress.getABIEncodedTransactionData(authorized); - const erc20AddAuthorizedAddressSubmitRes = await assetProxyOwnerWrapper.submitTransactionAsync( - erc20Proxy.address, - addAuthorizedAddressData, - owners[0], - ); - const erc721AddAuthorizedAddressSubmitRes = await assetProxyOwnerWrapper.submitTransactionAsync( - erc721Proxy.address, - addAuthorizedAddressData, - owners[0], - ); - const erc20AddAuthorizedAddressSubmitLog = erc20AddAuthorizedAddressSubmitRes.logs[0] as LogWithDecodedArgs< - AssetProxyOwnerSubmissionEventArgs - >; - const erc721AddAuthorizedAddressSubmitLog = erc721AddAuthorizedAddressSubmitRes - .logs[0] as LogWithDecodedArgs; - - const registerAssetProxyTxId = registerAssetProxySubmitLog.args.transactionId; - const erc20AddAuthorizedAddressTxId = erc20AddAuthorizedAddressSubmitLog.args.transactionId; - const erc721AddAuthorizedAddressTxId = erc721AddAuthorizedAddressSubmitLog.args.transactionId; - - await assetProxyOwnerWrapper.confirmTransactionAsync(registerAssetProxyTxId, owners[1]); - await assetProxyOwnerWrapper.confirmTransactionAsync(erc20AddAuthorizedAddressTxId, owners[1]); - await assetProxyOwnerWrapper.confirmTransactionAsync(erc721AddAuthorizedAddressTxId, owners[1]); - await increaseTimeAndMineBlockAsync(SECONDS_TIME_LOCKED.toNumber()); - await assetProxyOwnerWrapper.executeTransactionAsync(registerAssetProxyTxId, owners[0]); - await assetProxyOwnerWrapper.executeTransactionAsync(erc20AddAuthorizedAddressTxId, owners[0], { - gas: constants.MAX_EXECUTE_TRANSACTION_GAS, + blockchainTests.resets('executeTransaction', () => { + function assertReceiverCalledFromLogs( + logs: LogEntry[], + data: string[], + destinations: string[], + txId: BigNumber, + values?: BigNumber[], + ): void { + expect(logs.length).to.eq(data.length + 1); + data.forEach((calldata: string, i: number) => { + expect(logs[i].address).to.eq(destinations[i]); + const contractCallLogArgs = (logs[i] as LogWithDecodedArgs).args; + expect(contractCallLogArgs.functionSelector).to.eq(data[i].slice(0, 10)); + expect(contractCallLogArgs.data).to.eq(data[i]); + const value = values === undefined ? constants.ZERO_AMOUNT : values[i]; + expect(contractCallLogArgs.value).to.bignumber.eq(value); }); - await assetProxyOwnerWrapper.executeTransactionAsync(erc721AddAuthorizedAddressTxId, owners[0], { - gas: constants.MAX_EXECUTE_TRANSACTION_GAS, + const executionLog = logs[data.length] as LogWithDecodedArgs; + expect(executionLog.event).to.eq('Execution'); + expect(executionLog.args.transactionId).to.bignumber.eq(txId); + } + it('should revert if the transaction is not confirmed by the required amount of signers', async () => { + const data = [hexRandom()]; + const destinations = [receiver.address]; + const results = await assetProxyOwnerWrapper.submitTransactionAsync(data, destinations, signerAddresses[0]); + const tx = assetProxyOwner.executeTransaction.awaitTransactionSuccessAsync(results.txId, { + from: signerAddresses[1], }); + expect(tx).to.revertWith(RevertReason.TxNotFullyConfirmed); }); - - describe('validRemoveAuthorizedAddressAtIndexTx', () => { - it('should revert if data is not for removeAuthorizedAddressAtIndex and proxy is registered', async () => { - const notRemoveAuthorizedAddressData = erc20Proxy.addAuthorizedAddress.getABIEncodedTransactionData( - authorized, - ); - const submitTxRes = await assetProxyOwnerWrapper.submitTransactionAsync( - erc20Proxy.address, - notRemoveAuthorizedAddressData, - owners[0], - ); - const log = submitTxRes.logs[0] as LogWithDecodedArgs; - const txId = log.args.transactionId; - return expectContractCallFailedAsync( - testAssetProxyOwner.testValidRemoveAuthorizedAddressAtIndexTx.callAsync(txId), - RevertReason.InvalidFunctionSelector, - ); - }); - - it('should return true if data is for removeAuthorizedAddressAtIndex and proxy is registered', async () => { - const removeAuthorizedAddressAtIndexData = erc20Proxy.removeAuthorizedAddressAtIndex.getABIEncodedTransactionData( - authorized, - erc20Index, - ); - const submitTxRes = await assetProxyOwnerWrapper.submitTransactionAsync( - erc20Proxy.address, - removeAuthorizedAddressAtIndexData, - owners[0], - ); - const log = submitTxRes.logs[0] as LogWithDecodedArgs; - const txId = log.args.transactionId; - const isValidRemoveAuthorizedAddressAtIndexTx = await testAssetProxyOwner.testValidRemoveAuthorizedAddressAtIndexTx.callAsync( - txId, - ); - expect(isValidRemoveAuthorizedAddressAtIndexTx).to.be.true(); - }); - - it('should revert if data is for removeAuthorizedAddressAtIndex and proxy is not registered', async () => { - const removeAuthorizedAddressAtIndexData = erc721Proxy.removeAuthorizedAddressAtIndex.getABIEncodedTransactionData( - authorized, - erc721Index, - ); - const submitTxRes = await assetProxyOwnerWrapper.submitTransactionAsync( - erc721Proxy.address, - removeAuthorizedAddressAtIndexData, - owners[0], - ); - const log = submitTxRes.logs[0] as LogWithDecodedArgs; - const txId = log.args.transactionId; - return expectContractCallFailedAsync( - testAssetProxyOwner.testValidRemoveAuthorizedAddressAtIndexTx.callAsync(txId), - RevertReason.UnregisteredAssetProxy, - ); - }); + it('should be able to execute an unregistered function after the default timelock with no value', async () => { + const data = [hexRandom()]; + const destinations = [receiver.address]; + const results = await assetProxyOwnerWrapper.submitConfirmAndExecuteTransactionAsync( + data, + destinations, + signerAddresses, + DEFAULT_TIME_LOCK, + ); + assertReceiverCalledFromLogs(results.executionTxReceipt.logs, data, destinations, results.txId); }); - - describe('executeRemoveAuthorizedAddressAtIndex', () => { - it('should revert without the required confirmations', async () => { - const removeAuthorizedAddressAtIndexData = erc20Proxy.removeAuthorizedAddressAtIndex.getABIEncodedTransactionData( - authorized, - erc20Index, - ); - const res = await assetProxyOwnerWrapper.submitTransactionAsync( - erc20Proxy.address, - removeAuthorizedAddressAtIndexData, - owners[0], - ); - const log = res.logs[0] as LogWithDecodedArgs; - const txId = log.args.transactionId; - - return expectTransactionFailedAsync( - testAssetProxyOwner.executeRemoveAuthorizedAddressAtIndex.sendTransactionAsync(txId, { - from: owners[1], - }), - RevertReason.TxNotFullyConfirmed, - ); - }); - - it('should revert if tx destination is not registered', async () => { - const removeAuthorizedAddressAtIndexData = erc721Proxy.removeAuthorizedAddressAtIndex.getABIEncodedTransactionData( - authorized, - erc721Index, - ); - const res = await assetProxyOwnerWrapper.submitTransactionAsync( - erc721Proxy.address, - removeAuthorizedAddressAtIndexData, - owners[0], - ); - const log = res.logs[0] as LogWithDecodedArgs; - const txId = log.args.transactionId; - - await assetProxyOwnerWrapper.confirmTransactionAsync(txId, owners[1]); - - return expectTransactionFailedAsync( - testAssetProxyOwner.executeRemoveAuthorizedAddressAtIndex.sendTransactionAsync(txId, { - from: owners[1], - }), - RevertReason.UnregisteredAssetProxy, - ); - }); - - it('should revert if tx data is not for removeAuthorizedAddressAtIndex', async () => { - const newAuthorized = owners[1]; - const addAuthorizedAddressData = erc20Proxy.addAuthorizedAddress.getABIEncodedTransactionData( - newAuthorized, - ); - const res = await assetProxyOwnerWrapper.submitTransactionAsync( - erc20Proxy.address, - addAuthorizedAddressData, - owners[0], - ); - const log = res.logs[0] as LogWithDecodedArgs; - const txId = log.args.transactionId; - - await assetProxyOwnerWrapper.confirmTransactionAsync(txId, owners[1]); - - return expectTransactionFailedAsync( - testAssetProxyOwner.executeRemoveAuthorizedAddressAtIndex.sendTransactionAsync(txId, { - from: owners[1], - }), - RevertReason.InvalidFunctionSelector, - ); - }); - - 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, - ); - const submitRes = await assetProxyOwnerWrapper.submitTransactionAsync( - erc20Proxy.address, - removeAuthorizedAddressAtIndexData, - owners[0], - ); - const submitLog = submitRes.logs[0] as LogWithDecodedArgs; - const txId = submitLog.args.transactionId; - - await assetProxyOwnerWrapper.confirmTransactionAsync(txId, owners[1]); - - const execRes = await assetProxyOwnerWrapper.executeRemoveAuthorizedAddressAtIndexAsync( - txId, - owners[0], - ); - const execLog = execRes.logs[1] as LogWithDecodedArgs; - 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 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 assetProxyOwnerWrapper.submitTransactionAsync( - erc20Proxy.address, - removeAuthorizedAddressAtIndexData, - owners[0], - ); - const submitLog = submitRes.logs[0] as LogWithDecodedArgs; - const txId = submitLog.args.transactionId; - - await assetProxyOwnerWrapper.confirmTransactionAsync(txId, owners[1]); - - const execRes = await assetProxyOwnerWrapper.executeRemoveAuthorizedAddressAtIndexAsync(txId, notOwner); - const execLog = execRes.logs[1] as LogWithDecodedArgs; - 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 revert if already executed', async () => { - const removeAuthorizedAddressAtIndexData = erc20Proxy.removeAuthorizedAddressAtIndex.getABIEncodedTransactionData( - authorized, - erc20Index, - ); - const submitRes = await assetProxyOwnerWrapper.submitTransactionAsync( - erc20Proxy.address, - removeAuthorizedAddressAtIndexData, - owners[0], - ); - const submitLog = submitRes.logs[0] as LogWithDecodedArgs; - const txId = submitLog.args.transactionId; - - await assetProxyOwnerWrapper.confirmTransactionAsync(txId, owners[1]); - - const execRes = await assetProxyOwnerWrapper.executeRemoveAuthorizedAddressAtIndexAsync( - txId, - owners[0], - ); - const execLog = execRes.logs[1] as LogWithDecodedArgs; - 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); - - return expectTransactionFailedWithoutReasonAsync( - testAssetProxyOwner.executeRemoveAuthorizedAddressAtIndex.sendTransactionAsync(txId, { - from: owners[1], - }), - ); - }); + it('should be able to execute an unregistered function after the default timelock with a value', async () => { + const data = [hexRandom()]; + const destinations = [receiver.address]; + const values = [INITIAL_BALANCE]; + const results = await assetProxyOwnerWrapper.submitConfirmAndExecuteTransactionAsync( + data, + destinations, + signerAddresses, + DEFAULT_TIME_LOCK, + { values }, + ); + assertReceiverCalledFromLogs(results.executionTxReceipt.logs, data, destinations, results.txId, values); + }); + it('should be able to execute a registered function after a custom timelock with no value', async () => { + const data = [hexRandom()]; + const destinations = [receiver.address]; + const newTimeLock = new BigNumber(DEFAULT_TIME_LOCK).dividedToIntegerBy(2); + await assetProxyOwner.registerFunctionCallBypassWalet.awaitTransactionSuccessAsync( + true, + data[0].slice(0, 10), + receiver.address, + newTimeLock, + ); + const results = await assetProxyOwnerWrapper.submitConfirmAndExecuteTransactionAsync( + data, + destinations, + signerAddresses, + newTimeLock.toNumber(), + ); + assertReceiverCalledFromLogs(results.executionTxReceipt.logs, data, destinations, results.txId); + }); + it('should be able to execute a registered function with no timelock', async () => { + const data = [hexRandom()]; + const destinations = [receiver.address]; + const newTimeLock = constants.ZERO_AMOUNT; + await assetProxyOwner.registerFunctionCallBypassWalet.awaitTransactionSuccessAsync( + true, + data[0].slice(0, 10), + receiver.address, + newTimeLock, + ); + const results = await assetProxyOwnerWrapper.submitConfirmAndExecuteTransactionAsync( + data, + destinations, + signerAddresses, + newTimeLock.toNumber(), + ); + assertReceiverCalledFromLogs(results.executionTxReceipt.logs, data, destinations, results.txId); + }); + it('should be able to execute a registered function after a custom timelock with a value', async () => { + const data = [hexRandom()]; + const destinations = [receiver.address]; + const newTimeLock = new BigNumber(DEFAULT_TIME_LOCK).dividedToIntegerBy(2); + await assetProxyOwner.registerFunctionCallBypassWalet.awaitTransactionSuccessAsync( + true, + data[0].slice(0, 10), + receiver.address, + newTimeLock, + ); + const values = [INITIAL_BALANCE]; + const results = await assetProxyOwnerWrapper.submitConfirmAndExecuteTransactionAsync( + data, + destinations, + signerAddresses, + newTimeLock.toNumber(), + { values }, + ); + assertReceiverCalledFromLogs(results.executionTxReceipt.logs, data, destinations, results.txId, values); + }); + it('should be able to call multiple functions with a single destination and no values', async () => { + const data = [hexRandom(), hexRandom()]; + const destinations = [receiver.address, receiver.address]; + const results = await assetProxyOwnerWrapper.submitConfirmAndExecuteTransactionAsync( + data, + destinations, + signerAddresses, + DEFAULT_TIME_LOCK, + ); + assertReceiverCalledFromLogs(results.executionTxReceipt.logs, data, destinations, results.txId); + }); + it('should be able to call multiple functions with different destinations and values', async () => { + const receiver2 = await ContractCallReceiverContract.deployFrom0xArtifactAsync( + artifacts.ContractCallReceiver, + env.provider, + env.txDefaults, + {}, + ); + const data = [hexRandom(), hexRandom()]; + const destinations = [receiver.address, receiver2.address]; + const values = [INITIAL_BALANCE.dividedToIntegerBy(4), INITIAL_BALANCE.dividedToIntegerBy(3)]; + const results = await assetProxyOwnerWrapper.submitConfirmAndExecuteTransactionAsync( + data, + destinations, + signerAddresses, + DEFAULT_TIME_LOCK, + { values }, + ); + assertReceiverCalledFromLogs(results.executionTxReceipt.logs, data, destinations, results.txId, values); + }); + it('should be able to call a combination of registered and unregistered functions', async () => { + const data = [hexRandom(), hexRandom()]; + const destinations = [receiver.address, receiver.address]; + const newTimeLock = new BigNumber(DEFAULT_TIME_LOCK).dividedToIntegerBy(2); + await assetProxyOwner.registerFunctionCallBypassWalet.awaitTransactionSuccessAsync( + true, + data[0].slice(0, 10), + receiver.address, + newTimeLock, + ); + const results = await assetProxyOwnerWrapper.submitConfirmAndExecuteTransactionAsync( + data, + destinations, + signerAddresses, + DEFAULT_TIME_LOCK, + ); + assertReceiverCalledFromLogs(results.executionTxReceipt.logs, data, destinations, results.txId); + }); + it('should fail if a single function has not passed the timelock', async () => { + const data = [hexRandom(), hexRandom()]; + const destinations = [receiver.address, receiver.address]; + const newTimeLock = new BigNumber(DEFAULT_TIME_LOCK).dividedToIntegerBy(2); + await assetProxyOwner.registerFunctionCallBypassWalet.awaitTransactionSuccessAsync( + true, + data[0].slice(0, 10), + receiver.address, + newTimeLock, + ); + const tx = assetProxyOwnerWrapper.submitConfirmAndExecuteTransactionAsync( + data, + destinations, + signerAddresses, + newTimeLock.toNumber(), + ); + expect(tx).to.revertWith(RevertReason.DefaultTimeLockIncomplete); + }); + it('should be able to execute a transaction if called by any address', async () => { + const data = [hexRandom()]; + const destinations = [receiver.address]; + const results = await assetProxyOwnerWrapper.submitConfirmAndExecuteTransactionAsync( + data, + destinations, + signerAddresses, + DEFAULT_TIME_LOCK, + { executeFromAddress: notSignerAddress }, + ); + assertReceiverCalledFromLogs(results.executionTxReceipt.logs, data, destinations, results.txId); + }); + it('should be able to send value without data if past the default timelock', async () => { + // NOTE: elements of `data` must be at least 4 bytes long + const data = [constants.NULL_BYTES4]; + const destinations = [receiver.address]; + const results = await assetProxyOwnerWrapper.submitConfirmAndExecuteTransactionAsync( + data, + destinations, + signerAddresses, + DEFAULT_TIME_LOCK, + ); + assertReceiverCalledFromLogs(results.executionTxReceipt.logs, data, destinations, results.txId); + }); + it('should not call a function if the input array lengths are 0', async () => { + const data: string[] = []; + const destinations: string[] = []; + const results = await assetProxyOwnerWrapper.submitConfirmAndExecuteTransactionAsync( + data, + destinations, + signerAddresses, + DEFAULT_TIME_LOCK, + ); + assertReceiverCalledFromLogs(results.executionTxReceipt.logs, data, destinations, results.txId); + }); + it('should revert if destinations.length != data.length', async () => { + const data = [hexRandom(), hexRandom()]; + const destinations = [receiver.address]; + const tx = assetProxyOwnerWrapper.submitConfirmAndExecuteTransactionAsync( + data, + destinations, + signerAddresses, + DEFAULT_TIME_LOCK, + ); + expect(tx).to.revertWith(RevertReason.EqualLengthsRequired); + }); + it('should revert if values.length != data.length', async () => { + const data = [hexRandom()]; + const destinations = [receiver.address]; + const values = [constants.ZERO_AMOUNT, constants.ZERO_AMOUNT]; + const tx = assetProxyOwnerWrapper.submitConfirmAndExecuteTransactionAsync( + data, + destinations, + signerAddresses, + DEFAULT_TIME_LOCK, + { values }, + ); + expect(tx).to.revertWith(RevertReason.EqualLengthsRequired); + }); + it('should revert if the transaction is already executed', async () => { + const data = [hexRandom()]; + const destinations = [receiver.address]; + const results = await assetProxyOwnerWrapper.submitConfirmAndExecuteTransactionAsync( + data, + destinations, + signerAddresses, + DEFAULT_TIME_LOCK, + ); + const tx = assetProxyOwner.executeTransaction.awaitTransactionSuccessAsync(results.txId); + expect(tx).to.revertWith(RevertReason.TxAlreadyExecuted); + }); + it('should be able to call registerFunctionCall after the default timelock', async () => { + const reg = createFunctionRegistration(1, 1, 1); + const data = [ + assetProxyOwner.registerFunctionCall.getABIEncodedTransactionData( + true, + reg.functionSelectors[0], + reg.destinations[0], + reg.functionCallTimeLockSeconds[0], + ), + ]; + const destinations = [assetProxyOwner.address]; + const results = await assetProxyOwnerWrapper.submitConfirmAndExecuteTransactionAsync( + data, + destinations, + signerAddresses, + DEFAULT_TIME_LOCK, + ); + expect(results.executionTxReceipt.logs.length).to.eq(2); + const registrationLogArgs = (results.executionTxReceipt.logs[0] as LogWithDecodedArgs< + AssetProxyOwnerFunctionCallTimeLockRegistrationEventArgs + >).args; + expect(registrationLogArgs.destination).to.eq(reg.destinations[0]); + expect(registrationLogArgs.functionSelector).to.eq(reg.functionSelectors[0]); + expect(registrationLogArgs.hasCustomTimeLock).to.eq(true); + expect(registrationLogArgs.newSecondsTimeLocked).to.bignumber.eq(reg.functionCallTimeLockSeconds[0]); }); }); }); -// tslint:disable-line max-file-line-count diff --git a/contracts/multisig/test/utils/asset_proxy_owner_wrapper.ts b/contracts/multisig/test/utils/asset_proxy_owner_wrapper.ts index fdc3862450..625c8177f9 100644 --- a/contracts/multisig/test/utils/asset_proxy_owner_wrapper.ts +++ b/contracts/multisig/test/utils/asset_proxy_owner_wrapper.ts @@ -1,72 +1,48 @@ -import { artifacts as proxyArtifacts } from '@0x/contracts-asset-proxy'; -import { LogDecoder, Web3ProviderEngine } from '@0x/contracts-test-utils'; -import { BigNumber } from '@0x/utils'; -import { Web3Wrapper } from '@0x/web3-wrapper'; -import { TransactionReceiptWithDecodedLogs } from 'ethereum-types'; +import { constants, hexRandom, increaseTimeAndMineBlockAsync } from '@0x/contracts-test-utils'; +import { AbiEncoder, BigNumber } from '@0x/utils'; +import { LogWithDecodedArgs, TransactionReceiptWithDecodedLogs } from 'ethereum-types'; -import { AssetProxyOwnerContract, TestAssetProxyOwnerContract } from '../../src'; -import { artifacts } from '../../src/artifacts'; +import { AssetProxyOwnerContract, AssetProxyOwnerSubmissionEventArgs, TestAssetProxyOwnerContract } from '../../src'; export class AssetProxyOwnerWrapper { private readonly _assetProxyOwner: AssetProxyOwnerContract | TestAssetProxyOwnerContract; - private readonly _web3Wrapper: Web3Wrapper; - private readonly _logDecoder: LogDecoder; - constructor( - assetproxyOwnerContract: AssetProxyOwnerContract | TestAssetProxyOwnerContract, - provider: Web3ProviderEngine, - ) { + constructor(assetproxyOwnerContract: AssetProxyOwnerContract | TestAssetProxyOwnerContract) { this._assetProxyOwner = assetproxyOwnerContract; - this._web3Wrapper = new Web3Wrapper(provider); - this._logDecoder = new LogDecoder(this._web3Wrapper, { ...artifacts, ...proxyArtifacts }); } public async submitTransactionAsync( - destination: string, - data: string, + data: string[], + destinations: string[], from: string, - opts: { value?: BigNumber } = {}, - ): Promise { - const value = opts.value === undefined ? new BigNumber(0) : opts.value; - const txHash = await this._assetProxyOwner.submitTransaction.sendTransactionAsync(destination, value, data, { - from, - }); - const tx = await this._logDecoder.getTxWithDecodedLogsAsync(txHash); - return tx; - } - public async confirmTransactionAsync(txId: BigNumber, from: string): Promise { - const txHash = await this._assetProxyOwner.confirmTransaction.sendTransactionAsync(txId, { from }); - const tx = await this._logDecoder.getTxWithDecodedLogsAsync(txHash); - return tx; - } - public async revokeConfirmationAsync(txId: BigNumber, from: string): Promise { - const txHash = await this._assetProxyOwner.revokeConfirmation.sendTransactionAsync(txId, { from }); - const tx = await this._logDecoder.getTxWithDecodedLogsAsync(txHash); - return tx; - } - public async executeTransactionAsync( - txId: BigNumber, - from: string, - opts: { gas?: number } = {}, - ): Promise { - const txHash = await this._assetProxyOwner.executeTransaction.sendTransactionAsync(txId, { - from, - gas: opts.gas, - }); - const tx = await this._logDecoder.getTxWithDecodedLogsAsync(txHash); - return tx; - } - public async executeRemoveAuthorizedAddressAtIndexAsync( - txId: BigNumber, - from: string, - ): Promise { - // tslint:disable-next-line:no-unnecessary-type-assertion - const txHash = await (this - ._assetProxyOwner as TestAssetProxyOwnerContract).executeRemoveAuthorizedAddressAtIndex.sendTransactionAsync( - txId, - { - from, - }, + opts: { values?: BigNumber[] } = {}, + ): Promise<{ txReceipt: TransactionReceiptWithDecodedLogs; txId: BigNumber }> { + const values = opts.values === undefined ? data.map(() => constants.ZERO_AMOUNT) : opts.values; + const batchTransactionEncoder = AbiEncoder.create('(bytes[],address[],uint256[])'); + const batchTransactionData = batchTransactionEncoder.encode([data, destinations, values]); + const txReceipt = await this._assetProxyOwner.submitTransaction.awaitTransactionSuccessAsync( + hexRandom(20), // submitTransaction will fail if this is a null address + constants.ZERO_AMOUNT, + batchTransactionData, + { from }, ); - const tx = await this._logDecoder.getTxWithDecodedLogsAsync(txHash); - return tx; + const txId = (txReceipt.logs[0] as LogWithDecodedArgs).args.transactionId; + return { txReceipt, txId }; + } + public async submitConfirmAndExecuteTransactionAsync( + data: string[], + destinations: string[], + signerAddresses: string[], + increaseTimeSeconds: number, + opts: { values?: BigNumber[]; executeFromAddress?: string } = {}, + ): Promise<{ executionTxReceipt: TransactionReceiptWithDecodedLogs; txId: BigNumber }> { + const submitResults = await this.submitTransactionAsync(data, destinations, signerAddresses[0], opts); + await this._assetProxyOwner.confirmTransaction.awaitTransactionSuccessAsync(submitResults.txId, { + from: signerAddresses[1], + }); + await increaseTimeAndMineBlockAsync(increaseTimeSeconds); + const executionTxReceipt = await this._assetProxyOwner.executeTransaction.awaitTransactionSuccessAsync( + submitResults.txId, + { from: opts.executeFromAddress === undefined ? signerAddresses[0] : opts.executeFromAddress }, + ); + return { executionTxReceipt, txId: submitResults.txId }; } } diff --git a/contracts/multisig/test/utils/index.ts b/contracts/multisig/test/utils/index.ts index 382fd92e54..c6cc6e30d2 100644 --- a/contracts/multisig/test/utils/index.ts +++ b/contracts/multisig/test/utils/index.ts @@ -1,2 +1,2 @@ -export * from './asset_proxy_owner_wrapper'; export * from './multi_sig_wrapper'; +export * from './asset_proxy_owner_wrapper'; diff --git a/contracts/multisig/tsconfig.json b/contracts/multisig/tsconfig.json index 394575f96f..cbcd1aaf31 100644 --- a/contracts/multisig/tsconfig.json +++ b/contracts/multisig/tsconfig.json @@ -4,6 +4,7 @@ "include": ["./src/**/*", "./test/**/*", "./generated-wrappers/**/*"], "files": [ "generated-artifacts/AssetProxyOwner.json", + "generated-artifacts/ContractCallReceiver.json", "generated-artifacts/MultiSigWallet.json", "generated-artifacts/MultiSigWalletWithTimeLock.json", "generated-artifacts/TestAssetProxyOwner.json", diff --git a/contracts/test-utils/src/constants.ts b/contracts/test-utils/src/constants.ts index 96f3e189b2..83f6bd4197 100644 --- a/contracts/test-utils/src/constants.ts +++ b/contracts/test-utils/src/constants.ts @@ -53,8 +53,8 @@ export const constants = { NUM_DUMMY_ERC1155_CONTRACTS_TO_DEPLOY: 2, NUM_ERC1155_FUNGIBLE_TOKENS_MINT: 4, NUM_ERC1155_NONFUNGIBLE_TOKENS_MINT: 4, - NULL_ADDRESS: '0x0000000000000000000000000000000000000000', NULL_BYTES4: '0x00000000', + NULL_ADDRESS: '0x0000000000000000000000000000000000000000', NULL_BYTES32: '0x0000000000000000000000000000000000000000000000000000000000000000', UNLIMITED_ALLOWANCE_IN_BASE_UNITS: MAX_UINT256, MAX_UINT256, diff --git a/yarn.lock b/yarn.lock index cfd343eb17..2ec1658bdc 100644 --- a/yarn.lock +++ b/yarn.lock @@ -643,12 +643,6 @@ npmlog "^4.1.2" write-file-atomic "^2.3.0" -"@0x/abi-gen-wrappers@^3.0.1": - version "3.0.3" - resolved "https://registry.yarnpkg.com/@0x/abi-gen-wrappers/-/abi-gen-wrappers-3.0.3.tgz#a30fdb3c520ce45fb327590281d000e086ff9609" - dependencies: - "@0x/base-contract" "^4.0.3" - "@0x/asset-buyer@6.1.8": version "6.1.8" resolved "https://registry.yarnpkg.com/@0x/asset-buyer/-/asset-buyer-6.1.8.tgz#71f6abb366e89e62457c256644edb37e12113e94" @@ -666,27 +660,6 @@ ethereum-types "^2.1.3" lodash "^4.17.11" -"@0x/base-contract@^4.0.1", "@0x/base-contract@^4.0.3": - version "4.0.3" - resolved "https://registry.yarnpkg.com/@0x/base-contract/-/base-contract-4.0.3.tgz#ea5e3640824ee096813350e55546d98455a57805" - dependencies: - "@0x/typescript-typings" "^4.0.0" - "@0x/utils" "^4.1.0" - "@0x/web3-wrapper" "^5.0.0" - ethereum-types "^2.0.0" - ethers "~4.0.4" - lodash "^4.17.11" - -"@0x/contract-addresses@^2.2.1": - version "2.3.3" - resolved "https://registry.yarnpkg.com/@0x/contract-addresses/-/contract-addresses-2.3.3.tgz#8cf009e7668c2fccca416177c85f3f6612c724c6" - dependencies: - lodash "^4.17.11" - -"@0x/contract-artifacts@^1.3.0": - version "1.5.1" - resolved "https://registry.npmjs.org/@0x/contract-artifacts/-/contract-artifacts-1.5.1.tgz#6fba56a1d3e2d5d897a75fcfa432e49e2ebb17a7" - "@0x/contract-wrappers@^9.1.6", "@0x/contract-wrappers@^9.1.7": version "9.1.8" resolved "https://registry.yarnpkg.com/@0x/contract-wrappers/-/contract-wrappers-9.1.8.tgz#5923d35af3e4b442a57d02f74e02620b2d5b1356" @@ -711,21 +684,6 @@ lodash "^4.17.11" uuid "^3.3.2" -"@0x/contracts-utils@2.0.1": - version "2.0.1" - resolved "https://registry.yarnpkg.com/@0x/contracts-utils/-/contracts-utils-2.0.1.tgz#32e298ab5e6edb045c37294063ff928b629db0a4" - dependencies: - "@0x/base-contract" "^4.0.1" - "@0x/order-utils" "^5.0.0" - "@0x/types" "^2.0.1" - "@0x/typescript-typings" "^4.0.0" - "@0x/utils" "^4.0.2" - "@0x/web3-wrapper" "^4.0.1" - bn.js "^4.11.8" - ethereum-types "^2.0.0" - ethereumjs-util "^5.1.1" - lodash "^4.17.5" - "@0x/coordinator-server@^0.1.3": version "0.1.3" resolved "https://registry.yarnpkg.com/@0x/coordinator-server/-/coordinator-server-0.1.3.tgz#5fbb7c11bb641aa5386797769cab9a68a7d15b79" @@ -755,28 +713,6 @@ typeorm "0.2.7" websocket "^1.0.25" -"@0x/order-utils@^5.0.0": - version "5.0.0" - resolved "https://registry.yarnpkg.com/@0x/order-utils/-/order-utils-5.0.0.tgz#7f43e0310ace31738895881501c8dda9c3a3aefa" - dependencies: - "@0x/abi-gen-wrappers" "^3.0.1" - "@0x/assert" "^2.0.1" - "@0x/base-contract" "^4.0.1" - "@0x/contract-addresses" "^2.2.1" - "@0x/contract-artifacts" "^1.3.0" - "@0x/json-schemas" "^3.0.1" - "@0x/types" "^2.0.1" - "@0x/typescript-typings" "^4.0.0" - "@0x/utils" "^4.0.2" - "@0x/web3-wrapper" "^4.0.1" - "@types/node" "*" - bn.js "^4.11.8" - ethereum-types "^2.0.0" - ethereumjs-abi "0.6.5" - ethereumjs-util "^5.1.1" - ethers "~4.0.4" - lodash "^4.17.11" - "@0x/subproviders@^4.1.1": version "4.1.2" resolved "https://registry.yarnpkg.com/@0x/subproviders/-/subproviders-4.1.2.tgz#ab7bb0f482b11ccb4615fb5dd8ca85199cd0ae23" @@ -806,32 +742,6 @@ optionalDependencies: "@ledgerhq/hw-transport-node-hid" "^4.3.0" -"@0x/web3-wrapper@^4.0.1": - version "4.0.2" - resolved "https://registry.yarnpkg.com/@0x/web3-wrapper/-/web3-wrapper-4.0.2.tgz#d4e0a4fa1217155e1aed4cd91086654fd99f2959" - dependencies: - "@0x/assert" "^2.0.2" - "@0x/json-schemas" "^3.0.2" - "@0x/typescript-typings" "^4.0.0" - "@0x/utils" "^4.0.3" - ethereum-types "^2.0.0" - ethereumjs-util "^5.1.1" - ethers "~4.0.4" - lodash "^4.17.11" - -"@0x/web3-wrapper@^5.0.0": - version "5.0.0" - resolved "https://registry.yarnpkg.com/@0x/web3-wrapper/-/web3-wrapper-5.0.0.tgz#a9b4baf0dca125181885b31c4dd5a3fbf6b87260" - dependencies: - "@0x/assert" "^2.0.3" - "@0x/json-schemas" "^3.0.3" - "@0x/typescript-typings" "^4.0.0" - "@0x/utils" "^4.1.0" - ethereum-types "^2.0.0" - ethereumjs-util "^5.1.1" - ethers "~4.0.4" - lodash "^4.17.11" - "@0xproject/npm-cli-login@^0.0.11": version "0.0.11" resolved "https://registry.yarnpkg.com/@0xproject/npm-cli-login/-/npm-cli-login-0.0.11.tgz#3f1ec06112ce62aad300ff0575358f68aeecde2e"