import { artifacts as assetProxyArtifacts, ERC1155ProxyContract, ERC20ProxyContract, ERC721ProxyContract, MultiAssetProxyContract, StaticCallProxyContract, } from '@0x/contracts-asset-proxy'; import { artifacts as exchangeArtifacts, AssetProxyDispatcher, Authorizable, ExchangeAssetProxyRegisteredEventArgs, ExchangeContract, ExchangeEvents, ExchangeProtocolFeeCollectorAddressEventArgs, ExchangeProtocolFeeMultiplierEventArgs, Ownable, } from '@0x/contracts-exchange'; import { artifacts as multisigArtifacts, ZeroExGovernorContract } from '@0x/contracts-multisig'; import { artifacts as stakingArtifacts, constants as stakingConstants, ReadOnlyProxyContract, StakingContract, StakingEvents, StakingExchangeAddedEventArgs, StakingProxyContract, } from '@0x/contracts-staking'; import { blockchainTests, constants, expect, filterLogsToArguments } from '@0x/contracts-test-utils'; import { AuthorizableAuthorizedAddressAddedEventArgs, AuthorizableAuthorizedAddressRemovedEventArgs, AuthorizableEvents, } from '@0x/contracts-utils'; import { AssetProxyId } from '@0x/types'; import { BigNumber } from '@0x/utils'; import { TxData } from 'ethereum-types'; // tslint:disable:no-unnecessary-type-assertion blockchainTests('Deployment and Configuration End to End Tests', env => { // Available Addresses let owner: string; // Contract Instances let governor: ZeroExGovernorContract; 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; // TxDefaults let txDefaults: Partial; // ChainId of the Exchange let chainId: number; // Protocol Fees const protocolFeeMultiplier = new BigNumber(150000); before(async () => { // Get the chain ID. chainId = await env.getChainIdAsync(); // Create accounts and tx defaults [owner] = await env.getAccountAddressesAsync(); txDefaults = { ...env.txDefaults, from: owner, }; // Deploy ZeroExGovernor. For the purposes of this test, we will assume that // the ZeroExGovernor does not know what destinations will be needed during // construction. governor = await ZeroExGovernorContract.deployFrom0xArtifactAsync( multisigArtifacts.ZeroExGovernor, env.provider, txDefaults, multisigArtifacts, [], [], [], [owner], new BigNumber(1), constants.ZERO_AMOUNT, ); // Deploy Exchange. exchange = await ExchangeContract.deployFrom0xArtifactAsync( exchangeArtifacts.Exchange, env.provider, txDefaults, exchangeArtifacts, new BigNumber(chainId), ); // Deploy ReadOnlyProxy. readOnlyProxy = await ReadOnlyProxyContract.deployFrom0xArtifactAsync( stakingArtifacts.ReadOnlyProxy, env.provider, txDefaults, stakingArtifacts, ); // Deploy Staking. staking = await StakingContract.deployFrom0xArtifactAsync( stakingArtifacts.Staking, env.provider, txDefaults, stakingArtifacts, ); // Deploy the staking proxy. stakingProxy = await StakingProxyContract.deployFrom0xArtifactAsync( stakingArtifacts.StakingProxy, env.provider, txDefaults, stakingArtifacts, staking.address, readOnlyProxy.address, ); // Authorize owner in the staking proxy. await stakingProxy.addAuthorizedAddress.awaitTransactionSuccessAsync(owner); // 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); }); 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: AssetProxyDispatcher, assetProxyAddress: string, assetProxyId: string, ): Promise { // Register the asset proxy. const receipt = await registrationContract.registerAssetProxy.awaitTransactionSuccessAsync( assetProxyAddress, { from: owner, }, ); // Ensure that the correct event was logged. const logs = filterLogsToArguments( receipt.logs, ExchangeEvents.AssetProxyRegistered, ); expect(logs).to.be.deep.eq([{ id: assetProxyId, assetProxy: assetProxyAddress }]); // 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: Authorizable, newAuthorityAddress: string, ): Promise { // Authorize the address. const receipt = await authorizable.addAuthorizedAddress.awaitTransactionSuccessAsync( newAuthorityAddress, { from: owner }, ); // Ensure that the correct log was emitted. const logs = filterLogsToArguments( receipt.logs, AuthorizableEvents.AuthorizedAddressAdded, ); expect(logs).to.be.deep.eq([{ target: newAuthorityAddress, caller: 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, AssetProxyId.ERC20); await registerAssetProxyAndAssertSuccessAsync(exchange, erc721Proxy.address, AssetProxyId.ERC721); await registerAssetProxyAndAssertSuccessAsync(exchange, erc1155Proxy.address, AssetProxyId.ERC1155); await registerAssetProxyAndAssertSuccessAsync( exchange, multiAssetProxy.address, AssetProxyId.MultiAsset, ); await registerAssetProxyAndAssertSuccessAsync( exchange, staticCallProxy.address, AssetProxyId.StaticCall, ); }); 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, AssetProxyId.ERC20); await registerAssetProxyAndAssertSuccessAsync( multiAssetProxy, erc721Proxy.address, AssetProxyId.ERC721, ); await registerAssetProxyAndAssertSuccessAsync( multiAssetProxy, erc1155Proxy.address, AssetProxyId.ERC1155, ); await registerAssetProxyAndAssertSuccessAsync( multiAssetProxy, staticCallProxy.address, AssetProxyId.StaticCall, ); }); 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 readOnlyProxyAddress = await stakingProxy.readOnlyProxy.callAsync(); expect(readOnlyProxyAddress).to.be.eq(readOnlyProxy.address); // Ensure that the registered read-only proxy callee is correct. const readOnlyProxyCalleeAddress = await stakingProxy.readOnlyProxyCallee.callAsync(); expect(readOnlyProxyCalleeAddress).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).to.be.deep.eq([ stakingConstants.DEFAULT_PARAMS.epochDurationInSeconds, stakingConstants.DEFAULT_PARAMS.rewardDelegatedStakeWeight, stakingConstants.DEFAULT_PARAMS.minimumPoolStake, stakingConstants.DEFAULT_PARAMS.cobbDouglasAlphaNumerator, stakingConstants.DEFAULT_PARAMS.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. const logs = filterLogsToArguments( receipt.logs, StakingEvents.ExchangeAdded, ); expect(logs).to.be.deep.eq([{ exchangeAddress: 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. const logs = filterLogsToArguments( receipt.logs, ExchangeEvents.ProtocolFeeCollectorAddress, ); expect(logs).to.be.deep.eq([ { oldProtocolFeeCollector: constants.NULL_ADDRESS, updatedProtocolFeeCollector: 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. const logs = filterLogsToArguments( receipt.logs, ExchangeEvents.ProtocolFeeMultiplier, ); expect(logs).to.be.deep.eq([ { oldProtocolFeeMultiplier: constants.ZERO_AMOUNT, updatedProtocolFeeMultiplier: protocolFeeMultiplier, }, ]); // Ensure that the protocol fee multiplier was set correctly. const multiplier = await exchange.protocolFeeMultiplier.callAsync(); expect(multiplier).bignumber.to.be.eq(protocolFeeMultiplier); }); }); }); describe('transferring ownership', () => { // Removes authorization of the "externally owned address" owner and transfers the authorization // to the asset proxy owner. async function transferAuthorizationAndAssertSuccessAsync(contract: Authorizable): Promise { // Remove authorization from the old owner. let receipt = await contract.removeAuthorizedAddress.awaitTransactionSuccessAsync(owner, { from: owner }); // Ensure that the correct log was recorded. let logs = filterLogsToArguments( receipt.logs, AuthorizableEvents.AuthorizedAddressRemoved, ); expect(logs).to.be.deep.eq([{ target: owner, caller: 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(governor.address, { from: owner, }); // Ensure that the correct log was recorded. logs = filterLogsToArguments( receipt.logs, AuthorizableEvents.AuthorizedAddressAdded, ); expect(logs).to.be.deep.eq([{ target: governor.address, caller: owner }]); // Ensure that the asset-proxy owner was actually authorized. isAuthorized = await contract.authorized.callAsync(governor.address); expect(isAuthorized).to.be.true(); } // Transfers ownership of a contract to the asset-proxy owner, and ensures that the change was actually made. async function transferOwnershipAndAssertSuccessAsync(contract: Ownable): Promise { // Transfer ownership to the new owner. await contract.transferOwnership.awaitTransactionSuccessAsync(governor.address, { from: owner }); // Ensure that the owner address has been updated. const ownerAddress = await contract.owner.callAsync(); expect(ownerAddress).to.be.eq(governor.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(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