Skip to content

Commit

Permalink
Flipper for zero slippage trading of OUSD for stablecoins (#558)
Browse files Browse the repository at this point in the history
* 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]>
  • Loading branch information
DanielVF and Franck Chastagnol authored Feb 16, 2021
1 parent 4a89d54 commit f20102d
Show file tree
Hide file tree
Showing 17 changed files with 3,956 additions and 1,554 deletions.
119 changes: 119 additions & 0 deletions contracts/contracts/flipper/Flipper.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
pragma solidity 0.5.11;

import "../governance/Governable.sol";
import "../token/OUSD.sol";
import "../interfaces/Tether.sol";
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/SafeERC20.sol";

// Contract to exchange usdt, usdc, dai from and to ousd.
// - 1 to 1. No slippage
// - Optimized for low gas usage
// - No guarantee of availability


contract Flipper is Governable {
using SafeERC20 for IERC20;

uint256 constant MAXIMUM_PER_TRADE = (25000 * 1e18);

// Saves approx 4K gas per swap by using hardcoded addresses.
IERC20 dai = IERC20(0x6B175474E89094C44Da98b954EedeAC495271d0F);
OUSD constant ousd = OUSD(0x2A8e1E676Ec238d8A992307B495b45B3fEAa5e86);
IERC20 usdc = IERC20(0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48);
Tether constant usdt = Tether(0xdAC17F958D2ee523a2206206994597C13D831ec7);

// -----------
// Constructor
// -----------
constructor() public {}

// -----------------
// Trading functions
// -----------------

/// @notice Purchase OUSD with Dai
/// @param amount Amount of OUSD to purchase, in 18 fixed decimals.
function buyOusdWithDai(uint256 amount) external {
require(amount <= MAXIMUM_PER_TRADE, "Amount too large");
require(dai.transferFrom(msg.sender, address(this), amount));
require(ousd.transfer(msg.sender, amount));
}

/// @notice Sell OUSD for Dai
/// @param amount Amount of OUSD to sell, in 18 fixed decimals.
function sellOusdForDai(uint256 amount) external {
require(amount <= MAXIMUM_PER_TRADE, "Amount too large");
require(dai.transfer(msg.sender, amount));
require(ousd.transferFrom(msg.sender, address(this), amount));
}

/// @notice Purchase OUSD with USDC
/// @param amount Amount of OUSD to purchase, in 18 fixed decimals.
function buyOusdWithUsdc(uint256 amount) external {
require(amount <= MAXIMUM_PER_TRADE, "Amount too large");
// Potential rounding error is an intentional tradeoff
require(usdc.transferFrom(msg.sender, address(this), amount / 1e12));
require(ousd.transfer(msg.sender, amount));
}

/// @notice Sell OUSD for USDC
/// @param amount Amount of OUSD to sell, in 18 fixed decimals.
function sellOusdForUsdc(uint256 amount) external {
require(amount <= MAXIMUM_PER_TRADE, "Amount too large");
require(usdc.transfer(msg.sender, amount / 1e12));
require(ousd.transferFrom(msg.sender, address(this), amount));
}

/// @notice Purchase OUSD with USDT
/// @param amount Amount of OUSD to purchase, in 18 fixed decimals.
function buyOusdWithUsdt(uint256 amount) external {
require(amount <= MAXIMUM_PER_TRADE, "Amount too large");
// Potential rounding error is an intentional tradeoff
// USDT does not return a boolean and reverts,
// so no need for a require.
usdt.transferFrom(msg.sender, address(this), amount / 1e12);
require(ousd.transfer(msg.sender, amount));
}

/// @notice Sell OUSD for USDT
/// @param amount Amount of OUSD to sell, in 18 fixed decimals.
function sellOusdForUsdt(uint256 amount) external {
require(amount <= MAXIMUM_PER_TRADE, "Amount too large");
// USDT does not return a boolean and reverts,
// so no need for a require.
usdt.transfer(msg.sender, amount / 1e12);
require(ousd.transferFrom(msg.sender, address(this), amount));
}

// --------------------
// Governance functions
// --------------------

/// @dev Opting into yield reduces the gas cost per transfer by about 4K, since
/// ousd needs to do less accounting and one less storage write.
function rebaseOptIn() external onlyGovernor nonReentrant {
ousd.rebaseOptIn();
}

/// @notice Owner function to withdraw a specific amount of a token
function withdraw(address token, uint256 amount)
external
onlyGovernor
nonReentrant
{
IERC20(token).safeTransfer(_governor(), amount);
}

/// @notice Owner function to withdraw all tradable tokens
/// @dev Equivalent to "pausing" the contract.
function withdrawAll() external onlyGovernor nonReentrant {
IERC20(dai).safeTransfer(_governor(), dai.balanceOf(address(this)));
IERC20(ousd).safeTransfer(_governor(), ousd.balanceOf(address(this)));
IERC20(address(usdt)).safeTransfer(
_governor(),
usdt.balanceOf(address(this))
);
IERC20(usdc).safeTransfer(_governor(), usdc.balanceOf(address(this)));
}
}
133 changes: 133 additions & 0 deletions contracts/contracts/flipper/FlipperDev.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
pragma solidity 0.5.11;

import "../governance/Governable.sol";
import "../token/OUSD.sol";
import "../interfaces/Tether.sol";
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/SafeERC20.sol";

// Contract to exchange usdt, usdc, dai from and to ousd.
// - 1 to 1. No slippage
// - Optimized for low gas usage
// - No guarantee of availability


contract FlipperDev is Governable {
using SafeERC20 for IERC20;

uint256 constant MAXIMUM_PER_TRADE = (25000 * 1e18);

// Settable coin addresses allow easy testing and use of mock currencies.
IERC20 dai = IERC20(0);
OUSD ousd = OUSD(0);
IERC20 usdc = IERC20(0);
Tether usdt = Tether(0);

// ---------------------
// Dev constructor
// ---------------------
constructor(
address dai_,
address ousd_,
address usdc_,
address usdt_
) public {
dai = IERC20(dai_);
ousd = OUSD(ousd_);
usdc = IERC20(usdc_);
usdt = Tether(usdt_);
require(address(ousd) != address(0));
require(address(dai) != address(0));
require(address(usdc) != address(0));
require(address(usdt) != address(0));
}

// -----------------
// Trading functions
// -----------------

/// @notice Purchase OUSD with Dai
/// @param amount Amount of OUSD to purchase, in 18 fixed decimals.
function buyOusdWithDai(uint256 amount) external {
require(amount <= MAXIMUM_PER_TRADE, "Amount too large");
require(dai.transferFrom(msg.sender, address(this), amount));
require(ousd.transfer(msg.sender, amount));
}

/// @notice Sell OUSD for Dai
/// @param amount Amount of OUSD to sell, in 18 fixed decimals.
function sellOusdForDai(uint256 amount) external {
require(amount <= MAXIMUM_PER_TRADE, "Amount too large");
require(dai.transfer(msg.sender, amount));
require(ousd.transferFrom(msg.sender, address(this), amount));
}

/// @notice Purchase OUSD with USDC
/// @param amount Amount of OUSD to purchase, in 18 fixed decimals.
function buyOusdWithUsdc(uint256 amount) external {
require(amount <= MAXIMUM_PER_TRADE, "Amount too large");
// Potential rounding error is an intentional tradeoff
require(usdc.transferFrom(msg.sender, address(this), amount / 1e12));
require(ousd.transfer(msg.sender, amount));
}

/// @notice Sell OUSD for USDC
/// @param amount Amount of OUSD to sell, in 18 fixed decimals.
function sellOusdForUsdc(uint256 amount) external {
require(amount <= MAXIMUM_PER_TRADE, "Amount too large");
require(usdc.transfer(msg.sender, amount / 1e12));
require(ousd.transferFrom(msg.sender, address(this), amount));
}

/// @notice Purchase OUSD with USDT
/// @param amount Amount of OUSD to purchase, in 18 fixed decimals.
function buyOusdWithUsdt(uint256 amount) external {
require(amount <= MAXIMUM_PER_TRADE, "Amount too large");
// Potential rounding error is an intentional tradeoff
// USDT does not return a boolean and reverts,
// so no need for a require.
usdt.transferFrom(msg.sender, address(this), amount / 1e12);
require(ousd.transfer(msg.sender, amount));
}

/// @notice Sell OUSD for USDT
/// @param amount Amount of OUSD to sell, in 18 fixed decimals.
function sellOusdForUsdt(uint256 amount) external {
require(amount <= MAXIMUM_PER_TRADE, "Amount too large");
// USDT does not return a boolean and reverts,
// so no need for a require.
usdt.transfer(msg.sender, amount / 1e12);
require(ousd.transferFrom(msg.sender, address(this), amount));
}

// --------------------
// Governance functions
// --------------------

/// @dev Opting into yield reduces the gas cost per transfer by about 4K, since
/// ousd needs to do less accounting and one less storage write.
function rebaseOptIn() external onlyGovernor nonReentrant {
ousd.rebaseOptIn();
}

/// @notice Owner function to withdraw a specific amount of a token
function withdraw(address token, uint256 amount)
external
onlyGovernor
nonReentrant
{
IERC20(token).safeTransfer(_governor(), amount);
}

/// @notice Owner function to withdraw all tradable tokens
/// @dev Equivalent to "pausing" the contract.
function withdrawAll() external onlyGovernor nonReentrant {
IERC20(dai).safeTransfer(_governor(), dai.balanceOf(address(this)));
IERC20(ousd).safeTransfer(_governor(), ousd.balanceOf(address(this)));
IERC20(address(usdt)).safeTransfer(
_governor(),
usdt.balanceOf(address(this))
);
IERC20(usdc).safeTransfer(_governor(), usdc.balanceOf(address(this)));
}
}
13 changes: 13 additions & 0 deletions contracts/contracts/interfaces/Tether.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
pragma solidity 0.5.11;

interface Tether {
function transfer(address to, uint256 value) external;

function transferFrom(
address from,
address to,
uint256 value
) external;

function balanceOf(address) external returns (uint256);
}
19 changes: 19 additions & 0 deletions contracts/deploy/001_core.js
Original file line number Diff line number Diff line change
Expand Up @@ -427,6 +427,24 @@ const deployCore = async () => {
log("Initialized OUSD");
};

// Deploy the Flipper trading contract
const deployFlipper = async () => {
const assetAddresses = await getAssetAddresses(deployments);
const { governorAddr } = await hre.getNamedAccounts();
const sGovernor = await ethers.provider.getSigner(governorAddr);
const ousd = await ethers.getContract("OUSDProxy");

await deployWithConfirmation("FlipperDev", [
assetAddresses.DAI,
ousd.address,
assetAddresses.USDC,
assetAddresses.USDT,
]);
const flipper = await ethers.getContract("FlipperDev");
await withConfirmation(flipper.transferGovernance(governorAddr));
await withConfirmation(flipper.connect(sGovernor).claimGovernance());
};

const main = async () => {
console.log("Running 001_core deployment...");
await deployOracles();
Expand All @@ -435,6 +453,7 @@ const main = async () => {
await deployAaveStrategy();
await deployThreePoolStrategy();
await configureVault();
await deployFlipper();
console.log("001_core deploy done.");
return true;
};
Expand Down
Loading

0 comments on commit f20102d

Please sign in to comment.