diff options
Diffstat (limited to 'packages/contracts/test/multisig')
-rw-r--r-- | packages/contracts/test/multisig/asset_proxy_owner.ts | 112 | ||||
-rw-r--r-- | packages/contracts/test/multisig/multi_sig_with_time_lock.ts | 197 |
2 files changed, 259 insertions, 50 deletions
diff --git a/packages/contracts/test/multisig/asset_proxy_owner.ts b/packages/contracts/test/multisig/asset_proxy_owner.ts index 9515941ff..087152316 100644 --- a/packages/contracts/test/multisig/asset_proxy_owner.ts +++ b/packages/contracts/test/multisig/asset_proxy_owner.ts @@ -1,5 +1,6 @@ -import { BlockchainLifecycle } from '@0xproject/dev-utils'; -import { BigNumber } from '@0xproject/utils'; +import { BlockchainLifecycle } from '@0x/dev-utils'; +import { RevertReason } from '@0x/types'; +import { BigNumber } from '@0x/utils'; import * as chai from 'chai'; import { LogWithDecodedArgs } from 'ethereum-types'; @@ -9,14 +10,16 @@ import { AssetProxyOwnerExecutionEventArgs, AssetProxyOwnerExecutionFailureEventArgs, AssetProxyOwnerSubmissionEventArgs, -} from '../../generated_contract_wrappers/asset_proxy_owner'; -import { MixinAuthorizableContract } from '../../generated_contract_wrappers/mixin_authorizable'; -import { TestAssetProxyOwnerContract } from '../../generated_contract_wrappers/test_asset_proxy_owner'; -import { artifacts } from '../utils/artifacts'; +} from '../../generated-wrappers/asset_proxy_owner'; +import { MixinAuthorizableContract } from '../../generated-wrappers/mixin_authorizable'; +import { TestAssetProxyOwnerContract } from '../../generated-wrappers/test_asset_proxy_owner'; +import { artifacts } from '../../src/artifacts'; import { - expectContractCallFailedWithoutReasonAsync, - expectContractCreationFailedWithoutReason, + expectContractCallFailedAsync, + expectContractCreationFailedAsync, + expectTransactionFailedAsync, expectTransactionFailedWithoutReasonAsync, + sendTransactionResult, } from '../utils/assertions'; import { increaseTimeAndMineBlockAsync } from '../utils/block_timestamp'; import { chaiSetup } from '../utils/chai_setup'; @@ -31,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); @@ -48,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, @@ -109,8 +115,8 @@ describe('AssetProxyOwner', () => { }); it('should throw if a null address is included in assetProxyContracts', async () => { const assetProxyContractAddresses = [erc20Proxy.address, constants.NULL_ADDRESS]; - return expectContractCreationFailedWithoutReason( - AssetProxyOwnerContract.deployFrom0xArtifactAsync( + return expectContractCreationFailedAsync( + (AssetProxyOwnerContract.deployFrom0xArtifactAsync( artifacts.AssetProxyOwner, provider, txDefaults, @@ -118,7 +124,8 @@ describe('AssetProxyOwner', () => { assetProxyContractAddresses, REQUIRED_APPROVALS, SECONDS_TIME_LOCKED, - ), + ) as any) as sendTransactionResult, + RevertReason.InvalidAssetProxy, ); }); }); @@ -148,25 +155,6 @@ describe('AssetProxyOwner', () => { }); }); - describe('readBytes4', () => { - it('should revert if byte array has a length < 4', async () => { - const byteArrayLessThan4Bytes = '0x010101'; - return expectContractCallFailedWithoutReasonAsync( - testAssetProxyOwner.publicReadBytes4.callAsync(byteArrayLessThan4Bytes, new BigNumber(0)), - ); - }); - it('should return the first 4 bytes of a byte array of arbitrary length', async () => { - const byteArrayLongerThan32Bytes = - '0x0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef'; - const first4Bytes = await testAssetProxyOwner.publicReadBytes4.callAsync( - byteArrayLongerThan32Bytes, - new BigNumber(0), - ); - const expectedFirst4Bytes = byteArrayLongerThan32Bytes.slice(0, 10); - expect(first4Bytes).to.equal(expectedFirst4Bytes); - }); - }); - describe('registerAssetProxy', () => { it('should throw if not called by multisig', async () => { const isRegistered = true; @@ -284,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', () => { @@ -300,8 +292,9 @@ describe('AssetProxyOwner', () => { ); const log = submitTxRes.logs[0] as LogWithDecodedArgs<AssetProxyOwnerSubmissionEventArgs>; const txId = log.args.transactionId; - return expectContractCallFailedWithoutReasonAsync( + return expectContractCallFailedAsync( testAssetProxyOwner.testValidRemoveAuthorizedAddressAtIndexTx.callAsync(txId), + RevertReason.InvalidFunctionSelector, ); }); @@ -335,8 +328,9 @@ describe('AssetProxyOwner', () => { ); const log = submitTxRes.logs[0] as LogWithDecodedArgs<AssetProxyOwnerSubmissionEventArgs>; const txId = log.args.transactionId; - return expectContractCallFailedWithoutReasonAsync( + return expectContractCallFailedAsync( testAssetProxyOwner.testValidRemoveAuthorizedAddressAtIndexTx.callAsync(txId), + RevertReason.UnregisteredAssetProxy, ); }); }); @@ -355,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, ); }); @@ -377,10 +372,11 @@ describe('AssetProxyOwner', () => { await multiSigWrapper.confirmTransactionAsync(txId, owners[1]); - return expectTransactionFailedWithoutReasonAsync( + return expectTransactionFailedAsync( testAssetProxyOwner.executeRemoveAuthorizedAddressAtIndex.sendTransactionAsync(txId, { from: owners[1], }), + RevertReason.UnregisteredAssetProxy, ); }); @@ -399,14 +395,18 @@ describe('AssetProxyOwner', () => { await multiSigWrapper.confirmTransactionAsync(txId, owners[1]); - return expectTransactionFailedWithoutReasonAsync( + return expectTransactionFailedAsync( testAssetProxyOwner.executeRemoveAuthorizedAddressAtIndex.sendTransactionAsync(txId, { from: owners[1], }), + RevertReason.InvalidFunctionSelector, ); }); - 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, @@ -429,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..1c0cb0515 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 { BigNumber } from '@0xproject/utils'; +import { BlockchainLifecycle } from '@0x/dev-utils'; +import { RevertReason } from '@0x/types'; +import { BigNumber } from '@0x/utils'; import * as chai from 'chai'; import { LogWithDecodedArgs } from 'ethereum-types'; +import * as _ from 'lodash'; +import { DummyERC20TokenContract } from '../../generated-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'; +} from '../../generated-wrappers/multi_sig_wallet_with_time_lock'; +import { artifacts } from '../../src/artifacts'; +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 txReceipt = await multiSigWrapper.confirmTransactionAsync(txId, owners[1]); + const blockNum = await web3Wrapper.getBlockNumberAsync(); + const timestamp = new BigNumber(await web3Wrapper.getBlockTimestampAsync(blockNum)); + 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, ); }); @@ -94,7 +269,10 @@ describe('MultiSigWalletWithTimeLock', () => { expect(confirmRes.logs).to.have.length(2); const blockNum = await web3Wrapper.getBlockNumberAsync(); - const blockInfo = await web3Wrapper.getBlockAsync(blockNum); + const blockInfo = await web3Wrapper.getBlockIfExistsAsync(blockNum); + if (_.isUndefined(blockInfo)) { + throw new Error(`Unexpectedly failed to fetch block at #${blockNum}`); + } const timestamp = new BigNumber(blockInfo.timestamp); const confirmationTimeBigNum = new BigNumber(await multiSig.confirmationTimes.callAsync(txId)); @@ -147,8 +325,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, ); }); |