Skip to content

Commit f20102d

Browse files
DanielVFFranck Chastagnol
and
Franck Chastagnol
authored
Flipper for zero slippage trading of OUSD for stablecoins (#558)
* Add flipper contract allow zero slippage trading of OUSD for stablecoins. * Comments on methods, removing unneeded constant. * Hide errors about tether not being ERC20 complient. * Deploy script 15 + Prod flipper contract * Fix deploy 15 logging * Fix slither error * Rinkeby deploy * mainnet abi * Fix comments in contract files Co-authored-by: Franck Chastagnol <[email protected]>
1 parent 4a89d54 commit f20102d

17 files changed

+3956
-1554
lines changed
Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
pragma solidity 0.5.11;
2+
3+
import "../governance/Governable.sol";
4+
import "../token/OUSD.sol";
5+
import "../interfaces/Tether.sol";
6+
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
7+
import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/SafeERC20.sol";
8+
9+
// Contract to exchange usdt, usdc, dai from and to ousd.
10+
// - 1 to 1. No slippage
11+
// - Optimized for low gas usage
12+
// - No guarantee of availability
13+
14+
15+
contract Flipper is Governable {
16+
using SafeERC20 for IERC20;
17+
18+
uint256 constant MAXIMUM_PER_TRADE = (25000 * 1e18);
19+
20+
// Saves approx 4K gas per swap by using hardcoded addresses.
21+
IERC20 dai = IERC20(0x6B175474E89094C44Da98b954EedeAC495271d0F);
22+
OUSD constant ousd = OUSD(0x2A8e1E676Ec238d8A992307B495b45B3fEAa5e86);
23+
IERC20 usdc = IERC20(0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48);
24+
Tether constant usdt = Tether(0xdAC17F958D2ee523a2206206994597C13D831ec7);
25+
26+
// -----------
27+
// Constructor
28+
// -----------
29+
constructor() public {}
30+
31+
// -----------------
32+
// Trading functions
33+
// -----------------
34+
35+
/// @notice Purchase OUSD with Dai
36+
/// @param amount Amount of OUSD to purchase, in 18 fixed decimals.
37+
function buyOusdWithDai(uint256 amount) external {
38+
require(amount <= MAXIMUM_PER_TRADE, "Amount too large");
39+
require(dai.transferFrom(msg.sender, address(this), amount));
40+
require(ousd.transfer(msg.sender, amount));
41+
}
42+
43+
/// @notice Sell OUSD for Dai
44+
/// @param amount Amount of OUSD to sell, in 18 fixed decimals.
45+
function sellOusdForDai(uint256 amount) external {
46+
require(amount <= MAXIMUM_PER_TRADE, "Amount too large");
47+
require(dai.transfer(msg.sender, amount));
48+
require(ousd.transferFrom(msg.sender, address(this), amount));
49+
}
50+
51+
/// @notice Purchase OUSD with USDC
52+
/// @param amount Amount of OUSD to purchase, in 18 fixed decimals.
53+
function buyOusdWithUsdc(uint256 amount) external {
54+
require(amount <= MAXIMUM_PER_TRADE, "Amount too large");
55+
// Potential rounding error is an intentional tradeoff
56+
require(usdc.transferFrom(msg.sender, address(this), amount / 1e12));
57+
require(ousd.transfer(msg.sender, amount));
58+
}
59+
60+
/// @notice Sell OUSD for USDC
61+
/// @param amount Amount of OUSD to sell, in 18 fixed decimals.
62+
function sellOusdForUsdc(uint256 amount) external {
63+
require(amount <= MAXIMUM_PER_TRADE, "Amount too large");
64+
require(usdc.transfer(msg.sender, amount / 1e12));
65+
require(ousd.transferFrom(msg.sender, address(this), amount));
66+
}
67+
68+
/// @notice Purchase OUSD with USDT
69+
/// @param amount Amount of OUSD to purchase, in 18 fixed decimals.
70+
function buyOusdWithUsdt(uint256 amount) external {
71+
require(amount <= MAXIMUM_PER_TRADE, "Amount too large");
72+
// Potential rounding error is an intentional tradeoff
73+
// USDT does not return a boolean and reverts,
74+
// so no need for a require.
75+
usdt.transferFrom(msg.sender, address(this), amount / 1e12);
76+
require(ousd.transfer(msg.sender, amount));
77+
}
78+
79+
/// @notice Sell OUSD for USDT
80+
/// @param amount Amount of OUSD to sell, in 18 fixed decimals.
81+
function sellOusdForUsdt(uint256 amount) external {
82+
require(amount <= MAXIMUM_PER_TRADE, "Amount too large");
83+
// USDT does not return a boolean and reverts,
84+
// so no need for a require.
85+
usdt.transfer(msg.sender, amount / 1e12);
86+
require(ousd.transferFrom(msg.sender, address(this), amount));
87+
}
88+
89+
// --------------------
90+
// Governance functions
91+
// --------------------
92+
93+
/// @dev Opting into yield reduces the gas cost per transfer by about 4K, since
94+
/// ousd needs to do less accounting and one less storage write.
95+
function rebaseOptIn() external onlyGovernor nonReentrant {
96+
ousd.rebaseOptIn();
97+
}
98+
99+
/// @notice Owner function to withdraw a specific amount of a token
100+
function withdraw(address token, uint256 amount)
101+
external
102+
onlyGovernor
103+
nonReentrant
104+
{
105+
IERC20(token).safeTransfer(_governor(), amount);
106+
}
107+
108+
/// @notice Owner function to withdraw all tradable tokens
109+
/// @dev Equivalent to "pausing" the contract.
110+
function withdrawAll() external onlyGovernor nonReentrant {
111+
IERC20(dai).safeTransfer(_governor(), dai.balanceOf(address(this)));
112+
IERC20(ousd).safeTransfer(_governor(), ousd.balanceOf(address(this)));
113+
IERC20(address(usdt)).safeTransfer(
114+
_governor(),
115+
usdt.balanceOf(address(this))
116+
);
117+
IERC20(usdc).safeTransfer(_governor(), usdc.balanceOf(address(this)));
118+
}
119+
}
Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
pragma solidity 0.5.11;
2+
3+
import "../governance/Governable.sol";
4+
import "../token/OUSD.sol";
5+
import "../interfaces/Tether.sol";
6+
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
7+
import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/SafeERC20.sol";
8+
9+
// Contract to exchange usdt, usdc, dai from and to ousd.
10+
// - 1 to 1. No slippage
11+
// - Optimized for low gas usage
12+
// - No guarantee of availability
13+
14+
15+
contract FlipperDev is Governable {
16+
using SafeERC20 for IERC20;
17+
18+
uint256 constant MAXIMUM_PER_TRADE = (25000 * 1e18);
19+
20+
// Settable coin addresses allow easy testing and use of mock currencies.
21+
IERC20 dai = IERC20(0);
22+
OUSD ousd = OUSD(0);
23+
IERC20 usdc = IERC20(0);
24+
Tether usdt = Tether(0);
25+
26+
// ---------------------
27+
// Dev constructor
28+
// ---------------------
29+
constructor(
30+
address dai_,
31+
address ousd_,
32+
address usdc_,
33+
address usdt_
34+
) public {
35+
dai = IERC20(dai_);
36+
ousd = OUSD(ousd_);
37+
usdc = IERC20(usdc_);
38+
usdt = Tether(usdt_);
39+
require(address(ousd) != address(0));
40+
require(address(dai) != address(0));
41+
require(address(usdc) != address(0));
42+
require(address(usdt) != address(0));
43+
}
44+
45+
// -----------------
46+
// Trading functions
47+
// -----------------
48+
49+
/// @notice Purchase OUSD with Dai
50+
/// @param amount Amount of OUSD to purchase, in 18 fixed decimals.
51+
function buyOusdWithDai(uint256 amount) external {
52+
require(amount <= MAXIMUM_PER_TRADE, "Amount too large");
53+
require(dai.transferFrom(msg.sender, address(this), amount));
54+
require(ousd.transfer(msg.sender, amount));
55+
}
56+
57+
/// @notice Sell OUSD for Dai
58+
/// @param amount Amount of OUSD to sell, in 18 fixed decimals.
59+
function sellOusdForDai(uint256 amount) external {
60+
require(amount <= MAXIMUM_PER_TRADE, "Amount too large");
61+
require(dai.transfer(msg.sender, amount));
62+
require(ousd.transferFrom(msg.sender, address(this), amount));
63+
}
64+
65+
/// @notice Purchase OUSD with USDC
66+
/// @param amount Amount of OUSD to purchase, in 18 fixed decimals.
67+
function buyOusdWithUsdc(uint256 amount) external {
68+
require(amount <= MAXIMUM_PER_TRADE, "Amount too large");
69+
// Potential rounding error is an intentional tradeoff
70+
require(usdc.transferFrom(msg.sender, address(this), amount / 1e12));
71+
require(ousd.transfer(msg.sender, amount));
72+
}
73+
74+
/// @notice Sell OUSD for USDC
75+
/// @param amount Amount of OUSD to sell, in 18 fixed decimals.
76+
function sellOusdForUsdc(uint256 amount) external {
77+
require(amount <= MAXIMUM_PER_TRADE, "Amount too large");
78+
require(usdc.transfer(msg.sender, amount / 1e12));
79+
require(ousd.transferFrom(msg.sender, address(this), amount));
80+
}
81+
82+
/// @notice Purchase OUSD with USDT
83+
/// @param amount Amount of OUSD to purchase, in 18 fixed decimals.
84+
function buyOusdWithUsdt(uint256 amount) external {
85+
require(amount <= MAXIMUM_PER_TRADE, "Amount too large");
86+
// Potential rounding error is an intentional tradeoff
87+
// USDT does not return a boolean and reverts,
88+
// so no need for a require.
89+
usdt.transferFrom(msg.sender, address(this), amount / 1e12);
90+
require(ousd.transfer(msg.sender, amount));
91+
}
92+
93+
/// @notice Sell OUSD for USDT
94+
/// @param amount Amount of OUSD to sell, in 18 fixed decimals.
95+
function sellOusdForUsdt(uint256 amount) external {
96+
require(amount <= MAXIMUM_PER_TRADE, "Amount too large");
97+
// USDT does not return a boolean and reverts,
98+
// so no need for a require.
99+
usdt.transfer(msg.sender, amount / 1e12);
100+
require(ousd.transferFrom(msg.sender, address(this), amount));
101+
}
102+
103+
// --------------------
104+
// Governance functions
105+
// --------------------
106+
107+
/// @dev Opting into yield reduces the gas cost per transfer by about 4K, since
108+
/// ousd needs to do less accounting and one less storage write.
109+
function rebaseOptIn() external onlyGovernor nonReentrant {
110+
ousd.rebaseOptIn();
111+
}
112+
113+
/// @notice Owner function to withdraw a specific amount of a token
114+
function withdraw(address token, uint256 amount)
115+
external
116+
onlyGovernor
117+
nonReentrant
118+
{
119+
IERC20(token).safeTransfer(_governor(), amount);
120+
}
121+
122+
/// @notice Owner function to withdraw all tradable tokens
123+
/// @dev Equivalent to "pausing" the contract.
124+
function withdrawAll() external onlyGovernor nonReentrant {
125+
IERC20(dai).safeTransfer(_governor(), dai.balanceOf(address(this)));
126+
IERC20(ousd).safeTransfer(_governor(), ousd.balanceOf(address(this)));
127+
IERC20(address(usdt)).safeTransfer(
128+
_governor(),
129+
usdt.balanceOf(address(this))
130+
);
131+
IERC20(usdc).safeTransfer(_governor(), usdc.balanceOf(address(this)));
132+
}
133+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
pragma solidity 0.5.11;
2+
3+
interface Tether {
4+
function transfer(address to, uint256 value) external;
5+
6+
function transferFrom(
7+
address from,
8+
address to,
9+
uint256 value
10+
) external;
11+
12+
function balanceOf(address) external returns (uint256);
13+
}

contracts/deploy/001_core.js

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -427,6 +427,24 @@ const deployCore = async () => {
427427
log("Initialized OUSD");
428428
};
429429

430+
// Deploy the Flipper trading contract
431+
const deployFlipper = async () => {
432+
const assetAddresses = await getAssetAddresses(deployments);
433+
const { governorAddr } = await hre.getNamedAccounts();
434+
const sGovernor = await ethers.provider.getSigner(governorAddr);
435+
const ousd = await ethers.getContract("OUSDProxy");
436+
437+
await deployWithConfirmation("FlipperDev", [
438+
assetAddresses.DAI,
439+
ousd.address,
440+
assetAddresses.USDC,
441+
assetAddresses.USDT,
442+
]);
443+
const flipper = await ethers.getContract("FlipperDev");
444+
await withConfirmation(flipper.transferGovernance(governorAddr));
445+
await withConfirmation(flipper.connect(sGovernor).claimGovernance());
446+
};
447+
430448
const main = async () => {
431449
console.log("Running 001_core deployment...");
432450
await deployOracles();
@@ -435,6 +453,7 @@ const main = async () => {
435453
await deployAaveStrategy();
436454
await deployThreePoolStrategy();
437455
await configureVault();
456+
await deployFlipper();
438457
console.log("001_core deploy done.");
439458
return true;
440459
};

0 commit comments

Comments
 (0)