Merge development

This commit is contained in:
Leonid Logvinov
2019-01-28 17:26:13 +01:00
280 changed files with 1876 additions and 1411 deletions

View File

@@ -1,4 +1,17 @@
[
{
"version": "3.0.0",
"changes": [
{
"note": "Add AssetProxyOwner contract",
"pr": 1539
},
{
"note": "Rename multisig directory to src",
"pr": 1539
}
]
},
{
"version": "2.0.0",
"changes": [

View File

@@ -0,0 +1,17 @@
[
{
"name": "AssetProxyOwner",
"version": "1.0.0",
"changes": [
{
"note": "protocol v2 deploy",
"networks": {
"1": "0x17992e4ffb22730138e4b62aaa6367fa9d3699a6",
"3": "0xf5fa5b5fed2727a0e44ac67f6772e97977aa358b",
"4": "0x1da52d1d3a3acfa0a1836b737393b4e9931268fc",
"42": "0x2c824d2882baa668e0d5202b1e7f2922278703f8"
}
}
]
}
]

View File

@@ -1,15 +1,14 @@
## MultisSig Contracts
## MultiSignature Contracts
MultiSig smart contracts
This package contains various types of multisignature wallet contracts, including the [`AssetProxyOwner`](https://github.com/0xProject/0x-protocol-specification/blob/master/v2/v2-specification.md#assetproxyowner) contract that is responsible for upgrading the 0x protocol smart contracts. Addresses of the deployed contracts can be found in the 0x [wiki](https://0xproject.com/wiki#Deployed-Addresses) or the [DEPLOYS](./DEPLOYS.json) file within this package.
## Usage
## Installation
Contracts can be found in the [contracts](./contracts) directory. The contents of this directory are broken down into the following subdirectories:
**Install**
- [multisig](./contracts/multisig)
- This directory contains the [Gnosis MultiSigWallet](https://github.com/gnosis/MultiSigWallet) and a custom extension that adds a timelock to transactions within the MultiSigWallet.
- [test](./contracts/test)
- This directory contains mocks and other contracts that are used solely for testing contracts within the other directories.
```bash
npm install @0x/contracts-multisig --save
```
## Contributing

View File

@@ -19,5 +19,11 @@
}
}
},
"contracts": ["MultiSigWallet", "MultiSigWalletWithTimeLock", "TestRejectEther"]
"contracts": [
"AssetProxyOwner",
"MultiSigWallet",
"MultiSigWalletWithTimeLock",
"TestAssetProxyOwner",
"TestRejectEther"
]
}

View File

@@ -0,0 +1,108 @@
/*
Copyright 2018 ZeroEx Intl.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
pragma solidity 0.4.24;
import "./MultiSigWalletWithTimeLock.sol";
import "@0x/contracts-utils/contracts/src/LibBytes.sol";
contract AssetProxyOwner is
MultiSigWalletWithTimeLock
{
using LibBytes for bytes;
event AssetProxyRegistration(address assetProxyContract, bool isRegistered);
// Mapping of AssetProxy contract address =>
// if this contract is allowed to call the AssetProxy's `removeAuthorizedAddressAtIndex` method without a time lock.
mapping (address => bool) public isAssetProxyRegistered;
bytes4 constant internal REMOVE_AUTHORIZED_ADDRESS_AT_INDEX_SELECTOR = bytes4(keccak256("removeAuthorizedAddressAtIndex(address,uint256)"));
/// @dev Function will revert if the transaction does not call `removeAuthorizedAddressAtIndex`
/// on an approved AssetProxy contract.
modifier validRemoveAuthorizedAddressAtIndexTx(uint256 transactionId) {
Transaction storage txn = transactions[transactionId];
require(
isAssetProxyRegistered[txn.destination],
"UNREGISTERED_ASSET_PROXY"
);
require(
txn.data.readBytes4(0) == REMOVE_AUTHORIZED_ADDRESS_AT_INDEX_SELECTOR,
"INVALID_FUNCTION_SELECTOR"
);
_;
}
/// @dev Contract constructor sets initial owners, required number of confirmations,
/// time lock, and list of AssetProxy addresses.
/// @param _owners List of initial owners.
/// @param _assetProxyContracts Array of AssetProxy contract addresses.
/// @param _required Number of required confirmations.
/// @param _secondsTimeLocked Duration needed after a transaction is confirmed and before it becomes executable, in seconds.
constructor (
address[] memory _owners,
address[] memory _assetProxyContracts,
uint256 _required,
uint256 _secondsTimeLocked
)
public
MultiSigWalletWithTimeLock(_owners, _required, _secondsTimeLocked)
{
for (uint256 i = 0; i < _assetProxyContracts.length; i++) {
address assetProxy = _assetProxyContracts[i];
require(
assetProxy != address(0),
"INVALID_ASSET_PROXY"
);
isAssetProxyRegistered[assetProxy] = true;
}
}
/// @dev Registers or deregisters an AssetProxy to be able to execute
/// `removeAuthorizedAddressAtIndex` without a timelock.
/// @param assetProxyContract Address of AssetProxy contract.
/// @param isRegistered Status of approval for AssetProxy contract.
function registerAssetProxy(address assetProxyContract, bool isRegistered)
public
onlyWallet
notNull(assetProxyContract)
{
isAssetProxyRegistered[assetProxyContract] = isRegistered;
emit AssetProxyRegistration(assetProxyContract, isRegistered);
}
/// @dev Allows execution of `removeAuthorizedAddressAtIndex` without time lock.
/// @param transactionId Transaction ID.
function executeRemoveAuthorizedAddressAtIndex(uint256 transactionId)
public
notExecuted(transactionId)
fullyConfirmed(transactionId)
validRemoveAuthorizedAddressAtIndexTx(transactionId)
{
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;
}
}
}

View File

@@ -0,0 +1,58 @@
/*
Copyright 2018 ZeroEx Intl.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
pragma solidity 0.4.24;
import "../src/AssetProxyOwner.sol";
// solhint-disable no-empty-blocks
contract TestAssetProxyOwner is
AssetProxyOwner
{
constructor (
address[] memory _owners,
address[] memory _assetProxyContracts,
uint256 _required,
uint256 _secondsTimeLocked
)
public
AssetProxyOwner(_owners, _assetProxyContracts, _required, _secondsTimeLocked)
{}
function testValidRemoveAuthorizedAddressAtIndexTx(uint256 id)
public
view
validRemoveAuthorizedAddressAtIndexTx(id)
returns (bool)
{
// Do nothing. We expect reverts through the modifier
return true;
}
/// @dev Compares first 4 bytes of byte array to `removeAuthorizedAddressAtIndex` function selector.
/// @param data Transaction data.
/// @return Successful if data is a call to `removeAuthorizedAddressAtIndex`.
function isFunctionRemoveAuthorizedAddressAtIndex(bytes memory data)
public
pure
returns (bool)
{
return data.readBytes4(0) == REMOVE_AUTHORIZED_ADDRESS_AT_INDEX_SELECTOR;
}
}

View File

@@ -32,7 +32,7 @@
"lint-contracts": "solhint -c ../.solhint.json contracts/**/**/**/**/*.sol"
},
"config": {
"abis": "generated-artifacts/@(MultiSigWallet|MultiSigWalletWithTimeLock|TestRejectEther).json"
"abis": "generated-artifacts/@(AssetProxyOwner|MultiSigWallet|MultiSigWalletWithTimeLock|TestAssetProxyOwner|TestRejectEther).json"
},
"repository": {
"type": "git",
@@ -48,13 +48,9 @@
"@0x/contracts-test-utils": "^2.0.1",
"@0x/dev-utils": "^1.0.24",
"@0x/sol-compiler": "^2.0.2",
"@0x/subproviders": "^2.1.11",
"@0x/tslint-config": "^2.0.2",
"@types/bn.js": "^4.11.0",
"@types/ethereumjs-abi": "^0.6.0",
"@types/lodash": "4.14.104",
"@types/node": "*",
"@types/yargs": "^10.0.0",
"chai": "^4.0.1",
"chai-as-promised": "^7.1.0",
"chai-bignumber": "^3.0.0",
@@ -65,12 +61,12 @@
"shx": "^0.2.2",
"solhint": "^1.4.1",
"tslint": "5.11.0",
"typescript": "3.0.1",
"yargs": "^10.0.3"
"typescript": "3.0.1"
},
"dependencies": {
"@0x/base-contract": "^3.0.13",
"@0x/order-utils": "^3.1.2",
"@0x/contracts-asset-proxy": "1.0.0",
"@0x/contracts-erc20": "1.0.0",
"@0x/types": "^1.5.2",
"@0x/typescript-typings": "^3.0.8",
"@0x/utils": "^3.0.1",

View File

@@ -1,11 +1,15 @@
import { ContractArtifact } from 'ethereum-types';
import * as AssetProxyOwner from '../../generated-artifacts/AssetProxyOwner.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';
import * as TestRejectEther from '../../generated-artifacts/TestRejectEther.json';
export const artifacts = {
TestRejectEther: TestRejectEther as ContractArtifact,
AssetProxyOwner: AssetProxyOwner as ContractArtifact,
MultiSigWallet: MultiSigWallet as ContractArtifact,
MultiSigWalletWithTimeLock: MultiSigWalletWithTimeLock as ContractArtifact,
TestAssetProxyOwner: TestAssetProxyOwner as ContractArtifact,
TestRejectEther: TestRejectEther as ContractArtifact,
};

View File

@@ -1,2 +1,3 @@
export * from './artifacts';
export * from './wrappers';
export * from '../test/utils';

View File

@@ -1,2 +1,5 @@
export * from '../../generated-wrappers/asset_proxy_owner';
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';
export * from '../../generated-wrappers/test_reject_ether';

View File

@@ -0,0 +1,506 @@
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 { RevertReason } from '@0x/types';
import { BigNumber } from '@0x/utils';
import * as chai from 'chai';
import { LogWithDecodedArgs } from 'ethereum-types';
import {
artifacts,
AssetProxyOwnerAssetProxyRegistrationEventArgs,
AssetProxyOwnerContract,
AssetProxyOwnerExecutionEventArgs,
AssetProxyOwnerExecutionFailureEventArgs,
AssetProxyOwnerSubmissionEventArgs,
AssetProxyOwnerWrapper,
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;
let assetProxyOwnerWrapper: AssetProxyOwnerWrapper;
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,
);
erc721Proxy = await MixinAuthorizableContract.deployFrom0xArtifactAsync(
proxyArtifacts.MixinAuthorizable,
provider,
txDefaults,
);
const defaultAssetProxyContractAddresses: string[] = [];
testAssetProxyOwner = await TestAssetProxyOwnerContract.deployFrom0xArtifactAsync(
artifacts.TestAssetProxyOwner,
provider,
txDefaults,
owners,
defaultAssetProxyContractAddresses,
REQUIRED_APPROVALS,
SECONDS_TIME_LOCKED,
);
assetProxyOwnerWrapper = new AssetProxyOwnerWrapper(testAssetProxyOwner, provider);
await web3Wrapper.awaitTransactionSuccessAsync(
await erc20Proxy.transferOwnership.sendTransactionAsync(testAssetProxyOwner.address, {
from: initialOwner,
}),
constants.AWAIT_TRANSACTION_MINED_MS,
);
await web3Wrapper.awaitTransactionSuccessAsync(
await erc721Proxy.transferOwnership.sendTransactionAsync(testAssetProxyOwner.address, {
from: initialOwner,
}),
constants.AWAIT_TRANSACTION_MINED_MS,
);
});
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,
owners,
assetProxyContractAddresses,
REQUIRED_APPROVALS,
SECONDS_TIME_LOCKED,
);
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);
});
it('should throw if a null address is included in assetProxyContracts', async () => {
const assetProxyContractAddresses = [erc20Proxy.address, constants.NULL_ADDRESS];
return expectContractCreationFailedAsync(
(AssetProxyOwnerContract.deployFrom0xArtifactAsync(
artifacts.AssetProxyOwner,
provider,
txDefaults,
owners,
assetProxyContractAddresses,
REQUIRED_APPROVALS,
SECONDS_TIME_LOCKED,
) as any) as sendTransactionResult,
RevertReason.InvalidAssetProxy,
);
});
});
describe('isFunctionRemoveAuthorizedAddressAtIndex', () => {
it('should return false if data is not for removeAuthorizedAddressAtIndex', async () => {
const notRemoveAuthorizedAddressData = erc20Proxy.addAuthorizedAddress.getABIEncodedTransactionData(
owners[0],
);
const isFunctionRemoveAuthorizedAddressAtIndex = await testAssetProxyOwner.isFunctionRemoveAuthorizedAddressAtIndex.callAsync(
notRemoveAuthorizedAddressData,
);
expect(isFunctionRemoveAuthorizedAddressAtIndex).to.be.false();
});
it('should return true if data is for removeAuthorizedAddressAtIndex', async () => {
const index = new BigNumber(0);
const removeAuthorizedAddressAtIndexData = erc20Proxy.removeAuthorizedAddressAtIndex.getABIEncodedTransactionData(
owners[0],
index,
);
const isFunctionRemoveAuthorizedAddressAtIndex = await testAssetProxyOwner.isFunctionRemoveAuthorizedAddressAtIndex.callAsync(
removeAuthorizedAddressAtIndexData,
);
expect(isFunctionRemoveAuthorizedAddressAtIndex).to.be.true();
});
});
describe('registerAssetProxy', () => {
it('should throw if not called by multisig', async () => {
const isRegistered = true;
return expectTransactionFailedWithoutReasonAsync(
testAssetProxyOwner.registerAssetProxy.sendTransactionAsync(erc20Proxy.address, isRegistered, {
from: owners[0],
}),
);
});
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,
);
const submitTxRes = await assetProxyOwnerWrapper.submitTransactionAsync(
testAssetProxyOwner.address,
registerAssetProxyData,
owners[0],
);
const log = submitTxRes.logs[0] as LogWithDecodedArgs<AssetProxyOwnerSubmissionEventArgs>;
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);
});
it('should fail if registering a null address', async () => {
const addressToRegister = constants.NULL_ADDRESS;
const isRegistered = true;
const registerAssetProxyData = testAssetProxyOwner.registerAssetProxy.getABIEncodedTransactionData(
addressToRegister,
isRegistered,
);
const submitTxRes = await assetProxyOwnerWrapper.submitTransactionAsync(
testAssetProxyOwner.address,
registerAssetProxyData,
owners[0],
);
const log = submitTxRes.logs[0] as LogWithDecodedArgs<AssetProxyOwnerSubmissionEventArgs>;
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<AssetProxyOwnerExecutionFailureEventArgs>;
expect(failureLog.args.transactionId).to.be.bignumber.equal(txId);
const isAssetProxyRegistered = await testAssetProxyOwner.isAssetProxyRegistered.callAsync(
addressToRegister,
);
expect(isAssetProxyRegistered).to.equal(false);
});
});
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<AssetProxyOwnerSubmissionEventArgs>;
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,
});
await assetProxyOwnerWrapper.executeTransactionAsync(erc721AddAuthorizedAddressTxId, owners[0], {
gas: constants.MAX_EXECUTE_TRANSACTION_GAS,
});
});
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<AssetProxyOwnerSubmissionEventArgs>;
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<AssetProxyOwnerSubmissionEventArgs>;
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<AssetProxyOwnerSubmissionEventArgs>;
const txId = log.args.transactionId;
return expectContractCallFailedAsync(
testAssetProxyOwner.testValidRemoveAuthorizedAddressAtIndexTx.callAsync(txId),
RevertReason.UnregisteredAssetProxy,
);
});
});
describe('executeRemoveAuthorizedAddressAtIndex', () => {
it('should throw 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<AssetProxyOwnerSubmissionEventArgs>;
const txId = log.args.transactionId;
return expectTransactionFailedAsync(
testAssetProxyOwner.executeRemoveAuthorizedAddressAtIndex.sendTransactionAsync(txId, {
from: owners[1],
}),
RevertReason.TxNotFullyConfirmed,
);
});
it('should throw 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<AssetProxyOwnerSubmissionEventArgs>;
const txId = log.args.transactionId;
await assetProxyOwnerWrapper.confirmTransactionAsync(txId, owners[1]);
return expectTransactionFailedAsync(
testAssetProxyOwner.executeRemoveAuthorizedAddressAtIndex.sendTransactionAsync(txId, {
from: owners[1],
}),
RevertReason.UnregisteredAssetProxy,
);
});
it('should throw 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<AssetProxyOwnerSubmissionEventArgs>;
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<AssetProxyOwnerSubmissionEventArgs>;
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<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 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<AssetProxyOwnerSubmissionEventArgs>;
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<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 () => {
const removeAuthorizedAddressAtIndexData = erc20Proxy.removeAuthorizedAddressAtIndex.getABIEncodedTransactionData(
authorized,
erc20Index,
);
const submitRes = await assetProxyOwnerWrapper.submitTransactionAsync(
erc20Proxy.address,
removeAuthorizedAddressAtIndexData,
owners[0],
);
const submitLog = submitRes.logs[0] as LogWithDecodedArgs<AssetProxyOwnerSubmissionEventArgs>;
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<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);
return expectTransactionFailedWithoutReasonAsync(
testAssetProxyOwner.executeRemoveAuthorizedAddressAtIndex.sendTransactionAsync(txId, {
from: owners[1],
}),
);
});
});
});
});
// tslint:disable-line max-file-line-count

View File

@@ -16,17 +16,16 @@ import { LogWithDecodedArgs } from 'ethereum-types';
import * as _ from 'lodash';
import {
artifacts,
MultiSigWalletWithTimeLockConfirmationEventArgs,
MultiSigWalletWithTimeLockConfirmationTimeSetEventArgs,
MultiSigWalletWithTimeLockContract,
MultiSigWalletWithTimeLockExecutionEventArgs,
MultiSigWalletWithTimeLockExecutionFailureEventArgs,
MultiSigWalletWithTimeLockSubmissionEventArgs,
} from '../generated-wrappers/multi_sig_wallet_with_time_lock';
import { TestRejectEtherContract } from '../generated-wrappers/test_reject_ether';
import { artifacts } from '../src/artifacts';
import { MultiSigWrapper } from './utils/multi_sig_wrapper';
MultiSigWrapper,
TestRejectEtherContract,
} from '../src';
chaiSetup.configure();
const expect = chai.expect;

View File

@@ -0,0 +1,71 @@
import { artifacts as proxyArtifacts } from '@0x/contracts-asset-proxy';
import { artifacts as erc20Artifacts } from '@0x/contracts-erc20';
import { LogDecoder } from '@0x/contracts-test-utils';
import { BigNumber } from '@0x/utils';
import { Web3Wrapper } from '@0x/web3-wrapper';
import { Provider, TransactionReceiptWithDecodedLogs } from 'ethereum-types';
import * as _ from 'lodash';
import { AssetProxyOwnerContract } from '../../generated-wrappers/asset_proxy_owner';
import { artifacts } from '../../src/artifacts';
export class AssetProxyOwnerWrapper {
private readonly _assetProxyOwner: AssetProxyOwnerContract;
private readonly _web3Wrapper: Web3Wrapper;
private readonly _logDecoder: LogDecoder;
constructor(assetproxyOwnerContract: AssetProxyOwnerContract, provider: Provider) {
this._assetProxyOwner = assetproxyOwnerContract;
this._web3Wrapper = new Web3Wrapper(provider);
this._logDecoder = new LogDecoder(this._web3Wrapper, { ...artifacts, ...erc20Artifacts, ...proxyArtifacts });
}
public async submitTransactionAsync(
destination: string,
data: string,
from: string,
opts: { value?: BigNumber } = {},
): Promise<TransactionReceiptWithDecodedLogs> {
const value = _.isUndefined(opts.value) ? 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<TransactionReceiptWithDecodedLogs> {
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<TransactionReceiptWithDecodedLogs> {
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<TransactionReceiptWithDecodedLogs> {
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<TransactionReceiptWithDecodedLogs> {
// tslint:disable-next-line:no-unnecessary-type-assertion
const txHash = await (this
._assetProxyOwner as AssetProxyOwnerContract).executeRemoveAuthorizedAddressAtIndex.sendTransactionAsync(
txId,
{
from,
},
);
const tx = await this._logDecoder.getTxWithDecodedLogsAsync(txHash);
return tx;
}
}

View File

@@ -0,0 +1,2 @@
export * from './asset_proxy_owner_wrapper';
export * from './multi_sig_wrapper';

View File

@@ -7,8 +7,10 @@
},
"include": ["./src/**/*", "./test/**/*", "./generated-wrappers/**/*"],
"files": [
"./generated-artifacts/AssetProxyOwner.json",
"./generated-artifacts/MultiSigWallet.json",
"./generated-artifacts/MultiSigWalletWithTimeLock.json",
"./generated-artifacts/TestAssetProxyOwner.json",
"./generated-artifacts/TestRejectEther.json"
],
"exclude": ["./deploy/solc/solc_bin"]