diff options
Diffstat (limited to 'packages/contracts/test/tutorials')
-rw-r--r-- | packages/contracts/test/tutorials/arbitrage.ts | 226 |
1 files changed, 226 insertions, 0 deletions
diff --git a/packages/contracts/test/tutorials/arbitrage.ts b/packages/contracts/test/tutorials/arbitrage.ts new file mode 100644 index 000000000..2bafbff0b --- /dev/null +++ b/packages/contracts/test/tutorials/arbitrage.ts @@ -0,0 +1,226 @@ +import { ECSignature, SignedOrder, ZeroEx } from '0x.js'; +import { BlockchainLifecycle, devConstants, web3Factory } from '@0xproject/dev-utils'; +import { BigNumber } from '@0xproject/utils'; +import { Web3Wrapper } from '@0xproject/web3-wrapper'; +import * as chai from 'chai'; +import ethUtil = require('ethereumjs-util'); +import * as Web3 from 'web3'; + +import { ArbitrageContract } from '../../src/contract_wrappers/generated/arbitrage'; +import { EtherDeltaContract } from '../../src/contract_wrappers/generated/ether_delta'; +import { ExchangeContract } from '../../src/contract_wrappers/generated/exchange'; +import { Balances } from '../../util/balances'; +import { constants } from '../../util/constants'; +import { crypto } from '../../util/crypto'; +import { ExchangeWrapper } from '../../util/exchange_wrapper'; +import { OrderFactory } from '../../util/order_factory'; +import { BalancesByOwner, ContractName, ExchangeContractErrs } from '../../util/types'; +import { chaiSetup } from '../utils/chai_setup'; +import { deployer } from '../utils/deployer'; + +chaiSetup.configure(); +const expect = chai.expect; +const web3 = web3Factory.create(); +const web3Wrapper = new Web3Wrapper(web3.currentProvider); +const blockchainLifecycle = new BlockchainLifecycle(); + +describe('Arbitrage', () => { + let coinbase: string; + let maker: string; + let edMaker: string; + let edFrontRunner: string; + let amountGet: BigNumber; + let amountGive: BigNumber; + let makerTokenAmount: BigNumber; + let takerTokenAmount: BigNumber; + const feeRecipient = ZeroEx.NULL_ADDRESS; + const INITIAL_BALANCE = ZeroEx.toBaseUnitAmount(new BigNumber(10000), 18); + const INITIAL_ALLOWANCE = ZeroEx.toBaseUnitAmount(new BigNumber(10000), 18); + + let weth: Web3.ContractInstance; + let zrx: Web3.ContractInstance; + let arbitrage: ArbitrageContract; + let etherDelta: EtherDeltaContract; + + let signedOrder: SignedOrder; + let exWrapper: ExchangeWrapper; + let orderFactory: OrderFactory; + + let zeroEx: ZeroEx; + + // From a bird's eye view - we create two orders. + // 0x order of 1 ZRX (maker) for 1 WETH (taker) + // ED order of 2 WETH (tokenGive) for 1 ZRX (tokenGet) + // And then we do an atomic arbitrage between them which gives us 1 WETH. + before(async () => { + const accounts = await web3Wrapper.getAvailableAddressesAsync(); + [coinbase, maker, edMaker, edFrontRunner] = accounts; + weth = await deployer.deployAsync(ContractName.DummyToken); + zrx = await deployer.deployAsync(ContractName.DummyToken); + const accountLevels = await deployer.deployAsync(ContractName.AccountLevels); + const edAdminAddress = accounts[0]; + const edMakerFee = 0; + const edTakerFee = 0; + const edFeeRebate = 0; + const etherDeltaInstance = await deployer.deployAsync(ContractName.EtherDelta, [ + edAdminAddress, + feeRecipient, + accountLevels.address, + edMakerFee, + edTakerFee, + edFeeRebate, + ]); + etherDelta = new EtherDeltaContract(web3Wrapper, etherDeltaInstance.abi, etherDeltaInstance.address); + const tokenTransferProxy = await deployer.deployAsync(ContractName.TokenTransferProxy); + const exchangeInstance = await deployer.deployAsync(ContractName.Exchange, [ + zrx.address, + tokenTransferProxy.address, + ]); + await tokenTransferProxy.addAuthorizedAddress(exchangeInstance.address, { from: accounts[0] }); + zeroEx = new ZeroEx(web3.currentProvider, { + exchangeContractAddress: exchangeInstance.address, + networkId: constants.TESTRPC_NETWORK_ID, + }); + const exchange = new ExchangeContract(web3Wrapper, exchangeInstance.abi, exchangeInstance.address); + exWrapper = new ExchangeWrapper(exchange, zeroEx); + + makerTokenAmount = ZeroEx.toBaseUnitAmount(new BigNumber(1), 18); + takerTokenAmount = makerTokenAmount; + const defaultOrderParams = { + exchangeContractAddress: exchange.address, + maker, + feeRecipient, + makerTokenAddress: zrx.address, + takerTokenAddress: weth.address, + makerTokenAmount, + takerTokenAmount, + makerFee: new BigNumber(0), + takerFee: new BigNumber(0), + }; + orderFactory = new OrderFactory(zeroEx, defaultOrderParams); + const arbitrageInstance = await deployer.deployAsync(ContractName.Arbitrage, [ + exchange.address, + etherDelta.address, + tokenTransferProxy.address, + ]); + arbitrage = new ArbitrageContract(web3Wrapper, arbitrageInstance.abi, arbitrageInstance.address); + // Enable arbitrage and withdrawals of tokens + await arbitrage.setAllowances.sendTransactionAsync(weth.address, { from: coinbase }); + await arbitrage.setAllowances.sendTransactionAsync(zrx.address, { from: coinbase }); + + // Give some tokens to arbitrage contract + await weth.setBalance(arbitrage.address, takerTokenAmount, { from: coinbase }); + + // Fund the maker on exchange side + await zrx.setBalance(maker, makerTokenAmount, { from: coinbase }); + // Set the allowance for the maker on Exchange side + await zrx.approve(tokenTransferProxy.address, INITIAL_ALLOWANCE, { from: maker }); + + amountGive = ZeroEx.toBaseUnitAmount(new BigNumber(2), 18); + // Fund the maker on EtherDelta side + await weth.setBalance(edMaker, amountGive, { from: coinbase }); + // Set the allowance for the maker on EtherDelta side + await weth.approve(etherDelta.address, INITIAL_ALLOWANCE, { from: edMaker }); + // Deposit maker funds into EtherDelta + await etherDelta.depositToken.sendTransactionAsync(weth.address, amountGive, { from: edMaker }); + + amountGet = makerTokenAmount; + // Fund the front runner on EtherDelta side + await zrx.setBalance(edFrontRunner, amountGet, { from: coinbase }); + // Set the allowance for the front-runner on EtherDelta side + await zrx.approve(etherDelta.address, INITIAL_ALLOWANCE, { from: edFrontRunner }); + // Deposit front runner funds into EtherDelta + await etherDelta.depositToken.sendTransactionAsync(zrx.address, amountGet, { from: edFrontRunner }); + }); + beforeEach(async () => { + await blockchainLifecycle.startAsync(); + }); + afterEach(async () => { + await blockchainLifecycle.revertAsync(); + }); + describe('makeAtomicTrade', () => { + let addresses: string[]; + let values: BigNumber[]; + let v: number[]; + let r: string[]; + let s: string[]; + let tokenGet: string; + let tokenGive: string; + let expires: BigNumber; + let nonce: BigNumber; + let edSignature: ECSignature; + before(async () => { + signedOrder = await orderFactory.newSignedOrderAsync(); + tokenGet = zrx.address; + tokenGive = weth.address; + const blockNumber = await web3Wrapper.getBlockNumberAsync(); + const ED_ORDER_EXPIRATION_IN_BLOCKS = 10; + expires = new BigNumber(blockNumber + ED_ORDER_EXPIRATION_IN_BLOCKS); + nonce = new BigNumber(42); + const edOrderHash = `0x${crypto + .solSHA256([etherDelta.address, tokenGet, amountGet, tokenGive, amountGive, expires, nonce]) + .toString('hex')}`; + const shouldAddPersonalMessagePrefix = false; + edSignature = await zeroEx.signOrderHashAsync(edOrderHash, edMaker, shouldAddPersonalMessagePrefix); + addresses = [ + signedOrder.maker, + signedOrder.taker, + signedOrder.makerTokenAddress, + signedOrder.takerTokenAddress, + signedOrder.feeRecipient, + edMaker, + ]; + const fillTakerTokenAmount = takerTokenAmount; + const edFillAmount = makerTokenAmount; + values = [ + signedOrder.makerTokenAmount, + signedOrder.takerTokenAmount, + signedOrder.makerFee, + signedOrder.takerFee, + signedOrder.expirationUnixTimestampSec, + signedOrder.salt, + fillTakerTokenAmount, + amountGet, + amountGive, + expires, + nonce, + edFillAmount, + ]; + v = [signedOrder.ecSignature.v, edSignature.v]; + r = [signedOrder.ecSignature.r, edSignature.r]; + s = [signedOrder.ecSignature.s, edSignature.s]; + }); + it('should successfully execute the arbitrage if not front-runned', async () => { + const txHash = await arbitrage.makeAtomicTrade.sendTransactionAsync(addresses, values, v, r, s, { + from: coinbase, + }); + const res = await zeroEx.awaitTransactionMinedAsync(txHash); + const postBalance = await weth.balanceOf(arbitrage.address); + expect(postBalance).to.be.bignumber.equal(amountGive); + }); + it('should fail and revert if front-runned', async () => { + const preBalance = await weth.balanceOf(arbitrage.address); + // Front-running transaction + await etherDelta.trade.sendTransactionAsync( + tokenGet, + amountGet, + tokenGive, + amountGive, + expires, + nonce, + edMaker, + edSignature.v, + edSignature.r, + edSignature.s, + amountGet, + { from: edFrontRunner }, + ); + // tslint:disable-next-line:await-promise + await expect( + arbitrage.makeAtomicTrade.sendTransactionAsync(addresses, values, v, r, s, { from: coinbase }), + ).to.be.rejectedWith(constants.REVERT); + const postBalance = await weth.balanceOf(arbitrage.address); + expect(preBalance).to.be.bignumber.equal(postBalance); + }); + }); +}); |