aboutsummaryrefslogtreecommitdiffstats
path: root/packages/contracts
diff options
context:
space:
mode:
Diffstat (limited to 'packages/contracts')
-rw-r--r--packages/contracts/README.md12
-rw-r--r--packages/contracts/globals.d.ts1
-rw-r--r--packages/contracts/package.json36
-rw-r--r--packages/contracts/src/contracts/current/tutorials/Arbitrage/Arbitrage.sol114
-rw-r--r--packages/contracts/src/contracts/current/tutorials/EtherDelta/AccountLevels.sol11
-rw-r--r--packages/contracts/src/contracts/current/tutorials/EtherDelta/EtherDelta.sol168
-rw-r--r--packages/contracts/test/tutorials/arbitrage.ts226
-rw-r--r--packages/contracts/util/crypto.ts8
-rw-r--r--packages/contracts/util/types.ts3
9 files changed, 554 insertions, 25 deletions
diff --git a/packages/contracts/README.md b/packages/contracts/README.md
index 11b9e5056..7628b057b 100644
--- a/packages/contracts/README.md
+++ b/packages/contracts/README.md
@@ -4,11 +4,11 @@ Smart contracts that implement the 0x protocol.
## Usage
-* [Docs](https://0xproject.com/docs/contracts)
-* [Overview of 0x protocol architecture](https://0xproject.com/wiki#Architecture)
-* [0x smart contract interactions](https://0xproject.com/wiki#Contract-Interactions)
-* [Deployed smart contract addresses](https://0xproject.com/wiki#Deployed-Addresses)
-* [0x protocol message format](https://0xproject.com/wiki#Message-Format)
+* [Docs](https://0xproject.com/docs/contracts)
+* [Overview of 0x protocol architecture](https://0xproject.com/wiki#Architecture)
+* [0x smart contract interactions](https://0xproject.com/wiki#Contract-Interactions)
+* [Deployed smart contract addresses](https://0xproject.com/wiki#Deployed-Addresses)
+* [0x protocol message format](https://0xproject.com/wiki#Message-Format)
## Contributing
@@ -60,7 +60,7 @@ yarn lint
Before running the tests, you will need to spin up a [TestRPC](https://www.npmjs.com/package/ethereumjs-testrpc) instance.
-In a separate terminal, start TestRPC (a convenience command is provided as part of the [0x.js monorepo](https://github.com/0xProject/0x.js))
+In a separate terminal, start TestRPC (a convenience command is provided as part of the [0x.js monorepo](https://github.com/0xProject/0x-monorepo))
```bash
cd ../..
diff --git a/packages/contracts/globals.d.ts b/packages/contracts/globals.d.ts
index 0e6586a4b..c6597054a 100644
--- a/packages/contracts/globals.d.ts
+++ b/packages/contracts/globals.d.ts
@@ -30,5 +30,6 @@ declare module 'web3-eth-abi' {
declare module 'ethereumjs-abi' {
const soliditySHA3: (argTypes: string[], args: any[]) => Buffer;
+ const soliditySHA256: (argTypes: string[], args: any[]) => Buffer;
const methodID: (name: string, types: string[]) => Buffer;
}
diff --git a/packages/contracts/package.json b/packages/contracts/package.json
index 8159ba244..0d084a542 100644
--- a/packages/contracts/package.json
+++ b/packages/contracts/package.json
@@ -1,7 +1,7 @@
{
"private": true,
"name": "contracts",
- "version": "2.1.13",
+ "version": "2.1.14",
"description": "Smart contract components of 0x protocol",
"main": "index.js",
"directories": {
@@ -17,27 +17,27 @@
"compile:comment": "Yarn workspaces do not link binaries correctly so we need to reference them directly https://github.com/yarnpkg/yarn/issues/3846",
"compile": "node ../deployer/lib/src/cli.js compile --contracts ${npm_package_config_contracts} --contracts-dir src/contracts --artifacts-dir src/artifacts",
"clean": "shx rm -rf ./lib",
- "generate_contract_wrappers": "node ../abi-gen/lib/index.js --abis 'src/artifacts/@(DummyToken|TokenTransferProxy|Exchange|TokenRegistry|MultiSigWallet|MultiSigWalletWithTimeLock|MultiSigWalletWithTimeLockExceptRemoveAuthorizedAddress|TokenRegistry|ZRXToken).json' --template ../contract_templates/contract.handlebars --partials '../contract_templates/partials/**/*.handlebars' --output src/contract_wrappers/generated --backend ethers && prettier --write 'src/contract_wrappers/generated/**.ts'",
+ "generate_contract_wrappers": "node ../abi-gen/lib/index.js --abis 'src/artifacts/@(DummyToken|TokenTransferProxy|Exchange|TokenRegistry|MultiSigWallet|MultiSigWalletWithTimeLock|MultiSigWalletWithTimeLockExceptRemoveAuthorizedAddress|TokenRegistry|ZRXToken|Arbitrage|EtherDelta|AccountLevels).json' --template ../contract_templates/contract.handlebars --partials '../contract_templates/partials/**/*.handlebars' --output src/contract_wrappers/generated --backend ethers && prettier --write 'src/contract_wrappers/generated/**.ts'",
"migrate": "node ../deployer/lib/src/cli.js migrate",
"lint": "tslint --project . 'migrations/**/*.ts' 'test/**/*.ts' 'util/**/*.ts' 'deploy/**/*.ts'",
"test:circleci": "yarn test"
},
"config": {
- "contracts": "Exchange,DummyToken,ZRXToken,Token,WETH9,TokenTransferProxy,MultiSigWallet,MultiSigWalletWithTimeLock,MultiSigWalletWithTimeLockExceptRemoveAuthorizedAddress,MaliciousToken,TokenRegistry"
+ "contracts": "Exchange,DummyToken,ZRXToken,Token,WETH9,TokenTransferProxy,MultiSigWallet,MultiSigWalletWithTimeLock,MultiSigWalletWithTimeLockExceptRemoveAuthorizedAddress,MaliciousToken,TokenRegistry,Arbitrage,EtherDelta,AccountLevels"
},
"repository": {
"type": "git",
- "url": "https://github.com/0xProject/0x.js.git"
+ "url": "https://github.com/0xProject/0x-monorepo.git"
},
"author": "Amir Bandeali",
"license": "Apache-2.0",
"bugs": {
- "url": "https://github.com/0xProject/0x.js/issues"
+ "url": "https://github.com/0xProject/0x-monorepo/issues"
},
- "homepage": "https://github.com/0xProject/0x.js/packages/contracts/README.md",
+ "homepage": "https://github.com/0xProject/0x-monorepo/packages/contracts/README.md",
"devDependencies": {
- "@0xproject/dev-utils": "^0.1.0",
- "@0xproject/tslint-config": "^0.4.9",
+ "@0xproject/dev-utils": "^0.2.0",
+ "@0xproject/tslint-config": "^0.4.10",
"@types/bluebird": "^3.5.3",
"@types/lodash": "^4.14.86",
"@types/node": "^8.0.53",
@@ -45,11 +45,12 @@
"@types/yargs": "^10.0.0",
"chai": "^4.0.1",
"chai-as-promised": "^7.1.0",
- "chai-as-promised-typescript-typings": "^0.0.9",
+ "chai-as-promised-typescript-typings": "^0.0.10",
"chai-bignumber": "^2.0.1",
- "chai-typescript-typings": "^0.0.3",
+ "chai-typescript-typings": "^0.0.4",
"copyfiles": "^1.2.0",
"dirty-chai": "^2.0.1",
+ "ethers-typescript-typings": "^0.0.2",
"mocha": "^4.0.1",
"npm-run-all": "^4.1.2",
"shx": "^0.2.2",
@@ -58,17 +59,16 @@
"types-bn": "^0.0.1",
"types-ethereumjs-util": "0xProject/types-ethereumjs-util",
"typescript": "2.7.1",
- "ethers-typescript-typings": "^0.0.1",
- "web3-typescript-typings": "^0.9.11",
+ "web3-typescript-typings": "^0.10.0",
"yargs": "^10.0.3"
},
"dependencies": {
- "0x.js": "^0.32.4",
- "@0xproject/deployer": "^0.1.0",
- "@0xproject/json-schemas": "^0.7.12",
- "@0xproject/types": "^0.2.3",
- "@0xproject/utils": "^0.3.4",
- "@0xproject/web3-wrapper": "^0.1.14",
+ "0x.js": "^0.33.0",
+ "@0xproject/deployer": "^0.2.0",
+ "@0xproject/json-schemas": "^0.7.13",
+ "@0xproject/types": "^0.3.0",
+ "@0xproject/utils": "^0.4.0",
+ "@0xproject/web3-wrapper": "^0.2.0",
"bluebird": "^3.5.0",
"bn.js": "^4.11.8",
"ethereumjs-abi": "^0.6.4",
diff --git a/packages/contracts/src/contracts/current/tutorials/Arbitrage/Arbitrage.sol b/packages/contracts/src/contracts/current/tutorials/Arbitrage/Arbitrage.sol
new file mode 100644
index 000000000..a9f3c22e6
--- /dev/null
+++ b/packages/contracts/src/contracts/current/tutorials/Arbitrage/Arbitrage.sol
@@ -0,0 +1,114 @@
+pragma solidity ^0.4.19;
+
+import { Exchange } from "../../protocol/Exchange/Exchange.sol";
+import { EtherDelta } from "../EtherDelta/EtherDelta.sol";
+import { Ownable } from "../../utils/Ownable/Ownable.sol";
+import { Token } from "../../tokens/Token/Token.sol";
+
+/// @title Arbitrage - Facilitates atomic arbitrage of ERC20 tokens between EtherDelta and 0x Exchange contract.
+/// @author Leonid Logvinov - <leo@0xProject.com>
+contract Arbitrage is Ownable {
+
+ Exchange exchange;
+ EtherDelta etherDelta;
+ address proxyAddress;
+
+ uint256 constant MAX_UINT = 2**256 - 1;
+
+ function Arbitrage(address _exchangeAddress, address _etherDeltaAddress, address _proxyAddress) {
+ exchange = Exchange(_exchangeAddress);
+ etherDelta = EtherDelta(_etherDeltaAddress);
+ proxyAddress = _proxyAddress;
+ }
+
+ /*
+ * Makes token tradeable by setting an allowance for etherDelta and 0x proxy contract.
+ * Also sets an allowance for the owner of the contracts therefore allowing to withdraw tokens.
+ */
+ function setAllowances(address tokenAddress) external onlyOwner {
+ Token token = Token(tokenAddress);
+ token.approve(address(etherDelta), MAX_UINT);
+ token.approve(proxyAddress, MAX_UINT);
+ token.approve(owner, MAX_UINT);
+ }
+
+ /*
+ * Because of the limits on the number of local variables in Solidity we need to compress parameters while loosing
+ * readability. Scheme of the parameter layout:
+ *
+ * addresses
+ * 0..4 orderAddresses
+ * 5 user
+ *
+ * values
+ * 0..5 orderValues
+ * 6 fillTakerTokenAmount
+ * 7 amountGet
+ * 8 amountGive
+ * 9 expires
+ * 10 nonce
+ * 11 amount
+
+ * signature
+ * exchange then etherDelta
+ */
+ function makeAtomicTrade(
+ address[6] addresses, uint[12] values,
+ uint8[2] v, bytes32[2] r, bytes32[2] s
+ ) external onlyOwner {
+ makeExchangeTrade(addresses, values, v, r, s);
+ makeEtherDeltaTrade(addresses, values, v, r, s);
+ }
+
+ function makeEtherDeltaTrade(
+ address[6] addresses, uint[12] values,
+ uint8[2] v, bytes32[2] r, bytes32[2] s
+ ) internal {
+ uint amount = values[11];
+ etherDelta.depositToken(
+ addresses[2], // tokenGet === makerToken
+ values[7] // amountGet
+ );
+ etherDelta.trade(
+ addresses[2], // tokenGet === makerToken
+ values[7], // amountGet
+ addresses[3], // tokenGive === takerToken
+ values[8], // amountGive
+ values[9], // expires
+ values[10], // nonce
+ addresses[5], // user
+ v[1],
+ r[1],
+ s[1],
+ amount
+ );
+ etherDelta.withdrawToken(
+ addresses[3], // tokenGive === tokenToken
+ values[8] // amountGive
+ );
+ }
+
+ function makeExchangeTrade(
+ address[6] addresses, uint[12] values,
+ uint8[2] v, bytes32[2] r, bytes32[2] s
+ ) internal {
+ address[5] memory orderAddresses = [
+ addresses[0], // maker
+ addresses[1], // taker
+ addresses[2], // makerToken
+ addresses[3], // takerToken
+ addresses[4] // feeRecepient
+ ];
+ uint[6] memory orderValues = [
+ values[0], // makerTokenAmount
+ values[1], // takerTokenAmount
+ values[2], // makerFee
+ values[3], // takerFee
+ values[4], // expirationTimestampInSec
+ values[5] // salt
+ ];
+ uint fillTakerTokenAmount = values[6]; // fillTakerTokenAmount
+ // Execute Exchange trade. It either succeeds in full or fails and reverts all the changes.
+ exchange.fillOrKillOrder(orderAddresses, orderValues, fillTakerTokenAmount, v[0], r[0], s[0]);
+ }
+}
diff --git a/packages/contracts/src/contracts/current/tutorials/EtherDelta/AccountLevels.sol b/packages/contracts/src/contracts/current/tutorials/EtherDelta/AccountLevels.sol
new file mode 100644
index 000000000..8d7a930d3
--- /dev/null
+++ b/packages/contracts/src/contracts/current/tutorials/EtherDelta/AccountLevels.sol
@@ -0,0 +1,11 @@
+pragma solidity ^0.4.19;
+
+contract AccountLevels {
+ //given a user, returns an account level
+ //0 = regular user (pays take fee and make fee)
+ //1 = market maker silver (pays take fee, no make fee, gets rebate)
+ //2 = market maker gold (pays take fee, no make fee, gets entire counterparty's take fee as rebate)
+ function accountLevel(address user) constant returns(uint) {
+ return 0;
+ }
+}
diff --git a/packages/contracts/src/contracts/current/tutorials/EtherDelta/EtherDelta.sol b/packages/contracts/src/contracts/current/tutorials/EtherDelta/EtherDelta.sol
new file mode 100644
index 000000000..49847ab48
--- /dev/null
+++ b/packages/contracts/src/contracts/current/tutorials/EtherDelta/EtherDelta.sol
@@ -0,0 +1,168 @@
+pragma solidity ^0.4.19;
+
+import { SafeMath } from "../../utils/SafeMath/SafeMath.sol";
+import { AccountLevels } from "./AccountLevels.sol";
+import { Token } from "../../tokens/Token/Token.sol";
+
+contract EtherDelta is SafeMath {
+ address public admin; //the admin address
+ address public feeAccount; //the account that will receive fees
+ address public accountLevelsAddr; //the address of the AccountLevels contract
+ uint public feeMake; //percentage times (1 ether)
+ uint public feeTake; //percentage times (1 ether)
+ uint public feeRebate; //percentage times (1 ether)
+ mapping (address => mapping (address => uint)) public tokens; //mapping of token addresses to mapping of account balances (token=0 means Ether)
+ mapping (address => mapping (bytes32 => bool)) public orders; //mapping of user accounts to mapping of order hashes to booleans (true = submitted by user, equivalent to offchain signature)
+ mapping (address => mapping (bytes32 => uint)) public orderFills; //mapping of user accounts to mapping of order hashes to uints (amount of order that has been filled)
+
+ event Order(address tokenGet, uint amountGet, address tokenGive, uint amountGive, uint expires, uint nonce, address user);
+ event Cancel(address tokenGet, uint amountGet, address tokenGive, uint amountGive, uint expires, uint nonce, address user, uint8 v, bytes32 r, bytes32 s);
+ event Trade(address tokenGet, uint amountGet, address tokenGive, uint amountGive, address get, address give);
+ event Deposit(address token, address user, uint amount, uint balance);
+ event Withdraw(address token, address user, uint amount, uint balance);
+
+ function EtherDelta(address admin_, address feeAccount_, address accountLevelsAddr_, uint feeMake_, uint feeTake_, uint feeRebate_) {
+ admin = admin_;
+ feeAccount = feeAccount_;
+ accountLevelsAddr = accountLevelsAddr_;
+ feeMake = feeMake_;
+ feeTake = feeTake_;
+ feeRebate = feeRebate_;
+ }
+
+ function() {
+ throw;
+ }
+
+ function changeAdmin(address admin_) {
+ if (msg.sender != admin) throw;
+ admin = admin_;
+ }
+
+ function changeAccountLevelsAddr(address accountLevelsAddr_) {
+ if (msg.sender != admin) throw;
+ accountLevelsAddr = accountLevelsAddr_;
+ }
+
+ function changeFeeAccount(address feeAccount_) {
+ if (msg.sender != admin) throw;
+ feeAccount = feeAccount_;
+ }
+
+ function changeFeeMake(uint feeMake_) {
+ if (msg.sender != admin) throw;
+ if (feeMake_ > feeMake) throw;
+ feeMake = feeMake_;
+ }
+
+ function changeFeeTake(uint feeTake_) {
+ if (msg.sender != admin) throw;
+ if (feeTake_ > feeTake || feeTake_ < feeRebate) throw;
+ feeTake = feeTake_;
+ }
+
+ function changeFeeRebate(uint feeRebate_) {
+ if (msg.sender != admin) throw;
+ if (feeRebate_ < feeRebate || feeRebate_ > feeTake) throw;
+ feeRebate = feeRebate_;
+ }
+
+ function deposit() payable {
+ tokens[0][msg.sender] = safeAdd(tokens[0][msg.sender], msg.value);
+ Deposit(0, msg.sender, msg.value, tokens[0][msg.sender]);
+ }
+
+ function withdraw(uint amount) {
+ if (tokens[0][msg.sender] < amount) throw;
+ tokens[0][msg.sender] = safeSub(tokens[0][msg.sender], amount);
+ if (!msg.sender.call.value(amount)()) throw;
+ Withdraw(0, msg.sender, amount, tokens[0][msg.sender]);
+ }
+
+ function depositToken(address token, uint amount) {
+ //remember to call Token(address).approve(this, amount) or this contract will not be able to do the transfer on your behalf.
+ if (token==0) throw;
+ if (!Token(token).transferFrom(msg.sender, this, amount)) throw;
+ tokens[token][msg.sender] = safeAdd(tokens[token][msg.sender], amount);
+ Deposit(token, msg.sender, amount, tokens[token][msg.sender]);
+ }
+
+ function withdrawToken(address token, uint amount) {
+ if (token==0) throw;
+ if (tokens[token][msg.sender] < amount) throw;
+ tokens[token][msg.sender] = safeSub(tokens[token][msg.sender], amount);
+ if (!Token(token).transfer(msg.sender, amount)) throw;
+ Withdraw(token, msg.sender, amount, tokens[token][msg.sender]);
+ }
+
+ function balanceOf(address token, address user) constant returns (uint) {
+ return tokens[token][user];
+ }
+
+ function order(address tokenGet, uint amountGet, address tokenGive, uint amountGive, uint expires, uint nonce) {
+ bytes32 hash = sha256(this, tokenGet, amountGet, tokenGive, amountGive, expires, nonce);
+ orders[msg.sender][hash] = true;
+ Order(tokenGet, amountGet, tokenGive, amountGive, expires, nonce, msg.sender);
+ }
+
+ function trade(address tokenGet, uint amountGet, address tokenGive, uint amountGive, uint expires, uint nonce, address user, uint8 v, bytes32 r, bytes32 s, uint amount) {
+ //amount is in amountGet terms
+ bytes32 hash = sha256(this, tokenGet, amountGet, tokenGive, amountGive, expires, nonce);
+ if (!(
+ (orders[user][hash] || ecrecover(sha3("\x19Ethereum Signed Message:\n32", hash),v,r,s) == user) &&
+ block.number <= expires &&
+ safeAdd(orderFills[user][hash], amount) <= amountGet
+ )) throw;
+ tradeBalances(tokenGet, amountGet, tokenGive, amountGive, user, amount);
+ orderFills[user][hash] = safeAdd(orderFills[user][hash], amount);
+ Trade(tokenGet, amount, tokenGive, amountGive * amount / amountGet, user, msg.sender);
+ }
+
+ function tradeBalances(address tokenGet, uint amountGet, address tokenGive, uint amountGive, address user, uint amount) private {
+ uint feeMakeXfer = safeMul(amount, feeMake) / (1 ether);
+ uint feeTakeXfer = safeMul(amount, feeTake) / (1 ether);
+ uint feeRebateXfer = 0;
+ if (accountLevelsAddr != 0x0) {
+ uint accountLevel = AccountLevels(accountLevelsAddr).accountLevel(user);
+ if (accountLevel==1) feeRebateXfer = safeMul(amount, feeRebate) / (1 ether);
+ if (accountLevel==2) feeRebateXfer = feeTakeXfer;
+ }
+ tokens[tokenGet][msg.sender] = safeSub(tokens[tokenGet][msg.sender], safeAdd(amount, feeTakeXfer));
+ tokens[tokenGet][user] = safeAdd(tokens[tokenGet][user], safeSub(safeAdd(amount, feeRebateXfer), feeMakeXfer));
+ tokens[tokenGet][feeAccount] = safeAdd(tokens[tokenGet][feeAccount], safeSub(safeAdd(feeMakeXfer, feeTakeXfer), feeRebateXfer));
+ tokens[tokenGive][user] = safeSub(tokens[tokenGive][user], safeMul(amountGive, amount) / amountGet);
+ tokens[tokenGive][msg.sender] = safeAdd(tokens[tokenGive][msg.sender], safeMul(amountGive, amount) / amountGet);
+ }
+
+ function testTrade(address tokenGet, uint amountGet, address tokenGive, uint amountGive, uint expires, uint nonce, address user, uint8 v, bytes32 r, bytes32 s, uint amount, address sender) constant returns(bool) {
+ if (!(
+ tokens[tokenGet][sender] >= amount &&
+ availableVolume(tokenGet, amountGet, tokenGive, amountGive, expires, nonce, user, v, r, s) >= amount
+ )) return false;
+ return true;
+ }
+
+ function availableVolume(address tokenGet, uint amountGet, address tokenGive, uint amountGive, uint expires, uint nonce, address user, uint8 v, bytes32 r, bytes32 s) constant returns(uint) {
+ bytes32 hash = sha256(this, tokenGet, amountGet, tokenGive, amountGive, expires, nonce);
+ if (!(
+ (orders[user][hash] || ecrecover(sha3("\x19Ethereum Signed Message:\n32", hash),v,r,s) == user) &&
+ block.number <= expires
+ )) return 0;
+ uint available1 = safeSub(amountGet, orderFills[user][hash]);
+ uint available2 = safeMul(tokens[tokenGive][user], amountGet) / amountGive;
+ if (available1<available2) return available1;
+ return available2;
+ }
+
+ function amountFilled(address tokenGet, uint amountGet, address tokenGive, uint amountGive, uint expires, uint nonce, address user, uint8 v, bytes32 r, bytes32 s) constant returns(uint) {
+ bytes32 hash = sha256(this, tokenGet, amountGet, tokenGive, amountGive, expires, nonce);
+ return orderFills[user][hash];
+ }
+
+ function cancelOrder(address tokenGet, uint amountGet, address tokenGive, uint amountGive, uint expires, uint nonce, uint8 v, bytes32 r, bytes32 s) {
+ bytes32 hash = sha256(this, tokenGet, amountGet, tokenGive, amountGive, expires, nonce);
+ if (!(orders[msg.sender][hash] || ecrecover(sha3("\x19Ethereum Signed Message:\n32", hash),v,r,s) == msg.sender)) throw;
+ orderFills[msg.sender][hash] = amountGet;
+ Cancel(tokenGet, amountGet, tokenGive, amountGive, expires, nonce, msg.sender, v, r, s);
+ }
+}
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);
+ });
+ });
+});
diff --git a/packages/contracts/util/crypto.ts b/packages/contracts/util/crypto.ts
index 9173df643..97b8f5643 100644
--- a/packages/contracts/util/crypto.ts
+++ b/packages/contracts/util/crypto.ts
@@ -13,6 +13,12 @@ export const crypto = {
* valid Ethereum address -> address
*/
solSHA3(args: any[]): Buffer {
+ return crypto._solHash(args, ABI.soliditySHA3);
+ },
+ solSHA256(args: any[]): Buffer {
+ return crypto._solHash(args, ABI.soliditySHA256);
+ },
+ _solHash(args: any[], hashFunction: (types: string[], values: any[]) => Buffer) {
const argTypes: string[] = [];
_.each(args, (arg, i) => {
const isNumber = _.isFinite(arg);
@@ -31,7 +37,7 @@ export const crypto = {
throw new Error(`Unable to guess arg type: ${arg}`);
}
});
- const hash = ABI.soliditySHA3(argTypes, args);
+ const hash = hashFunction(argTypes, args);
return hash;
},
};
diff --git a/packages/contracts/util/types.ts b/packages/contracts/util/types.ts
index d6e4c587d..61a19acb4 100644
--- a/packages/contracts/util/types.ts
+++ b/packages/contracts/util/types.ts
@@ -96,6 +96,9 @@ export enum ContractName {
EtherToken = 'WETH9',
MultiSigWalletWithTimeLockExceptRemoveAuthorizedAddress = 'MultiSigWalletWithTimeLockExceptRemoveAuthorizedAddress',
MaliciousToken = 'MaliciousToken',
+ AccountLevels = 'AccountLevels',
+ EtherDelta = 'EtherDelta',
+ Arbitrage = 'Arbitrage',
}
export interface Artifact {