@0x:contracts-staking Added a full end-to-end test of a deployment of the exchange and staking system

This commit is contained in:
Alex Towle
2019-09-26 18:00:25 -07:00
parent 3fa922dbab
commit 9282684d93
4 changed files with 352 additions and 31 deletions

View File

@@ -51,6 +51,8 @@
"devDependencies": {
"@0x/abi-gen": "^4.1.0",
"@0x/contracts-gen": "^1.0.13",
"@0x/contracts-exchange": "^2.1.14",
"@0x/contracts-multisig": "^3.1.14",
"@0x/contracts-test-utils": "^3.1.2",
"@0x/dev-utils": "^2.2.1",
"@0x/sol-compiler": "^3.1.6",

View File

@@ -1,21 +1,50 @@
import { artifacts as exchangeArtifacts, ExchangeContract } from '@0x/contracts-exchange';
import {
artifacts as assetProxyArtifacts,
ERC1155ProxyContract,
ERC20ProxyContract,
ERC721ProxyContract,
MultiAssetProxyContract,
StaticCallProxyContract,
} from '@0x/contracts-asset-proxy';
import {
artifacts as exchangeArtifacts,
ExchangeContract,
MixinAssetProxyDispatcherAssetProxyRegisteredEventArgs,
MixinProtocolFeesProtocolFeeCollectorAddressEventArgs,
MixinProtocolFeesProtocolFeeMultiplierEventArgs,
} from '@0x/contracts-exchange';
import { artifacts as multisigArtifacts, AssetProxyOwnerContract } from '@0x/contracts-multisig';
import { blockchainTests, constants, expect } from '@0x/contracts-test-utils';
import {
AuthorizableAuthorizedAddressAddedEventArgs,
AuthorizableAuthorizedAddressRemovedEventArgs,
} from '@0x/contracts-utils';
import { BigNumber } from '@0x/utils';
import { TxData } from 'ethereum-types';
import { LogWithDecodedArgs, TxData } from 'ethereum-types';
import { artifacts as stakingArtifacts, ReadOnlyProxyContract, StakingContract, StakingProxyContract } from '../../src';
import {
artifacts as stakingArtifacts,
MixinExchangeManagerExchangeAddedEventArgs,
ReadOnlyProxyContract,
StakingContract,
StakingProxyContract,
} from '../../src';
blockchainTests.only('Deployment and Configuration End to End Tests', env => {
// tslint:disable:no-unnecessary-type-assertion
blockchainTests('Deployment and Configuration End to End Tests', env => {
// Available Addresses
let nonOwner: string;
let owner: string;
// Contract Instances
let assetProxyOwner: AssetProxyOwnerContract;
let erc20Proxy: ERC20ProxyContract;
let erc721Proxy: ERC721ProxyContract;
let erc1155Proxy: ERC1155ProxyContract;
let exchange: ExchangeContract;
let multiAssetProxy: MultiAssetProxyContract;
let readOnlyProxy: ReadOnlyProxyContract;
let staking: StakingContract;
let staticCallProxy: StaticCallProxyContract;
let stakingProxy: StakingProxyContract;
let stakingWrapper: StakingContract;
@@ -23,14 +52,20 @@ blockchainTests.only('Deployment and Configuration End to End Tests', env => {
let txDefaults: Partial<TxData>;
// ChainId of the Exchange
const chainId = new BigNumber(1);
let chainId: number;
// Protocol Fees
const protocolFeeMultiplier = new BigNumber(150000);
before(async () => {
[nonOwner, owner] = await env.getAccountAddressesAsync();
// Get the chain ID.
chainId = await env.getChainIdAsync();
// Create accounts and tx defaults
[owner] = await env.getAccountAddressesAsync();
txDefaults = {
from: owner,
...env.txDefaults,
from: owner,
};
// Deploy AssetProxyOwner. For the purposes of this test, we will assume that
@@ -55,7 +90,7 @@ blockchainTests.only('Deployment and Configuration End to End Tests', env => {
env.provider,
txDefaults,
exchangeArtifacts,
chainId,
new BigNumber(chainId),
);
// Deploy ReadOnlyProxy.
@@ -74,7 +109,7 @@ blockchainTests.only('Deployment and Configuration End to End Tests', env => {
stakingArtifacts,
);
// Deploy Staking.
// Deploy the staking proxy.
stakingProxy = await StakingProxyContract.deployFrom0xArtifactAsync(
stakingArtifacts.StakingProxy,
env.provider,
@@ -84,34 +119,313 @@ blockchainTests.only('Deployment and Configuration End to End Tests', env => {
readOnlyProxy.address,
);
// Deploy the asset proxy contracts.
erc20Proxy = await ERC20ProxyContract.deployFrom0xArtifactAsync(
assetProxyArtifacts.ERC20Proxy,
env.provider,
txDefaults,
assetProxyArtifacts,
);
erc721Proxy = await ERC721ProxyContract.deployFrom0xArtifactAsync(
assetProxyArtifacts.ERC721Proxy,
env.provider,
txDefaults,
assetProxyArtifacts,
);
erc1155Proxy = await ERC1155ProxyContract.deployFrom0xArtifactAsync(
assetProxyArtifacts.ERC1155Proxy,
env.provider,
txDefaults,
assetProxyArtifacts,
);
multiAssetProxy = await MultiAssetProxyContract.deployFrom0xArtifactAsync(
assetProxyArtifacts.MultiAssetProxy,
env.provider,
txDefaults,
assetProxyArtifacts,
);
staticCallProxy = await StaticCallProxyContract.deployFrom0xArtifactAsync(
assetProxyArtifacts.StaticCallProxy,
env.provider,
txDefaults,
assetProxyArtifacts,
);
// Set up the staking wrapper so that the entire staking interface can be accessed
// easily through the proxy.
stakingWrapper = new StakingContract(stakingProxy.address, env.provider);
});
it('should have properly configured the staking proxy', async () => {
// Ensure that the registered read-only proxy is correct.
const readOnlyProxyAddres = await stakingProxy.readOnlyProxy.callAsync();
expect(readOnlyProxyAddres).to.be.eq(readOnlyProxy.address);
describe('deployment and configuration', () => {
describe('exchange specific', () => {
// Registers an asset proxy in the exchange contract and ensure that the correct state changes occurred.
async function registerAssetProxyAndAssertSuccessAsync(
registrationContract: any, // TODO(jalextowle): Express this in a type-safe way (during a debt-demolition)
assetProxyAddress: string,
assetProxyId: string,
): Promise<void> {
// Register the asset proxy.
const receipt = await registrationContract.registerAssetProxy.awaitTransactionSuccessAsync(
assetProxyAddress,
{
from: owner,
},
);
// Ensure that the registered read-only proxy callee is correct.
const readOnlyProxyCalleeAddres = await stakingProxy.readOnlyProxyCallee.callAsync();
expect(readOnlyProxyCalleeAddres).to.be.eq(staking.address);
// Ensure that the correct event was logged.
expect(receipt.logs.length).to.be.eq(1);
const log = receipt.logs[0] as LogWithDecodedArgs<
MixinAssetProxyDispatcherAssetProxyRegisteredEventArgs
>;
expect(log.event).to.be.eq('AssetProxyRegistered');
expect(log.args.id).to.be.eq(assetProxyId);
expect(log.args.assetProxy).to.be.eq(assetProxyAddress);
// Ensure that the registered staking contract is correct.
const stakingAddress = await stakingProxy.stakingContract.callAsync();
expect(stakingAddress).to.be.eq(staking.address);
// Ensure that the asset proxy was actually registered.
const proxyAddress = await registrationContract.getAssetProxy.callAsync(assetProxyId);
expect(proxyAddress).to.be.eq(assetProxyAddress);
}
// Authorizes an address for a given asset proxy using the owner address.
async function authorizeAddressAndAssertSuccessAsync(
authorizable: any, // TODO(jalextowle): Express this in a type-safe way (during a debt-demolition)
newAuthorityAddress: string,
): Promise<void> {
// Authorize the address.
const receipt = await authorizable.addAuthorizedAddress.awaitTransactionSuccessAsync(
newAuthorityAddress,
{ from: owner },
);
// Ensure that the correct log was emitted.
expect(receipt.logs.length).to.be.eq(1);
const log = receipt.logs[0] as LogWithDecodedArgs<AuthorizableAuthorizedAddressAddedEventArgs>;
expect(log.event).to.be.eq('AuthorizedAddressAdded');
expect(log.args.target).to.be.eq(newAuthorityAddress);
expect(log.args.caller).to.be.eq(owner);
// Ensure that the address was actually authorized.
const wasAuthorized = await authorizable.authorized.callAsync(newAuthorityAddress);
expect(wasAuthorized).to.be.true();
}
it('should successfully register the asset proxies in the exchange', async () => {
// Register the asset proxies in the exchange.
await registerAssetProxyAndAssertSuccessAsync(exchange, erc20Proxy.address, constants.ERC20_PROXY_ID);
await registerAssetProxyAndAssertSuccessAsync(exchange, erc721Proxy.address, constants.ERC721_PROXY_ID);
await registerAssetProxyAndAssertSuccessAsync(
exchange,
erc1155Proxy.address,
constants.ERC1155_PROXY_ID,
);
await registerAssetProxyAndAssertSuccessAsync(
exchange,
multiAssetProxy.address,
constants.MULTI_ASSET_PROXY_ID,
);
await registerAssetProxyAndAssertSuccessAsync(
exchange,
staticCallProxy.address,
constants.STATIC_CALL_PROXY_ID,
);
});
it('should successfully register the asset proxies in the multi-asset proxy', async () => {
// Register the asset proxies in the multi-asset proxy.
await registerAssetProxyAndAssertSuccessAsync(
multiAssetProxy,
erc20Proxy.address,
constants.ERC20_PROXY_ID,
);
await registerAssetProxyAndAssertSuccessAsync(
multiAssetProxy,
erc721Proxy.address,
constants.ERC721_PROXY_ID,
);
await registerAssetProxyAndAssertSuccessAsync(
multiAssetProxy,
erc1155Proxy.address,
constants.ERC1155_PROXY_ID,
);
await registerAssetProxyAndAssertSuccessAsync(
multiAssetProxy,
staticCallProxy.address,
constants.STATIC_CALL_PROXY_ID,
);
});
it('should successfully add the exchange as an authority in the appropriate asset proxies', async () => {
// Authorize the exchange in all of the asset proxies, except for the static call proxy.
await authorizeAddressAndAssertSuccessAsync(erc20Proxy, exchange.address);
await authorizeAddressAndAssertSuccessAsync(erc721Proxy, exchange.address);
await authorizeAddressAndAssertSuccessAsync(erc1155Proxy, exchange.address);
await authorizeAddressAndAssertSuccessAsync(multiAssetProxy, exchange.address);
});
it('should successfully add the multi asset proxy as an authority in the appropriate asset proxies', async () => {
// Authorize the multi-asset proxy in the token asset proxies.
await authorizeAddressAndAssertSuccessAsync(erc20Proxy, multiAssetProxy.address);
await authorizeAddressAndAssertSuccessAsync(erc721Proxy, multiAssetProxy.address);
await authorizeAddressAndAssertSuccessAsync(erc1155Proxy, multiAssetProxy.address);
});
});
describe('staking specific', () => {
it('should have properly configured the staking proxy with the logic contract and read-only proxy', async () => {
// Ensure that the registered read-only proxy is correct.
const readOnlyProxyAddres = await stakingProxy.readOnlyProxy.callAsync();
expect(readOnlyProxyAddres).to.be.eq(readOnlyProxy.address);
// Ensure that the registered read-only proxy callee is correct.
const readOnlyProxyCalleeAddres = await stakingProxy.readOnlyProxyCallee.callAsync();
expect(readOnlyProxyCalleeAddres).to.be.eq(staking.address);
// Ensure that the registered staking contract is correct.
const stakingAddress = await stakingProxy.stakingContract.callAsync();
expect(stakingAddress).to.be.eq(staking.address);
});
it('should have initialized the correct parameters in the staking proxy', async () => {
// Ensure that the correct parameters were set.
const params = await stakingWrapper.getParams.callAsync();
expect(params.length).to.be.eq(6);
expect(params[0]).bignumber.to.be.eq(new BigNumber(864000)); // epochDurationInSeconds
expect(params[1]).bignumber.to.be.eq(new BigNumber(900000)); // rewardDelegatedStakeWeight
expect(params[2]).bignumber.to.be.eq(new BigNumber(100000000000000000000)); // minimumPoolStake
expect(params[3]).bignumber.to.be.eq(10); // maximumMakerInPool
expect(params[4]).bignumber.to.be.eq(1); // cobbDouglasAlphaNumerator
expect(params[5]).bignumber.to.be.eq(2); // cobbDouglasAlphaDenominator
});
});
describe('exchange and staking integration', () => {
it('should successfully register the exchange in the staking contract', async () => {
// Register the exchange.
const receipt = await stakingWrapper.addExchangeAddress.awaitTransactionSuccessAsync(exchange.address, {
from: owner,
});
// Ensure that the correct events were logged.
expect(receipt.logs.length).to.be.eq(1);
const log = receipt.logs[0] as LogWithDecodedArgs<MixinExchangeManagerExchangeAddedEventArgs>;
expect(log.event).to.be.eq('ExchangeAdded');
expect(log.args.exchangeAddress).to.be.eq(exchange.address);
// Ensure that the exchange was registered.
const wasRegistered = await stakingWrapper.validExchanges.callAsync(exchange.address);
expect(wasRegistered).to.be.true();
});
it('should successfully register the staking contract in the exchange', async () => {
// Register the staking contract.
const receipt = await exchange.setProtocolFeeCollectorAddress.awaitTransactionSuccessAsync(
stakingProxy.address,
{
from: owner,
},
);
// Ensure that the correct events were logged.
expect(receipt.logs.length).to.be.eq(1);
const log = receipt.logs[0] as LogWithDecodedArgs<
MixinProtocolFeesProtocolFeeCollectorAddressEventArgs
>;
expect(log.event).to.be.eq('ProtocolFeeCollectorAddress');
expect(log.args.oldProtocolFeeCollector).bignumber.to.be.eq(constants.NULL_ADDRESS);
expect(log.args.updatedProtocolFeeCollector).bignumber.to.be.eq(stakingProxy.address);
// Ensure that the staking contract was registered.
const feeCollector = await exchange.protocolFeeCollector.callAsync();
expect(feeCollector).to.be.eq(stakingProxy.address);
});
it('should successfully update the protocol fee multiplier in the staking contract', async () => {
// Update the protocol fee multiplier.
const receipt = await exchange.setProtocolFeeMultiplier.awaitTransactionSuccessAsync(
protocolFeeMultiplier,
);
// Ensure that the correct events were logged.
expect(receipt.logs.length).to.be.eq(1);
const log = receipt.logs[0] as LogWithDecodedArgs<MixinProtocolFeesProtocolFeeMultiplierEventArgs>;
expect(log.event).to.be.eq('ProtocolFeeMultiplier');
expect(log.args.oldProtocolFeeMultiplier).bignumber.to.be.eq(constants.ZERO_AMOUNT);
expect(log.args.updatedProtocolFeeMultiplier).bignumber.to.be.eq(protocolFeeMultiplier);
// Ensure that the protocol fee multiplier was set correctly.
const multiplier = await exchange.protocolFeeMultiplier.callAsync();
expect(multiplier).bignumber.to.be.eq(protocolFeeMultiplier);
});
});
});
it('should have initialized the correct parameters', async () => {
// Ensure that the correct parameters were set.
const params = await stakingWrapper.getParams.callAsync();
expect(params.length).to.be.eq(6);
expect(params[0]).bignumber.to.be.eq(new BigNumber(864000)); // epochDurationInSeconds
expect(params[1]).bignumber.to.be.eq(new BigNumber(900000)); // rewardDelegatedStakeWeight
expect(params[2]).bignumber.to.be.eq(new BigNumber(100000000000000000000)); // minimumPoolStake
expect(params[3]).bignumber.to.be.eq(10); // maximumMakerInPool
expect(params[4]).bignumber.to.be.eq(1); // cobbDouglasAlphaNumerator
expect(params[5]).bignumber.to.be.eq(2); // cobbDouglasAlphaDenominator
describe('transferring ownership', () => {
// TODO(jalextowle): Express this in a type-safe way (during a debt-demolition)
// Removes authorization of the "externally owned address" owner and transfers the authorization
// to the asset proxy owner.
async function transferAuthorizationAndAssertSuccessAsync(contract: any): Promise<void> {
// Remove authorization from the old owner.
let receipt = await contract.removeAuthorizedAddress.awaitTransactionSuccessAsync(owner, { from: owner });
// Ensure that the correct log was recorded.
expect(receipt.logs.length).to.be.eq(1);
let log = receipt.logs[0] as LogWithDecodedArgs<AuthorizableAuthorizedAddressRemovedEventArgs>;
expect(log.event).to.be.eq('AuthorizedAddressRemoved');
expect(log.args.target).to.be.eq(owner);
expect(log.args.caller).to.be.eq(owner);
// Ensure that the owner was actually removed.
let isAuthorized = await contract.authorized.callAsync(owner);
expect(isAuthorized).to.be.false();
// Authorize the asset-proxy owner.
receipt = await contract.addAuthorizedAddress.awaitTransactionSuccessAsync(assetProxyOwner.address, {
from: owner,
});
// Ensure that the correct log was recorded.
expect(receipt.logs.length).to.be.eq(1);
log = receipt.logs[0] as LogWithDecodedArgs<AuthorizableAuthorizedAddressRemovedEventArgs>;
expect(log.event).to.be.eq('AuthorizedAddressAdded');
expect(log.args.target).to.be.eq(assetProxyOwner.address);
expect(log.args.caller).to.be.eq(owner);
// Ensure that the asset-proxy owner was actually authorized.
isAuthorized = await contract.authorized.callAsync(assetProxyOwner.address);
expect(isAuthorized).to.be.true();
}
// TODO(jalextowle): Express this in a type-safe way (during a debt-demolition)
// Transfers ownership of a contract to the asset-proxy owner, and ensures that the change was actually made.
async function transferOwnershipAndAssertSuccessAsync(contract: any): Promise<void> {
// Transfer ownership to the new owner.
await contract.transferOwnership.awaitTransactionSuccessAsync(assetProxyOwner.address, { from: owner });
// Ensure that the owner address has been updated.
const ownerAddress = await contract.owner.callAsync();
expect(ownerAddress).to.be.eq(assetProxyOwner.address);
}
it('should transfer authorization of the owner to the asset-proxy owner in the staking contracts', async () => {
// Transfer authorization of the staking system. We intentionally neglect
// to add the asset-proxy owner as an authorized address in the asset proxies
// as a security precaution.
await transferAuthorizationAndAssertSuccessAsync(readOnlyProxy);
await transferAuthorizationAndAssertSuccessAsync(staking);
await transferAuthorizationAndAssertSuccessAsync(stakingProxy);
});
it('should transfer ownership of all appropriate contracts to the asset-proxy owner', async () => {
// Transfer ownership of most contracts (we exclude contracts that are not ownable).
await transferOwnershipAndAssertSuccessAsync(exchange);
await transferOwnershipAndAssertSuccessAsync(readOnlyProxy);
await transferOwnershipAndAssertSuccessAsync(staking);
await transferOwnershipAndAssertSuccessAsync(stakingProxy);
await transferOwnershipAndAssertSuccessAsync(erc20Proxy);
await transferOwnershipAndAssertSuccessAsync(erc721Proxy);
await transferOwnershipAndAssertSuccessAsync(erc1155Proxy);
await transferOwnershipAndAssertSuccessAsync(multiAssetProxy);
});
});
});
// tslint:enable:no-unnecessary-type-assertion

View File

@@ -84,4 +84,9 @@ export const constants = {
PPM_DENOMINATOR: 1e6,
PPM_100_PERCENT: 1e6,
MAX_CODE_SIZE: 24576,
ERC20_PROXY_ID: '0xf47261b0',
ERC721_PROXY_ID: '0x02571792',
ERC1155_PROXY_ID: '0xa7cb5fb7',
MULTI_ASSET_PROXY_ID: '0x94cfcdd7',
STATIC_CALL_PROXY_ID: '0xc339d10a',
};

View File

@@ -151,6 +151,6 @@ contract Authorizable is
delete authorized[target];
authorities[index] = authorities[authorities.length - 1];
authorities.length -= 1;
emit AuthorizedAddressRemoved(target, msg.sender);
emit AuthorizedAddressRemoved(target, msg.sender);
}
}