Skip to content
This repository was archived by the owner on Apr 17, 2023. It is now read-only.

Commit

Permalink
Add UniswapV2 support (#375)
Browse files Browse the repository at this point in the history
* Add UniswapV2 support

* Bump package version

* Test fix

* Fix comment

* Fix comment

* Advance time to fix test fail
  • Loading branch information
HardlyDifficult authored Jun 5, 2020
1 parent c9040ff commit 379aa2c
Show file tree
Hide file tree
Showing 13 changed files with 189 additions and 7 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,5 @@ build/
/.coverage_contracts/
coverage.json
yarn.lock

tenderly.yaml
5 changes: 3 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "hardlydifficult-eth",
"version": "1.0.0",
"version": "1.1.0",
"description": "A collection of reusable contracts and Javascript helpers for Ethereum.",
"scripts": {
"build": "npm run compile",
Expand All @@ -9,7 +9,7 @@
"lint": "solium -d contracts --fix && eslint . --fix",
"lintNoFix": "solium -d contracts && eslint .",
"coverage": "truffle run coverage && cat coverage/lcov.info | coveralls",
"ganache": "ganache-cli"
"ganache": "ganache-cli -e 10000000000"
},
"files": [
"build",
Expand Down Expand Up @@ -44,6 +44,7 @@
},
"devDependencies": {
"@codechecks/client": "0.1.10",
"@openzeppelin/test-helpers": "0.5.6",
"coveralls": "3.1.0",
"eslint": "7.1.0",
"eslint-config-prettier": "6.11.0",
Expand Down
3 changes: 2 additions & 1 deletion src/protocols/index.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
module.exports = {
unlock: require("./unlock"),
uniswap: require("./uniswap"),
uniswapV1: require("./uniswapV1"),
uniswapV2: require("./uniswapV2"),
};
2 changes: 1 addition & 1 deletion src/protocols/uniswap.js → src/protocols/uniswapV1.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
const { truffleContract } = require("../helpers");
const uniswapJson = require("./uniswap.json");
const uniswapJson = require("./uniswapV1.json");

module.exports = {
mainnetFactoryAddress: "0xc0a47dfe034b400b47bdad5fecda2621de6c4d95",
Expand Down
File renamed without changes.
47 changes: 47 additions & 0 deletions src/protocols/uniswapV2.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
const { truffleContract } = require("../helpers");
const uniswapJson = require("./uniswapV2.json");

module.exports = {
mainnetFactoryAddress: "0x5C69bEe701ef814a2B6a3EDD4B1652CB9cc5aA6f",
mainnetRouterAddress: "0xf164fC0Ec4E93095b804a4795bBe1e041497b92a",
/**
* Deploys the Uniswap factory and router.
* @param feeSetter allows a fee to be enabled. May be address(0)
* @param wethAddress the address of the WETH token (used by the router)
* @returns the Uniswap router as a truffleContract object
* This is useful for testing on Ganache
*/
deploy: async (web3, owner, feeSetter, wethAddress) => {
const factory = await truffleContract.new(
web3,
uniswapJson.abi,
`0x${uniswapJson.bytecode.replace(/0x/, "")}`,
owner,
feeSetter
);

const router = await truffleContract.new(
web3,
uniswapJson.router.abi,
`0x${uniswapJson.router.bytecode.replace(/0x/, "")}`,
owner,
factory.address,
wethAddress
);

return router;
},
getFactory: async (web3, factoryAddress) => {
return await truffleContract.at(web3, uniswapJson.abi, factoryAddress);
},
getRouter: async (web3, routerAddress) => {
return await truffleContract.at(
web3,
uniswapJson.router.abi,
routerAddress
);
},
getPair: async (web3, pairAddress) => {
return await truffleContract.at(web3, uniswapJson.pair.abi, pairAddress);
},
};
11 changes: 11 additions & 0 deletions src/protocols/uniswapV2.json

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions src/tokens/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,5 @@ module.exports = {
dai: require("./dai"),
sai: require("./sai"),
usdc: require("./usdc"),
weth: require("./weth"),
};
22 changes: 22 additions & 0 deletions src/tokens/weth.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
const { truffleContract } = require("../helpers");
const wethJson = require("./weth.json");

async function getToken(web3, tokenAddress) {
return await truffleContract.at(web3, wethJson.abi, tokenAddress);
}

module.exports = {
mainnetAddress: "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2",
decimals: 18,
deploy: async (web3, from) => {
const result = await truffleContract.new(
web3,
wethJson.abi,
`0x${wethJson.bytecode.replace(/0x/, "")}`,
from
);

return result;
},
getToken,
};
4 changes: 4 additions & 0 deletions src/tokens/weth.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"abi": [{"constant":true,"inputs":[],"name":"name","outputs":[{"name":"","type":"string"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"guy","type":"address"},{"name":"wad","type":"uint256"}],"name":"approve","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"totalSupply","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"src","type":"address"},{"name":"dst","type":"address"},{"name":"wad","type":"uint256"}],"name":"transferFrom","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"wad","type":"uint256"}],"name":"withdraw","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"decimals","outputs":[{"name":"","type":"uint8"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"","type":"address"}],"name":"balanceOf","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"symbol","outputs":[{"name":"","type":"string"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"dst","type":"address"},{"name":"wad","type":"uint256"}],"name":"transfer","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[],"name":"deposit","outputs":[],"payable":true,"stateMutability":"payable","type":"function"},{"constant":true,"inputs":[{"name":"","type":"address"},{"name":"","type":"address"}],"name":"allowance","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"payable":true,"stateMutability":"payable","type":"fallback"},{"anonymous":false,"inputs":[{"indexed":true,"name":"src","type":"address"},{"indexed":true,"name":"guy","type":"address"},{"indexed":false,"name":"wad","type":"uint256"}],"name":"Approval","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"src","type":"address"},{"indexed":true,"name":"dst","type":"address"},{"indexed":false,"name":"wad","type":"uint256"}],"name":"Transfer","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"dst","type":"address"},{"indexed":false,"name":"wad","type":"uint256"}],"name":"Deposit","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"src","type":"address"},{"indexed":false,"name":"wad","type":"uint256"}],"name":"Withdrawal","type":"event"}],
"bytecode": "60606040526040805190810160405280600d81526020017f57726170706564204574686572000000000000000000000000000000000000008152506000908051906020019061004f9291906100c8565b506040805190810160405280600481526020017f57455448000000000000000000000000000000000000000000000000000000008152506001908051906020019061009b9291906100c8565b506012600260006101000a81548160ff021916908360ff16021790555034156100c357600080fd5b61016d565b828054600181600116156101000203166002900490600052602060002090601f016020900481019282601f1061010957805160ff1916838001178555610137565b82800160010185558215610137579182015b8281111561013657825182559160200191906001019061011b565b5b5090506101449190610148565b5090565b61016a91905b8082111561016657600081600090555060010161014e565b5090565b90565b610c348061017c6000396000f3006060604052600436106100af576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff16806306fdde03146100b9578063095ea7b31461014757806318160ddd146101a157806323b872dd146101ca5780632e1a7d4d14610243578063313ce5671461026657806370a082311461029557806395d89b41146102e2578063a9059cbb14610370578063d0e30db0146103ca578063dd62ed3e146103d4575b6100b7610440565b005b34156100c457600080fd5b6100cc6104dd565b6040518080602001828103825283818151815260200191508051906020019080838360005b8381101561010c5780820151818401526020810190506100f1565b50505050905090810190601f1680156101395780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b341561015257600080fd5b610187600480803573ffffffffffffffffffffffffffffffffffffffff1690602001909190803590602001909190505061057b565b604051808215151515815260200191505060405180910390f35b34156101ac57600080fd5b6101b461066d565b6040518082815260200191505060405180910390f35b34156101d557600080fd5b610229600480803573ffffffffffffffffffffffffffffffffffffffff1690602001909190803573ffffffffffffffffffffffffffffffffffffffff1690602001909190803590602001909190505061068c565b604051808215151515815260200191505060405180910390f35b341561024e57600080fd5b61026460048080359060200190919050506109d9565b005b341561027157600080fd5b610279610b05565b604051808260ff1660ff16815260200191505060405180910390f35b34156102a057600080fd5b6102cc600480803573ffffffffffffffffffffffffffffffffffffffff16906020019091905050610b18565b6040518082815260200191505060405180910390f35b34156102ed57600080fd5b6102f5610b30565b6040518080602001828103825283818151815260200191508051906020019080838360005b8381101561033557808201518184015260208101905061031a565b50505050905090810190601f1680156103625780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b341561037b57600080fd5b6103b0600480803573ffffffffffffffffffffffffffffffffffffffff16906020019091908035906020019091905050610bce565b604051808215151515815260200191505060405180910390f35b6103d2610440565b005b34156103df57600080fd5b61042a600480803573ffffffffffffffffffffffffffffffffffffffff1690602001909190803573ffffffffffffffffffffffffffffffffffffffff16906020019091905050610be3565b6040518082815260200191505060405180910390f35b34600360003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020600082825401925050819055503373ffffffffffffffffffffffffffffffffffffffff167fe1fffcc4923d04b559f4d29a8bfc6cda04eb5b0d3c460751c2402c5c5cc9109c346040518082815260200191505060405180910390a2565b60008054600181600116156101000203166002900480601f0160208091040260200160405190810160405280929190818152602001828054600181600116156101000203166002900480156105735780601f1061054857610100808354040283529160200191610573565b820191906000526020600020905b81548152906001019060200180831161055657829003601f168201915b505050505081565b600081600460003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020819055508273ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff167f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925846040518082815260200191505060405180910390a36001905092915050565b60003073ffffffffffffffffffffffffffffffffffffffff1631905090565b600081600360008673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054101515156106dc57600080fd5b3373ffffffffffffffffffffffffffffffffffffffff168473ffffffffffffffffffffffffffffffffffffffff16141580156107b457507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff600460008673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000205414155b156108cf5781600460008673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020541015151561084457600080fd5b81600460008673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020600082825403925050819055505b81600360008673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000206000828254039250508190555081600360008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020600082825401925050819055508273ffffffffffffffffffffffffffffffffffffffff168473ffffffffffffffffffffffffffffffffffffffff167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef846040518082815260200191505060405180910390a3600190509392505050565b80600360003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000205410151515610a2757600080fd5b80600360003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020600082825403925050819055503373ffffffffffffffffffffffffffffffffffffffff166108fc829081150290604051600060405180830381858888f193505050501515610ab457600080fd5b3373ffffffffffffffffffffffffffffffffffffffff167f7fcf532c15f0a6db0bd6d0e038bea71d30d808c7d98cb3bf7268a95bf5081b65826040518082815260200191505060405180910390a250565b600260009054906101000a900460ff1681565b60036020528060005260406000206000915090505481565b60018054600181600116156101000203166002900480601f016020809104026020016040519081016040528092919081815260200182805460018160011615610100020316600290048015610bc65780601f10610b9b57610100808354040283529160200191610bc6565b820191906000526020600020905b815481529060010190602001808311610ba957829003601f168201915b505050505081565b6000610bdb33848461068c565b905092915050565b60046020528160005260406000206020528060005260406000206000915091505054815600a165627a7a72305820deb4c2ccab3c2fdca32ab3f46728389c2fe2c165d5fafa07661e4e004f6c344a0029"
}
6 changes: 3 additions & 3 deletions test/protocols/uniswap.js → test/protocols/uniswapV1.js
Original file line number Diff line number Diff line change
@@ -1,20 +1,20 @@
const { tokens, protocols } = require("../..");

contract("protocols / uniswap", (accounts) => {
contract("protocols / uniswapV1", (accounts) => {
const protocolOwner = accounts[0];
let uniswap;
let sai;

before(async () => {
uniswap = await protocols.uniswap.deploy(web3, protocolOwner);
uniswap = await protocols.uniswapV1.deploy(web3, protocolOwner);
sai = await tokens.sai.deploy(web3, protocolOwner);
});

it("Can create an exchange and add liquidity", async () => {
const tx = await uniswap.createExchange(sai.address, {
from: protocolOwner,
});
const exchange = await protocols.uniswap.getExchange(
const exchange = await protocols.uniswapV1.getExchange(
web3,
tx.logs[0].args.exchange
);
Expand Down
75 changes: 75 additions & 0 deletions test/protocols/uniswapV2.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
const { constants, tokens, protocols } = require("../..");
const { time } = require("@openzeppelin/test-helpers");

contract("protocols / uniswapV2", (accounts) => {
const protocolOwner = accounts[9];
let uniswapRouter;
let dai, weth;

beforeEach(async () => {
dai = await tokens.dai.deploy(web3, protocolOwner);
weth = await tokens.weth.deploy(web3, protocolOwner);
uniswapRouter = await protocols.uniswapV2.deploy(
web3,
protocolOwner,
constants.ZERO_ADDRESS,
weth.address
);
});

describe("create a pair", () => {
const liquidityOwner = accounts[1];

beforeEach(async () => {
await dai.mint(liquidityOwner, web3.utils.toWei("100000", "ether"), {
from: protocolOwner,
});
await dai.approve(uniswapRouter.address, constants.MAX_UINT, {
from: liquidityOwner,
});
await uniswapRouter.addLiquidityETH(
dai.address,
web3.utils.toWei("2000", "ether"),
"1",
"1",
liquidityOwner,
constants.MAX_UINT,
{ from: liquidityOwner, value: web3.utils.toWei("10", "ether") }
);
});

it("can read exchange rate", async () => {
const rate = (
await uniswapRouter.getAmountsOut(web3.utils.toWei("1", "ether"), [
dai.address,
weth.address,
])
)[1];
// 1 DAI is worth ~0.004 ETH
assert.equal(rate.toString(), "4982516215666490");
});

describe("swap tokens", () => {
const trader = accounts[2];

beforeEach(async () => {
// Advancing time to avoid an intermittent test fail
await time.increase(time.duration.hours(1));

await uniswapRouter.swapExactETHForTokens(
1,
[weth.address, dai.address],
trader,
constants.MAX_UINT,
{ from: trader, value: web3.utils.toWei("1", "ether") }
);
});

it("got dai from the swap", async () => {
const balance = await dai.balanceOf(trader);
// 1 ETH is worth ~180 DAI
assert.equal(balance.toString(), "181322178776029826316");
});
});
});
});
18 changes: 18 additions & 0 deletions test/tokens/weth.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
const { tokens } = require("../..");

contract("tokens / WETH", (accounts) => {
let token;

beforeEach(async () => {
// The from address is not relevant for this token
token = await tokens.weth.deploy(web3, accounts[9]);
});

it("Can deposit", async () => {
const account = accounts[1];
const value = web3.utils.toWei("4.2", "ether");
await token.deposit({ from: account, value });
const balance = await token.balanceOf(account);
assert.equal(balance.toString(), value);
});
});

0 comments on commit 379aa2c

Please sign in to comment.