Skip to content

Commit ae43932

Browse files
josojoNicholas Rodrigues Lordello
authored andcommitted
initial build
0 parents  commit ae43932

File tree

13 files changed

+9708
-0
lines changed

13 files changed

+9708
-0
lines changed

.eslintrc.json

Lines changed: 430 additions & 0 deletions
Large diffs are not rendered by default.

.gitattributes

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
*.sol linguist-language=Solidity

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
build
2+
node_modules

LICENSE

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
The MIT License (MIT)
2+
3+
Copyright (c) 2018 Truffle
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in all
13+
copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
SOFTWARE.
22+

README.md

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
2+
## One Block Auctions (OBA):
3+
4+
To run the project execute:
5+
6+
```
7+
yarn
8+
docker pull ethereum/solc:0.6.12
9+
yarn build
10+
```
11+
12+
To run test
13+
```
14+
yarn ganache
15+
yarn test
16+
```

package.json

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
{
2+
"name": "test",
3+
"scripts": {
4+
"build": "waffle",
5+
"test": "export NODE_ENV=test && mocha -r ts-node/register 'test/**/*.ts'",
6+
"btest": "yarn run build && yarn run test",
7+
"lint": "eslint '{src,test}/**/*.ts'",
8+
"lint:fix": "eslint --fix '{src,test}/**/*.ts'",
9+
"ganache": "ganache-cli --host 0.0.0.0 --gasLimit 12e6 --deterministic"
10+
},
11+
"dependencies": {
12+
"bn.js": "^5.1.3",
13+
"ethereumjs-abi": "^0.6.8",
14+
"ethereumjs-util": "^7.0.5",
15+
"ethers": "5.0.12"
16+
},
17+
"devDependencies": {
18+
"@ethereum-waffle/mock-contract": "^3.1.0",
19+
"@openzeppelin/contracts": "^2.5.0",
20+
"@types/chai": "^4.2.3",
21+
"@types/mocha": "^5.2.7",
22+
"@typescript-eslint/eslint-plugin": "^2.30.0",
23+
"@typescript-eslint/parser": "^2.30.0",
24+
"@uniswap/v2-core": "^1.0.1",
25+
"chai": "^4.2.0",
26+
"eslint": "^6.8.0",
27+
"eslint-plugin-import": "^2.20.2",
28+
"ethereum-waffle": "^3.1.0",
29+
"mocha": "^8.1.3",
30+
"prettier": "^2.1.1",
31+
"ts-node": "^8.9.1",
32+
"typescript": "^3.8.3"
33+
}
34+
}

src/contracts/PreAMMBatcher.sol

Lines changed: 252 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,252 @@
1+
pragma experimental ABIEncoderV2;
2+
pragma solidity ^0.5.16;
3+
import "@uniswap/v2-core/contracts/interfaces/IUniswapV2Pair.sol";
4+
import "@uniswap/v2-core/contracts/interfaces/IUniswapV2Factory.sol";
5+
6+
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
7+
import "@uniswap/v2-core/contracts/libraries/Math.sol";
8+
import "@openzeppelin/contracts/math/SafeMath.sol";
9+
10+
contract PreAMMBatcher {
11+
using SafeMath for uint256;
12+
IUniswapV2Factory uniswapFactory;
13+
14+
bytes32 public constant DOMAIN_SEPARATOR = keccak256("preBatcher-V1");
15+
mapping(address => uint8) public nonces; // probably a nonce per tokenpair would be better
16+
17+
struct Order {
18+
uint256 sellAmount;
19+
uint256 buyAmount;
20+
address sellToken;
21+
address buyToken;
22+
address owner;
23+
}
24+
25+
struct Fraction {
26+
uint256 numerator;
27+
uint256 denominator;
28+
}
29+
30+
event BatchSettlement(
31+
address token0,
32+
address token1,
33+
uint256 sellAmountToken0,
34+
uint256 sellAmountToken1
35+
);
36+
37+
constructor(IUniswapV2Factory _uniswapFactory) public {
38+
uniswapFactory = _uniswapFactory;
39+
}
40+
41+
function preBatchTrade(bytes memory order0bytes, bytes memory order1bytes)
42+
public
43+
returns (Fraction memory clearingPrice)
44+
{
45+
Order memory sellOrderToken0 = parseOrderBytes(order0bytes);
46+
Order memory sellOrderToken1 = parseOrderBytes(order1bytes);
47+
require(
48+
orderChecks(sellOrderToken0, sellOrderToken1),
49+
"orders-checks are not succesful"
50+
);
51+
receiveTradeAmounts(sellOrderToken0, sellOrderToken1);
52+
IUniswapV2Pair uniswapPool = IUniswapV2Pair(
53+
uniswapFactory.getPair(
54+
sellOrderToken0.sellToken,
55+
sellOrderToken1.sellToken
56+
)
57+
);
58+
clearingPrice = calculateSettlementPrice(
59+
sellOrderToken0,
60+
sellOrderToken1,
61+
uniswapPool
62+
);
63+
uint256 unmatchedAmountToken0 = sellOrderToken0.sellAmount.sub(
64+
sellOrderToken1.sellAmount.mul(clearingPrice.numerator).div(
65+
clearingPrice.denominator
66+
)
67+
);
68+
settleUnmatchedAmountsToUniswap(
69+
unmatchedAmountToken0,
70+
clearingPrice,
71+
sellOrderToken0,
72+
uniswapPool
73+
);
74+
payOutTradeProceedings(sellOrderToken0, sellOrderToken1);
75+
emit BatchSettlement(
76+
sellOrderToken0.sellToken,
77+
sellOrderToken1.sellToken,
78+
sellOrderToken0.sellAmount.sub(unmatchedAmountToken0),
79+
sellOrderToken1.sellAmount
80+
);
81+
}
82+
83+
function orderChecks(
84+
Order memory sellOrderToken0,
85+
Order memory sellOrderToken1
86+
) public pure returns (bool) {
87+
// later the signature verification should happen here as well
88+
return
89+
sellOrderToken0.sellToken == sellOrderToken1.buyToken &&
90+
sellOrderToken1.sellToken == sellOrderToken0.buyToken;
91+
}
92+
93+
function parseOrderBytes(bytes memory orderBytes)
94+
public
95+
returns (Order memory order)
96+
{
97+
(
98+
uint256 sellAmount,
99+
uint256 buyAmount,
100+
address sellToken,
101+
address buyToken,
102+
address owner,
103+
uint8 nonce,
104+
uint8 v,
105+
bytes32 r,
106+
bytes32 s
107+
) = abi.decode(
108+
orderBytes,
109+
(
110+
uint256,
111+
uint256,
112+
address,
113+
address,
114+
address,
115+
uint8,
116+
uint8,
117+
bytes32,
118+
bytes32
119+
)
120+
);
121+
bytes32 digest = keccak256(
122+
abi.encode(
123+
DOMAIN_SEPARATOR,
124+
sellAmount,
125+
buyAmount,
126+
sellToken,
127+
buyToken,
128+
owner,
129+
nonce
130+
)
131+
);
132+
address recoveredAddress = ecrecover(digest, v, r, s);
133+
require(
134+
recoveredAddress != address(0) && recoveredAddress == owner,
135+
"invalid_signature"
136+
);
137+
require(nonces[owner] < nonce, "nonce already used");
138+
nonces[owner] = nonce;
139+
order = Order({
140+
sellAmount: sellAmount,
141+
buyAmount: buyAmount,
142+
buyToken: buyToken,
143+
sellToken: sellToken,
144+
owner: owner
145+
});
146+
}
147+
148+
function calculateSettlementPrice(
149+
Order memory sellOrderToken0,
150+
Order memory sellOrderToken1,
151+
IUniswapV2Pair uniswapPool
152+
) public view returns (Fraction memory clearingPrice) {
153+
(uint112 reserve0, uint112 reserve1, ) = uniswapPool.getReserves();
154+
uint256 uniswapK = uint256(reserve0).mul(reserve1);
155+
// if deltaUniswapToken0 will be > 0
156+
// if(sellOrderToken1.sellAmount * serve0 / reserve1 > sellToken2Order.sellAmount)
157+
uint256 p = uniswapK.div(
158+
uint256(2).mul(uint256(reserve1).add(sellOrderToken1.sellAmount))
159+
);
160+
uint256 newReserve0 = p.add(
161+
Math.sqrt(
162+
p.mul(p).add(
163+
uniswapK.mul(sellOrderToken0.sellAmount).div(
164+
uint256(reserve1).add(sellOrderToken1.sellAmount)
165+
)
166+
)
167+
)
168+
);
169+
uint256 newReserve1 = uniswapK.div(newReserve0);
170+
clearingPrice = Fraction({
171+
numerator: newReserve0,
172+
denominator: newReserve1
173+
});
174+
require(
175+
clearingPrice.numerator.mul(sellOrderToken0.sellAmount) >=
176+
clearingPrice.denominator.mul(sellOrderToken0.buyAmount),
177+
"sellOrderToken0 price violations"
178+
);
179+
require(
180+
clearingPrice.numerator.mul(sellOrderToken1.sellAmount) >
181+
clearingPrice.denominator.mul(sellOrderToken1.buyAmount),
182+
"sellOrderToken1 price violations"
183+
);
184+
}
185+
186+
function settleUnmatchedAmountsToUniswap(
187+
uint256 unsettledDirectAmountToken0,
188+
Fraction memory clearingPrice,
189+
Order memory sellOrderToken0,
190+
IUniswapV2Pair uniswapPool
191+
) internal {
192+
require(
193+
IERC20(sellOrderToken0.sellToken).transfer(
194+
address(uniswapPool),
195+
unsettledDirectAmountToken0
196+
),
197+
"transfer to uniswap failed"
198+
);
199+
uniswapPool.swap(
200+
0,
201+
unsettledDirectAmountToken0
202+
.mul(clearingPrice.denominator)
203+
.mul(997)
204+
.div(clearingPrice.numerator)
205+
.div(1000),
206+
address(this),
207+
""
208+
);
209+
}
210+
211+
function receiveTradeAmounts(
212+
Order memory sellOrderToken0,
213+
Order memory sellOrderToken1
214+
) internal {
215+
require(
216+
IERC20(sellOrderToken0.sellToken).transferFrom(
217+
sellOrderToken0.owner,
218+
address(this),
219+
sellOrderToken0.sellAmount
220+
),
221+
"transferFrom for token0 was not succesful"
222+
);
223+
require(
224+
IERC20(sellOrderToken1.sellToken).transferFrom(
225+
sellOrderToken1.owner,
226+
address(this),
227+
sellOrderToken1.sellAmount
228+
),
229+
"transferFrom for token1 was not succesful"
230+
);
231+
}
232+
233+
function payOutTradeProceedings(
234+
Order memory sellOrderToken0,
235+
Order memory sellOrderToken1
236+
) internal {
237+
require(
238+
IERC20(sellOrderToken0.sellToken).transfer(
239+
sellOrderToken1.owner,
240+
IERC20(sellOrderToken0.sellToken).balanceOf(address(this))
241+
),
242+
"final token transfer failed"
243+
);
244+
require(
245+
IERC20(sellOrderToken0.buyToken).transfer(
246+
sellOrderToken0.owner,
247+
IERC20(sellOrderToken0.buyToken).balanceOf(address(this))
248+
),
249+
"final token1 transfer failed"
250+
);
251+
}
252+
}

src/js/orders.spec.ts

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
2+
import BN from 'bn.js';
3+
const abi = require('ethereumjs-abi');
4+
import {utils, Wallet} from 'ethers';
5+
import {ecsign} from 'ethereumjs-util';
6+
7+
const DOMAIN_SEPARATOR = '0x24a654ed47680d6a76f087ec92b3a0f0fe4c9c82c26bff3bb22dffe0f120c7f0';
8+
9+
export class Order {
10+
sellAmount: BN;
11+
buyAmount: BN;
12+
sellToken: string;
13+
buyToken: string;
14+
wallet: Wallet;
15+
nonce: BN;
16+
17+
constructor(sellAmount: BN | number, buyAmount: BN | number, sellToken: string,
18+
buyToken: string, wallet: Wallet, nonce: BN | number) {
19+
this.sellAmount = new BN(sellAmount);
20+
this.buyAmount = new BN(buyAmount);
21+
this.sellToken = sellToken;
22+
this.buyToken = buyToken;
23+
this.wallet = wallet;
24+
this.nonce = new BN(nonce);
25+
}
26+
27+
encode(): string {
28+
const digest = this.getOrderDigest();
29+
const {v, r, s} = ecsign(Buffer.from(digest.slice(2), 'hex'), Buffer.from(this.wallet.privateKey.slice(2), 'hex'));
30+
return abi.rawEncode(['uint256', 'uint256', 'address', 'address',
31+
'address', 'uint8', 'uint8', 'bytes32', 'bytes32'],
32+
[this.sellAmount.toString(), this.buyAmount.toString(), this.sellToken, this.buyToken,
33+
this.wallet.address, this.nonce.toString(), v, utils.hexlify(r), utils.hexlify(s)]);
34+
}
35+
36+
getOrderDigest(): string {
37+
return utils.keccak256(
38+
utils.defaultAbiCoder.encode(
39+
['bytes32', 'uint256', 'uint256', 'address', 'address', 'address', 'uint8'],
40+
[DOMAIN_SEPARATOR, this.sellAmount.toString(), this.buyAmount.toString(), this.sellToken,
41+
this.buyToken, this.wallet.address, this.nonce.toString()]
42+
));
43+
}
44+
}

0 commit comments

Comments
 (0)