import { ECSignature, 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 { Balances } from '../../util/balances';
import { constants } from '../../util/constants';
import { crypto } from '../../util/crypto';
import { ExchangeWrapper } from '../../util/exchange_wrapper';
import { Order } from '../../util/order';
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.only('Arbitrage', () => {
let coinbase: string;
let maker: string;
let edMaker: string;
let edFrontRunner: string;
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: Web3.ContractInstance;
let etherDelta: Web3.ContractInstance;
let order: Order;
let exWrapper: ExchangeWrapper;
let orderFactory: OrderFactory;
let zeroEx: ZeroEx;
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;
etherDelta = await deployer.deployAsync(ContractName.EtherDelta, [
edAdminAddress,
feeRecipient,
accountLevels.address,
edMakerFee,
edTakerFee,
edFeeRebate,
]);
const tokenTransferProxy = await deployer.deployAsync(ContractName.TokenTransferProxy);
const exchange = await deployer.deployAsync(ContractName.Exchange, [zrx.address, tokenTransferProxy.address]);
await tokenTransferProxy.addAuthorizedAddress(exchange.address, { from: accounts[0] });
zeroEx = new ZeroEx(web3.currentProvider, {
exchangeContractAddress: exchange.address,
networkId: constants.TESTRPC_NETWORK_ID,
});
exWrapper = new ExchangeWrapper(exchange, zeroEx);
const defaultOrderParams = {
exchangeContractAddress: exchange.address,
maker,
feeRecipient,
makerToken: zrx.address,
takerToken: weth.address,
makerTokenAmount: ZeroEx.toBaseUnitAmount(new BigNumber(1), 18),
takerTokenAmount: ZeroEx.toBaseUnitAmount(new BigNumber(1), 18),
makerFee: new BigNumber(0),
takerFee: new BigNumber(0),
};
orderFactory = new OrderFactory(web3Wrapper, defaultOrderParams);
arbitrage = await deployer.deployAsync(ContractName.Arbitrage, [
exchange.address,
etherDelta.address,
tokenTransferProxy.address,
]);
// Enable arbitrage and withdrawals of tokens
await arbitrage.setAllowances(weth.address, { from: coinbase });
await arbitrage.setAllowances(zrx.address, { from: coinbase });
// Give some tokens to arbitrage contract
await weth.setBalance(arbitrage.address, ZeroEx.toBaseUnitAmount(new BigNumber(1), 18), { from: coinbase });
// Fund the maker on exchange side
await zrx.setBalance(maker, ZeroEx.toBaseUnitAmount(new BigNumber(1), 18), { from: coinbase });
// Set the allowance for the maker on Exchange side
await zrx.approve(tokenTransferProxy.address, INITIAL_ALLOWANCE, { from: maker });
const amountGive = ZeroEx.toBaseUnitAmount(new BigNumber(2), 18);
// Fund the maker on EtherDelta side
await weth.setBalance(edMaker, ZeroEx.toBaseUnitAmount(new BigNumber(2), 18), { 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(weth.address, amountGive, { from: edMaker });
const amountGet = ZeroEx.toBaseUnitAmount(new BigNumber(1), 18);
// 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(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 amountGet: BigNumber;
let amountGive: BigNumber;
let expires: BigNumber;
let nonce: BigNumber;
let edSignature: ECSignature;
before(async () => {
order = await orderFactory.newSignedOrderAsync();
tokenGet = zrx.address;
amountGet = ZeroEx.toBaseUnitAmount(new BigNumber(1), 18);
tokenGive = weth.address;
amountGive = ZeroEx.toBaseUnitAmount(new BigNumber(2), 18);
const blockNumber = await web3Wrapper.getBlockNumberAsync();
expires = new BigNumber(blockNumber + 10);
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 = [
order.params.maker,
order.params.taker,
order.params.makerToken,
order.params.takerToken,
order.params.feeRecipient,
edMaker,
];
const fillTakerTokenAmount = ZeroEx.toBaseUnitAmount(new BigNumber(1), 18);
const edFillAmount = ZeroEx.toBaseUnitAmount(new BigNumber(1), 18);
values = [
order.params.makerTokenAmount,
order.params.takerTokenAmount,
order.params.makerFee,
order.params.takerFee,
order.params.expirationTimestampInSec,
order.params.salt,
fillTakerTokenAmount,
amountGet,
amountGive,
expires,
nonce,
edFillAmount,
];
v = [order.params.v as number, edSignature.v];
r = [order.params.r as string, edSignature.r];
s = [order.params.s as string, edSignature.s];
});
it('should successfully execute the arbitrage if not front-runned', async () => {
const txHash = await arbitrage.makeAtomicTrade(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(ZeroEx.toBaseUnitAmount(new BigNumber(2), 18));
});
it('should fail and revert if front-runned', async () => {
const preBalance = await weth.balanceOf(arbitrage.address);
// Front-running transaction
await etherDelta.trade(
tokenGet,
amountGet,
tokenGive,
amountGive,
expires,
nonce,
edMaker,
edSignature.v,
edSignature.r,
edSignature.s,
ZeroEx.toBaseUnitAmount(new BigNumber(1), 18),
{ from: edFrontRunner },
);
// tslint:disable-next-line:await-promise
await expect(arbitrage.makeAtomicTrade(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);
});
});
});