diff --git a/contracts/erc20/contracts/src/ERC20Token.sol b/contracts/erc20/contracts/src/ERC20Token.sol index 4c4737d864..313bf1784d 100644 --- a/contracts/erc20/contracts/src/ERC20Token.sol +++ b/contracts/erc20/contracts/src/ERC20Token.sol @@ -87,13 +87,13 @@ contract ERC20Token is balances[_to] += _value; balances[_from] -= _value; allowed[_from][msg.sender] -= _value; - + emit Transfer( _from, _to, _value ); - + return true; } diff --git a/contracts/integrations/src/index.ts b/contracts/integrations/src/index.ts index 97179831da..be79d53fa1 100644 --- a/contracts/integrations/src/index.ts +++ b/contracts/integrations/src/index.ts @@ -2,3 +2,4 @@ export * from './artifacts'; export * from './wrappers'; export * from '../test/utils/function_assertions'; export * from '../test/utils/deployment_manager'; +export * from '../test/utils/address_manager'; diff --git a/contracts/integrations/test/0x-integration-tests/exchange_integration_test.ts b/contracts/integrations/test/0x-integration-tests/exchange_integration_test.ts new file mode 100644 index 0000000000..e279bba3df --- /dev/null +++ b/contracts/integrations/test/0x-integration-tests/exchange_integration_test.ts @@ -0,0 +1,138 @@ +import { blockchainTests, constants, expect, filterLogsToArguments, OrderFactory } from '@0x/contracts-test-utils'; +import { DummyERC20TokenContract, IERC20TokenEvents, IERC20TokenTransferEventArgs } from '@0x/contracts-erc20'; +import { IExchangeEvents, IExchangeFillEventArgs } from '@0x/contracts-exchange'; +import { IStakingEventsEvents, IStakingEventsStakingPoolActivatedEventArgs } from '@0x/contracts-staking'; +import { assetDataUtils, orderHashUtils } from '@0x/order-utils'; +import { BigNumber } from '@0x/utils'; + +import { DeploymentManager, AddressManager } from '../../src'; + +blockchainTests('Exchange & Staking', async env => { + let accounts: string[]; + let makerAddress: string; + let takers: string[]; + let delegators: string[]; + let feeRecipientAddress: string; + let addressManager: AddressManager; + let deploymentManager: DeploymentManager; + let orderFactory: OrderFactory; + let makerAsset: DummyERC20TokenContract; + let takerAsset: DummyERC20TokenContract; + let feeAsset: DummyERC20TokenContract; + + const gasPrice = 1e9; + + before(async () => { + const chainId = await env.getChainIdAsync(); + accounts = await env.getAccountAddressesAsync(); + makerAddress = accounts[1]; + feeRecipientAddress = accounts[2]; + takers = [accounts[3], accounts[4]]; + delegators = [accounts[5], accounts[6], accounts[7]]; + deploymentManager = await DeploymentManager.deployAsync(env); + + // Create a staking pool with the operator as a maker address. + await deploymentManager.staking.stakingWrapper.createStakingPool.awaitTransactionSuccessAsync( + constants.ZERO_AMOUNT, + true, + { from: makerAddress }, + ); + + // Set up an address for market making. + addressManager = new AddressManager(); + await addressManager.addMakerAsync( + deploymentManager, + { + address: makerAddress, + mainToken: deploymentManager.tokens.erc20[0], + feeToken: deploymentManager.tokens.erc20[2], + }, + env, + deploymentManager.tokens.erc20[1], + feeRecipientAddress, + chainId, + ); + + // Set up two addresses for taking orders. + await addressManager.addTakersAsync( + deploymentManager, + takers.map(takerAddress => { + return { + address: takerAddress, + mainToken: deploymentManager.tokens.erc20[1], + feeToken: deploymentManager.tokens.erc20[2], + }; + }), + ); + }); + + describe('fillOrder', () => { + it('should be able to fill an order', async () => { + const order = await addressManager.makerAddresses[0].orderFactory.newSignedOrderAsync({ + makerAddress, + makerAssetAmount: new BigNumber(1), + takerAssetAmount: new BigNumber(1), + makerFee: constants.ZERO_AMOUNT, + takerFee: constants.ZERO_AMOUNT, + feeRecipientAddress, + }); + + const receipt = await deploymentManager.exchange.fillOrder.awaitTransactionSuccessAsync( + order, + new BigNumber(1), + order.signature, + { + from: takers[0], + gasPrice, + value: DeploymentManager.protocolFeeMultiplier.times(gasPrice), + }, + ); + + // Ensure that the number of emitted logs is equal to 3. There should have been a fill event + // and two transfer events. A 'StakingPoolActivated' event should not be expected because + // the only staking pool that was created does not have enough stake. + expect(receipt.logs.length).to.be.eq(3); + + // Ensure that the fill event was correct. + const fillArgs = filterLogsToArguments(receipt.logs, IExchangeEvents.Fill); + expect(fillArgs.length).to.be.eq(1); + expect(fillArgs).to.be.deep.eq([ + { + makerAddress, + feeRecipientAddress, + makerAssetData: order.makerAssetData, + takerAssetData: order.takerAssetData, + makerFeeAssetData: order.makerFeeAssetData, + takerFeeAssetData: order.takerFeeAssetData, + orderHash: orderHashUtils.getOrderHashHex(order), + takerAddress: takers[0], + senderAddress: takers[0], + makerAssetFilledAmount: order.makerAssetAmount, + takerAssetFilledAmount: order.takerAssetAmount, + makerFeePaid: constants.ZERO_AMOUNT, + takerFeePaid: constants.ZERO_AMOUNT, + protocolFeePaid: DeploymentManager.protocolFeeMultiplier.times(gasPrice), + }, + ]); + + // Ensure that the transfer events were correctly emitted. + const transferArgs = filterLogsToArguments( + receipt.logs, + IERC20TokenEvents.Transfer, + ); + expect(transferArgs.length).to.be.eq(2); + expect(transferArgs).to.be.deep.eq([ + { + _from: takers[0], + _to: makerAddress, + _value: order.takerAssetAmount, + }, + { + _from: makerAddress, + _to: takers[0], + _value: order.makerAssetAmount, + }, + ]); + }); + }); +}); diff --git a/contracts/integrations/test/0x-integration-tests/random_test.ts b/contracts/integrations/test/0x-integration-tests/random_test.ts deleted file mode 100644 index 9b051162b9..0000000000 --- a/contracts/integrations/test/0x-integration-tests/random_test.ts +++ /dev/null @@ -1,50 +0,0 @@ -import { blockchainTests, constants, expect } from '@0x/contracts-test-utils'; -import { assetDataUtils } from '@0x/order-utils'; -import { BigNumber } from '@0x/utils'; - -import { DeploymentManager } from '../../src'; - -blockchainTests('Exchange & Staking', env => { - let accounts: string[]; - let deploymentManager: DeploymentManager; - - before(async () => { - accounts = await env.getAccountAddressesAsync(); - deploymentManager = await DeploymentManager.deployAsync(env); - - // Create a staking pool with the operator as a maker address. - await deploymentManager.staking.stakingWrapper.createStakingPool.awaitTransactionSuccessAsync( - constants.ZERO_AMOUNT, - true, - ); - - // TODO(jalextowle): I will eventually want these utilities to be in the deployment manager. - // Create default order parameters - // const defaultOrderParams = { - // ...constants.STATIC_ORDER_PARAMS, - // makerAddress: accounts[1], - // makerAssetData: assetDataUtils.encodeERC20AssetData(defaultERC20MakerAssetAddress), - // takerAssetData: assetDataUtils.encodeERC20AssetData(defaultERC20TakerAssetAddress), - // makerFeeAssetData: assetDataUtils.encodeERC20AssetData(defaultFeeTokenAddress), - // takerFeeAssetData: assetDataUtils.encodeERC20AssetData(defaultFeeTokenAddress), - // feeRecipientAddress: feeRecipientAddressLeft, - // exchangeAddress: exchange.address, - // chainId, - // }; - }); - - // Function assertions for all of the functions involved in - // (1) Creating a staking pool - // - At first, this can be isolated. - // (2) Joining a staking pool as a maker - // - This can be isolated too, and we can assume a limited number - // of market makers to make things easy. - // (3) Paying a protocol fee - // - I'm personally of the opinion that we should write integration - // tests for the exchange and the staking contracts to interoperate. - // (4) Going to the next epoch - // - This might be something that just get's called every certain number of itterations - // for simple tests. - // (5) Finalizing a pool in the epoch - // - Ditto -}); diff --git a/contracts/integrations/test/utils/address_manager.ts b/contracts/integrations/test/utils/address_manager.ts new file mode 100644 index 0000000000..db0c8d686a --- /dev/null +++ b/contracts/integrations/test/utils/address_manager.ts @@ -0,0 +1,122 @@ +import { DummyERC20TokenContract } from '@0x/contracts-erc20'; +import { constants, OrderFactory, BlockchainTestsEnvironment } from '@0x/contracts-test-utils'; +import { assetDataUtils, Order, SignatureType, SignedOrder } from '@0x/order-utils'; + +import { DeploymentManager } from '../../src'; + +interface MarketMaker { + address: string; + orderFactory: OrderFactory; +} + +interface ConfigurationArgs { + address: string; + mainToken: DummyERC20TokenContract; + feeToken: DummyERC20TokenContract; +} + +export class AddressManager { + // A set of addresses that have been configured for market making. + public makerAddresses: MarketMaker[]; + + // A set of addresses that have been configured to take orders. + public takerAddresses: string[]; + + /** + * Sets up an address to take orders. + */ + public async addTakerAsync(deploymentManager: DeploymentManager, configArgs: ConfigurationArgs): Promise { + // Configure the taker address with the taker and fee tokens. + await this._configureTokenForAddressAsync(deploymentManager, configArgs.address, configArgs.mainToken); + await this._configureTokenForAddressAsync(deploymentManager, configArgs.address, configArgs.feeToken); + + // Add the taker to the list of configured taker addresses. + this.takerAddresses.push(configArgs.address); + } + + /** + * Sets up a list of addresses to take orders. + */ + public async addTakersAsync(deploymentManager: DeploymentManager, configArgs: ConfigurationArgs[]): Promise { + for (const args of configArgs) { + await this.addTakerAsync(deploymentManager, args); + } + } + + /** + * Sets up an address for market making. + */ + public async addMakerAsync( + deploymentManager: DeploymentManager, + configArgs: ConfigurationArgs, + environment: BlockchainTestsEnvironment, + takerToken: DummyERC20TokenContract, + feeRecipientAddress: string, + chainId: number, + ): Promise { + const accounts = await environment.getAccountAddressesAsync(); + + // Set up order signing for the maker address. + const defaultOrderParams = { + ...constants.STATIC_ORDER_PARAMS, + makerAddress: configArgs.address, + makerAssetData: assetDataUtils.encodeERC20AssetData(configArgs.mainToken.address), + takerAssetData: assetDataUtils.encodeERC20AssetData(takerToken.address), + makerFeeAssetData: assetDataUtils.encodeERC20AssetData(configArgs.feeToken.address), + takerFeeAssetData: assetDataUtils.encodeERC20AssetData(configArgs.feeToken.address), + feeRecipientAddress, + exchangeAddress: deploymentManager.exchange.address, + chainId, + }; + const privateKey = constants.TESTRPC_PRIVATE_KEYS[accounts.indexOf(configArgs.address)]; + const orderFactory = new OrderFactory(privateKey, defaultOrderParams); + + // Configure the maker address with the maker and fee tokens. + await this._configureTokenForAddressAsync(deploymentManager, configArgs.address, configArgs.mainToken); + await this._configureTokenForAddressAsync(deploymentManager, configArgs.address, configArgs.feeToken); + + // Add the maker to the list of configured maker addresses. + this.makerAddresses.push({ + address: configArgs.address, + orderFactory, + }); + } + + /** + * Sets up several market makers. + */ + public async addMakersAsync( + deploymentManager: DeploymentManager, + configArgs: ConfigurationArgs[], + environment: BlockchainTestsEnvironment, + takerToken: DummyERC20TokenContract, + feeRecipientAddress: string, + chainId: number, + ): Promise { + for (const args of configArgs) { + await this.addMakerAsync(deploymentManager, args, environment, takerToken, feeRecipientAddress, chainId); + } + } + + /** + * Sets up initial account balances for a token and approves the ERC20 asset proxy + * to transfer the token. + */ + protected async _configureTokenForAddressAsync( + deploymentManager: DeploymentManager, + address: string, + token: DummyERC20TokenContract, + ): Promise { + await token.setBalance.awaitTransactionSuccessAsync(address, constants.INITIAL_ERC20_BALANCE); + await token.approve.awaitTransactionSuccessAsync( + deploymentManager.assetProxies.erc20Proxy.address, + constants.MAX_UINT256, + { from: address }, + ); + } + + constructor(makers?: MarketMaker[], takers?: string[]) { + this.makerAddresses = []; + this.takerAddresses = []; + } +} diff --git a/contracts/integrations/test/utils/deployment_manager.ts b/contracts/integrations/test/utils/deployment_manager.ts index d6f41a4570..115bc435e3 100644 --- a/contracts/integrations/test/utils/deployment_manager.ts +++ b/contracts/integrations/test/utils/deployment_manager.ts @@ -34,11 +34,6 @@ import { BigNumber } from '@0x/utils'; import { TxData } from 'ethereum-types'; import * as _ from 'lodash'; -<<<<<<< HEAD:contracts/integrations/test/deployment/deployment_mananger.ts -======= -import { artifacts, TestStakingPlaceholderContract } from '../../src'; - ->>>>>>> `@0x/contracts-integrations` Created the FunctionAssertion class and examples:contracts/integrations/test/utils/deployment_mananger.ts /** * Adds a batch of authorities to a list of authorizable contracts. * @param owner The owner of the authorizable contracts. @@ -111,15 +106,9 @@ interface StakingContracts { // Contract wrappers for tokens. interface TokenContracts { -<<<<<<< HEAD:contracts/integrations/test/deployment/deployment_mananger.ts erc20: DummyERC20TokenContract[]; erc721: DummyERC721TokenContract[]; erc1155: ERC1155MintableContract[]; -======= - erc1155: ERC1155Contract; - erc20: ERC20TokenContract[]; - erc721: ERC721TokenContract; ->>>>>>> `@0x/contracts-integrations` Created the FunctionAssertion class and examples:contracts/integrations/test/utils/deployment_mananger.ts weth: WETH9Contract; zrx: ZRXTokenContract; } @@ -160,7 +149,7 @@ export class DeploymentManager { exchangeArtifacts.Exchange, environment.provider, environment.txDefaults, - exchangeArtifacts, + { ...ERC20Artifacts, ...exchangeArtifacts }, new BigNumber(chainId), ); const governor = await ZeroExGovernorContract.deployFrom0xArtifactAsync( @@ -418,7 +407,6 @@ export class DeploymentManager { txDefaults: Partial, options: Partial, ): Promise { -<<<<<<< HEAD:contracts/integrations/test/deployment/deployment_mananger.ts const numErc20TokensToDeploy = options.numErc20TokensToDeploy !== undefined ? options.numErc20TokensToDeploy @@ -447,26 +435,6 @@ export class DeploymentManager { constants.DUMMY_TOKEN_TOTAL_SUPPLY, ), ), -======= - const erc20 = []; - erc20[0] = await ERC20TokenContract.deployFrom0xArtifactAsync( - ERC20Artifacts.ERC20Token, - environment.provider, - txDefaults, - ERC20Artifacts, - ); - erc20[1] = await ERC20TokenContract.deployFrom0xArtifactAsync( - ERC20Artifacts.ERC20Token, - environment.provider, - txDefaults, - ERC20Artifacts, - ); - erc20[2] = await ERC20TokenContract.deployFrom0xArtifactAsync( - ERC20Artifacts.ERC20Token, - environment.provider, - txDefaults, - ERC20Artifacts, ->>>>>>> `@0x/contracts-integrations` Created the FunctionAssertion class and examples:contracts/integrations/test/utils/deployment_mananger.ts ); const erc721 = await Promise.all( _.times( diff --git a/contracts/test-utils/src/order_factory.ts b/contracts/test-utils/src/order_factory.ts index 1a2e1187e6..4a20febc4b 100644 --- a/contracts/test-utils/src/order_factory.ts +++ b/contracts/test-utils/src/order_factory.ts @@ -9,10 +9,12 @@ import { signingUtils } from './signing_utils'; export class OrderFactory { private readonly _defaultOrderParams: Partial; private readonly _privateKey: Buffer; + constructor(privateKey: Buffer, defaultOrderParams: Partial) { this._defaultOrderParams = defaultOrderParams; this._privateKey = privateKey; } + public async newSignedOrderAsync( customOrderParams: Partial = {}, signatureType: SignatureType = SignatureType.EthSign,