Contract Name:
CurveExchangeAdapterSBTC
Contract Source Code:
File 1 of 1 : CurveExchangeAdapterSBTC
/**
*Submitted for verification at Etherscan.io on 2020-05-18
*/
/**
*Submitted for verification at Etherscan.io on 2020-01-23
*/
// File: github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/GSN/Context.sol
pragma solidity ^0.6.0;
library SafeMath {
function add(uint a, uint b) internal pure returns (uint c) {
c = a + b;
require(c >= a); // dev: overflow
}
function sub(uint a, uint b) internal pure returns (uint c) {
require(b <= a); // dev: underflow
c = a - b;
}
function mul(uint a, uint b) internal pure returns (uint c) {
c = a * b;
require(a == 0 || c / a == b); // dev: overflow
}
function div(uint a, uint b) internal pure returns (uint c) {
require(b > 0); // dev: divide by zero
c = a / b;
}
}
contract BasicMetaTransaction {
using SafeMath for uint256;
event MetaTransactionExecuted(address userAddress, address payable relayerAddress, bytes functionSignature);
mapping(address => uint256) nonces;
function getChainID() public pure returns (uint256) {
uint256 id;
assembly {
id := chainid()
}
return id;
}
/**
* Main function to be called when user wants to execute meta transaction.
* The actual function to be called should be passed as param with name functionSignature
* Here the basic signature recovery is being used. Signature is expected to be generated using
* personal_sign method.
* @param userAddress Address of user trying to do meta transaction
* @param functionSignature Signature of the actual function to be called via meta transaction
* @param message Message to be signed by the user
* @param length Length of complete message that was signed
* @param sigR R part of the signature
* @param sigS S part of the signature
* @param sigV V part of the signature
*/
function executeMetaTransaction(address userAddress,
bytes memory functionSignature, string memory message, string memory length,
bytes32 sigR, bytes32 sigS, uint8 sigV) public payable returns(bytes memory) {
require(verify(userAddress, message, length, nonces[userAddress], getChainID(), sigR, sigS, sigV), "Signer and signature do not match");
// Append userAddress and relayer address at the end to extract it from calling context
(bool success, bytes memory returnData) = address(this).call(abi.encodePacked(functionSignature, userAddress));
require(success, "Function call not successfull");
nonces[userAddress] = nonces[userAddress].add(1);
emit MetaTransactionExecuted(userAddress, msg.sender, functionSignature);
return returnData;
}
function getNonce(address user) public view returns(uint256 nonce) {
nonce = nonces[user];
}
function verify(address owner, string memory message, string memory length, uint256 nonce, uint256 chainID,
bytes32 sigR, bytes32 sigS, uint8 sigV) public pure returns (bool) {
string memory nonceStr = uint2str(nonce);
string memory chainIDStr = uint2str(chainID);
bytes32 hash = keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n", length, message, nonceStr, chainIDStr));
return (owner == ecrecover(hash, sigV, sigR, sigS));
}
/**
* Internal utility function used to convert an int to string.
* @param _i integer to be converted into a string
*/
function uint2str(uint256 _i) internal pure returns (string memory _uintAsString) {
if (_i == 0) {
return "0";
}
uint j = _i;
uint len;
while (j != 0) {
len++;
j /= 10;
}
bytes memory bstr = new bytes(len);
uint k = len - 1;
uint256 temp = _i;
while (temp != 0) {
bstr[k--] = byte(uint8(48 + temp % 10));
temp /= 10;
}
return string(bstr);
}
function msgSender() internal view returns(address sender) {
if(msg.sender == address(this)) {
bytes memory array = msg.data;
uint256 index = msg.data.length;
assembly {
// Load the 32 bytes word from memory with the address on the lower 20 bytes, and mask those.
sender := and(mload(add(array, index)), 0xffffffffffffffffffffffffffffffffffffffff)
}
} else {
sender = msg.sender;
}
return sender;
}
// To recieve ether in contract
receive() external payable { }
fallback() external payable { }
}
// File: browser/dex-adapter-simple.sol
library Math {
/**
* @dev Returns the largest of two numbers.
*/
function max(uint256 a, uint256 b) internal pure returns (uint256) {
return a >= b ? a : b;
}
/**
* @dev Returns the smallest of two numbers.
*/
function min(uint256 a, uint256 b) internal pure returns (uint256) {
return a < b ? a : b;
}
/**
* @dev Returns the average of two numbers. The result is rounded towards
* zero.
*/
function average(uint256 a, uint256 b) internal pure returns (uint256) {
// (a + b) / 2 can overflow, so we distribute
return (a / 2) + (b / 2) + ((a % 2 + b % 2) / 2);
}
}
interface IERC20 {
function transfer(address recipient, uint256 amount) external returns (bool);
function transferFrom(address sender, address recipient, uint256 amount) external returns (bool);
function approve(address _spender, uint256 _value) external returns (bool);
function balanceOf(address _owner) external view returns (uint256 balance);
}
interface IGateway {
function mint(bytes32 _pHash, uint256 _amount, bytes32 _nHash, bytes calldata _sig) external returns (uint256);
function burn(bytes calldata _to, uint256 _amount) external returns (uint256);
}
interface IGatewayRegistry {
function getGatewayBySymbol(string calldata _tokenSymbol) external view returns (IGateway);
function getGatewayByToken(address _tokenAddress) external view returns (IGateway);
function getTokenBySymbol(string calldata _tokenSymbol) external view returns (IERC20);
}
interface ICurveExchange {
function exchange(int128 i, int128 j, uint256 dx, uint256 min_dy) external;
function get_dy(int128, int128 j, uint256 dx) external view returns (uint256);
function calc_token_amount(uint256[3] calldata amounts, bool deposit) external returns (uint256 amount);
function add_liquidity(uint256[3] calldata amounts, uint256 min_mint_amount) external;
function remove_liquidity(
uint256 _amount,
uint256[3] calldata min_amounts
) external;
function remove_liquidity_imbalance(uint256[3] calldata amounts, uint256 max_burn_amount) external;
function remove_liquidity_one_coin(uint256 _token_amounts, int128 i, uint256 min_amount) external;
}
interface IFreeFromUpTo {
function freeFromUpTo(address from, uint256 value) external returns (uint256 freed);
function balanceOf(address account) external view returns (uint256);
function approve(address spender, uint256 amount) external returns (bool);
}
contract CurveExchangeAdapterSBTC is BasicMetaTransaction {
using SafeMath for uint256;
IFreeFromUpTo public constant chi = IFreeFromUpTo(0x0000000000004946c0e9F43F4Dee607b0eF1fA1c);
modifier discountCHI {
uint256 gasStart = gasleft();
_;
uint256 gasSpent = 21000 + gasStart - gasleft() + 16 *
msg.data.length;
if(chi.balanceOf(address(this)) > 0) {
chi.freeFromUpTo(address(this), (gasSpent + 14154) / 41947);
}
else {
chi.freeFromUpTo(msgSender(), (gasSpent + 14154) / 41947);
}
}
uint256 constant N_COINS = 3;
//first coin always is renBTC
IERC20[N_COINS] coins;
uint256[N_COINS] precisions_normalized = [1,1,1e10];
IERC20 curveToken;
ICurveExchange public exchange;
IGatewayRegistry public registry;
event SwapReceived(uint256 mintedAmount, uint256 erc20BTCAmount, int128 j);
event DepositMintedCurve(uint256 mintedAmount, uint256 curveAmount, uint256[N_COINS] amounts);
event ReceiveRen(uint256 renAmount);
event Burn(uint256 burnAmount);
constructor(ICurveExchange _exchange, address _curveTokenAddress, IGatewayRegistry _registry, IERC20[N_COINS] memory _coins) public {
exchange = _exchange;
registry = _registry;
curveToken = IERC20(_curveTokenAddress);
for(uint256 i = 0; i < N_COINS; i++) {
coins[i] = _coins[i];
require(coins[i].approve(address(exchange), uint256(-1)));
}
require(chi.approve(address(this), uint256(-1)));
}
function recoverStuck(
bytes calldata encoded,
uint256 _amount,
bytes32 _nHash,
bytes calldata _sig
) external {
uint256 start = encoded.length - 32;
address sender = abi.decode(encoded[start:], (address));
require(sender == msgSender());
bytes32 pHash = keccak256(encoded);
uint256 mintedAmount = registry.getGatewayBySymbol("BTC").mint(pHash, _amount, _nHash, _sig);
require(coins[0].transfer(msgSender(), mintedAmount));
}
function mintThenSwap(
uint256 _minExchangeRate,
uint256 _newMinExchangeRate,
uint256 _slippage,
int128 _j,
address payable _coinDestination,
uint256 _amount,
bytes32 _nHash,
bytes calldata _sig
) external discountCHI {
//params is [_minExchangeRate, _slippage, _i, _j]
//fail early so not to spend much gas?
//require(_i <= 2 && _j <= 2 && _i != _j);
// Mint renBTC tokens
bytes32 pHash = keccak256(abi.encode(_minExchangeRate, _slippage, _j, _coinDestination, msgSender()));
uint256 mintedAmount = registry.getGatewayBySymbol("BTC").mint(pHash, _amount, _nHash, _sig);
// Get price
// compare if the exchange rate now * slippage in BPS is what user submitted as
uint256 dy = exchange.get_dy(0, _j, mintedAmount);
uint256 rate = dy.mul(1e8).div(precisions_normalized[uint256(_j)]).div(mintedAmount);
_slippage = uint256(1e4).sub(_slippage);
uint256 min_dy = dy.mul(_slippage).div(1e4);
// Price is OK
if (rate >= _newMinExchangeRate) {
require(_j != 0);
doSwap(_j, mintedAmount, min_dy, _coinDestination);
} else {
//Send renBTC to the User instead
require(coins[0].transfer(_coinDestination, mintedAmount));
emit ReceiveRen(mintedAmount);
}
}
function doSwap(int128 _j, uint256 _mintedAmount, uint256 _min_dy, address payable _coinDestination) internal {
uint256 startBalance = coins[uint256(_j)].balanceOf(address(this));
exchange.exchange(0, _j, _mintedAmount, _min_dy);
uint256 endBalance = coins[uint256(_j)].balanceOf(address(this));
uint256 bought = endBalance.sub(startBalance);
//Send proceeds to the User
require(coins[uint256(_j)].transfer(_coinDestination, bought));
emit SwapReceived(_mintedAmount, bought, _j);
}
function mintThenDeposit(
address payable _wbtcDestination,
uint256 _amount,
uint256[N_COINS] calldata _amounts,
uint256 _min_mint_amount,
uint256 _new_min_mint_amount,
bytes32 _nHash,
bytes calldata _sig
) external discountCHI {
// Mint renBTC tokens
bytes32 pHash = keccak256(abi.encode(_wbtcDestination, _amounts, _min_mint_amount, msgSender()));
//use actual _amount the user sent
uint256 mintedAmount = registry.getGatewayBySymbol("BTC").mint(pHash, _amount, _nHash, _sig);
//set renBTC to actual minted amount in case the user sent less BTC to Ren
uint256[N_COINS] memory receivedAmounts = _amounts;
receivedAmounts[0] = mintedAmount;
for(uint256 i = 1; i < N_COINS; i++) {
receivedAmounts[i] = _amounts[i];
}
if(exchange.calc_token_amount(_amounts, true) >= _new_min_mint_amount) {
doDeposit(receivedAmounts, mintedAmount, _new_min_mint_amount, _wbtcDestination);
}
else {
require(coins[0].transfer(_wbtcDestination, mintedAmount));
emit ReceiveRen(mintedAmount);
}
}
function doDeposit(uint256[N_COINS] memory receivedAmounts, uint256 mintedAmount, uint256 _new_min_mint_amount, address _wbtcDestination) internal {
for(uint256 i = 1; i < N_COINS; i++) {
if(receivedAmounts[i] > 0) {
require(coins[i].transferFrom(msgSender(), address(this), receivedAmounts[i]));
}
}
uint256 curveBalanceBefore = curveToken.balanceOf(address(this));
exchange.add_liquidity(receivedAmounts, 0);
uint256 curveBalanceAfter = curveToken.balanceOf(address(this));
uint256 curveAmount = curveBalanceAfter.sub(curveBalanceBefore);
require(curveAmount >= _new_min_mint_amount);
require(curveToken.transfer(_wbtcDestination, curveAmount));
emit DepositMintedCurve(mintedAmount, curveAmount, receivedAmounts);
}
// function mintNoSwap(
// uint256 _minExchangeRate,
// uint256 _newMinExchangeRate,
// uint256 _slippage,
// int128 _j,
// address payable _wbtcDestination,
// uint256 _amount,
// bytes32 _nHash,
// bytes calldata _sig
// ) external discountCHI {
// bytes32 pHash = keccak256(abi.encode(_minExchangeRate, _slippage, _j, _wbtcDestination, msgSender()));
// uint256 mintedAmount = registry.getGatewayBySymbol("BTC").mint(pHash, _amount, _nHash, _sig);
// require(coins[0].transfer(_wbtcDestination, mintedAmount));
// emit ReceiveRen(mintedAmount);
// }
// function mintNoDeposit(
// address payable _wbtcDestination,
// uint256 _amount,
// uint256[N_COINS] calldata _amounts,
// uint256 _min_mint_amount,
// uint256 _new_min_mint_amount,
// bytes32 _nHash,
// bytes calldata _sig
// ) external discountCHI {
// // Mint renBTC tokens
// bytes32 pHash = keccak256(abi.encode(_wbtcDestination, _amounts, _min_mint_amount, msgSender()));
// //use actual _amount the user sent
// uint256 mintedAmount = registry.getGatewayBySymbol("BTC").mint(pHash, _amount, _nHash, _sig);
// require(coins[0].transfer(_wbtcDestination, mintedAmount));
// emit ReceiveRen(mintedAmount);
// }
function removeLiquidityThenBurn(bytes calldata _btcDestination, address _coinDestination, uint256 amount, uint256[N_COINS] calldata min_amounts) external discountCHI {
uint256[N_COINS] memory balances;
for(uint256 i = 0; i < coins.length; i++) {
balances[i] = coins[i].balanceOf(address(this));
}
require(curveToken.transferFrom(msgSender(), address(this), amount));
exchange.remove_liquidity(amount, min_amounts);
for(uint256 i = 0; i < coins.length; i++) {
balances[i] = coins[i].balanceOf(address(this)).sub(balances[i]);
if(i == 0) continue;
require(coins[i].transfer(_coinDestination, balances[i]));
}
// Burn and send proceeds to the User
uint256 burnAmount = registry.getGatewayBySymbol("BTC").burn(_btcDestination, balances[0]);
emit Burn(burnAmount);
}
function removeLiquidityImbalanceThenBurn(bytes calldata _btcDestination, address _coinDestination, uint256[N_COINS] calldata amounts, uint256 max_burn_amount) external discountCHI {
uint256[N_COINS] memory balances;
for(uint256 i = 0; i < coins.length; i++) {
balances[i] = coins[i].balanceOf(address(this));
}
uint256 _tokens = curveToken.balanceOf(msgSender());
if(_tokens > max_burn_amount) {
_tokens = max_burn_amount;
}
require(curveToken.transferFrom(msgSender(), address(this), _tokens));
exchange.remove_liquidity_imbalance(amounts, max_burn_amount.mul(101).div(100));
_tokens = curveToken.balanceOf(address(this));
require(curveToken.transfer(_coinDestination, _tokens));
for(uint256 i = 0; i < coins.length; i++) {
balances[i] = coins[i].balanceOf(address(this)).sub(balances[i]);
if(i == 0) continue;
require(coins[i].transfer(_coinDestination, balances[i]));
}
// Burn and send proceeds to the User
uint256 burnAmount = registry.getGatewayBySymbol("BTC").burn(_btcDestination, balances[0]);
emit Burn(burnAmount);
}
//always removing in renBTC, else use normal method
function removeLiquidityOneCoinThenBurn(bytes calldata _btcDestination, uint256 _token_amounts, uint256 min_amount, uint8 _i) external discountCHI {
uint256 startRenbtcBalance = coins[0].balanceOf(address(this));
require(curveToken.transferFrom(msgSender(), address(this), _token_amounts));
exchange.remove_liquidity_one_coin(_token_amounts, _i, min_amount);
uint256 endRenbtcBalance = coins[0].balanceOf(address(this));
uint256 renbtcWithdrawn = endRenbtcBalance.sub(startRenbtcBalance);
// Burn and send proceeds to the User
uint256 burnAmount = registry.getGatewayBySymbol("BTC").burn(_btcDestination, renbtcWithdrawn);
emit Burn(burnAmount);
}
function swapThenBurn(bytes calldata _btcDestination, uint256 _amount, uint256 _minRenbtcAmount, uint8 _i) external discountCHI {
require(coins[_i].transferFrom(msgSender(), address(this), _amount));
uint256 startRenbtcBalance = coins[0].balanceOf(address(this));
exchange.exchange(_i, 0, _amount, _minRenbtcAmount);
uint256 endRenbtcBalance = coins[0].balanceOf(address(this));
uint256 renbtcBought = endRenbtcBalance.sub(startRenbtcBalance);
// Burn and send proceeds to the User
uint256 burnAmount = registry.getGatewayBySymbol("BTC").burn(_btcDestination, renbtcBought);
emit Burn(burnAmount);
}
}