Earn interest on locked tokens using Compound Protocol (#590)
This commit is contained in:
parent
c9377114f7
commit
e7f7fae726
|
@ -10,6 +10,9 @@
|
|||
"bracket-align": "off",
|
||||
"no-complex-fallback": "off",
|
||||
"no-simple-event-func-name": "off",
|
||||
"const-name-snakecase": "off",
|
||||
"no-empty-blocks": "off",
|
||||
"not-rely-on-time": "off",
|
||||
"compiler-version": ["error", "0.4.24"]
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,10 @@
|
|||
pragma solidity 0.4.24;
|
||||
|
||||
interface ICToken {
|
||||
function mint(uint256 mintAmount) external returns (uint256);
|
||||
function redeemUnderlying(uint256 redeemAmount) external returns (uint256);
|
||||
function balanceOf(address account) external view returns (uint256);
|
||||
function balanceOfUnderlying(address account) external view returns (uint256);
|
||||
function borrow(uint256 borrowAmount) external returns (uint256);
|
||||
function repayBorrow(uint256 borrowAmount) external returns (uint256);
|
||||
}
|
|
@ -1,15 +0,0 @@
|
|||
pragma solidity 0.4.24;
|
||||
|
||||
import "openzeppelin-solidity/contracts/token/ERC20/ERC20.sol";
|
||||
import "../interfaces/IPot.sol";
|
||||
|
||||
interface IChai {
|
||||
function pot() external view returns (IPot);
|
||||
function daiToken() external view returns (ERC20);
|
||||
function balanceOf(address) external view returns (uint256);
|
||||
function dai(address) external view returns (uint256);
|
||||
function join(address, uint256) external;
|
||||
function draw(address, uint256) external;
|
||||
function exit(address, uint256) external;
|
||||
function transfer(address, uint256) external;
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
pragma solidity 0.4.24;
|
||||
|
||||
interface IComptroller {
|
||||
function claimComp(address[] holders, address[] cTokens, bool borrowers, bool suppliers) external;
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
pragma solidity 0.4.24;
|
||||
|
||||
interface IInterestReceiver {
|
||||
function onInterestReceived(address _token) external;
|
||||
}
|
|
@ -1,7 +0,0 @@
|
|||
pragma solidity 0.4.24;
|
||||
|
||||
interface IPot {
|
||||
function chi() external view returns (uint256);
|
||||
function rho() external view returns (uint256);
|
||||
function drip() external returns (uint256);
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
pragma solidity 0.4.24;
|
||||
|
||||
interface IUniswapRouterV2 {
|
||||
function swapExactTokensForTokens(
|
||||
uint256 amountIn,
|
||||
uint256 amountOutMin,
|
||||
address[] path,
|
||||
address to,
|
||||
uint256 deadline
|
||||
) external returns (uint256[] amounts);
|
||||
function swapExactTokensForETH(uint256 amountIn, uint256 amountOutMin, address[] path, address to, uint256 deadline)
|
||||
external
|
||||
returns (uint256[] amounts);
|
||||
function getAmountsOut(uint256 amountIn, address[] path) external view returns (uint256[] memory amounts);
|
||||
}
|
|
@ -1,198 +0,0 @@
|
|||
// chai.sol -- a dai savings token
|
||||
// Copyright (C) 2017, 2018, 2019 dbrock, rain, mrchico, lucasvo, livnev
|
||||
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
/* solhint-disable */
|
||||
pragma solidity 0.4.24;
|
||||
|
||||
contract VatLike {
|
||||
function hope(address) external;
|
||||
}
|
||||
|
||||
contract PotLike {
|
||||
function chi() external returns (uint256);
|
||||
function rho() external returns (uint256);
|
||||
function drip() external returns (uint256);
|
||||
function join(uint256) external;
|
||||
function exit(uint256) external;
|
||||
}
|
||||
|
||||
contract JoinLike {
|
||||
function join(address, uint256) external;
|
||||
function exit(address, uint256) external;
|
||||
}
|
||||
|
||||
contract GemLike {
|
||||
function transferFrom(address, address, uint256) external returns (bool);
|
||||
function approve(address, uint256) external returns (bool);
|
||||
}
|
||||
|
||||
contract ChaiMock {
|
||||
// --- Data ---
|
||||
VatLike public vat = VatLike(0x35D1b3F3D7966A1DFe207aa4514C12a259A0492B);
|
||||
PotLike public pot = PotLike(0x197E90f9FAD81970bA7976f33CbD77088E5D7cf7);
|
||||
JoinLike public daiJoin = JoinLike(0x9759A6Ac90977b93B58547b4A71c78317f391A28);
|
||||
GemLike public daiToken = GemLike(0x6B175474E89094C44Da98b954EedeAC495271d0F);
|
||||
|
||||
// --- ERC20 Data ---
|
||||
string public constant name = "Chai";
|
||||
string public constant symbol = "CHAI";
|
||||
string public constant version = "1";
|
||||
uint8 public constant decimals = 18;
|
||||
uint256 public totalSupply;
|
||||
|
||||
mapping(address => uint256) public balanceOf;
|
||||
mapping(address => mapping(address => uint256)) public allowance;
|
||||
mapping(address => uint256) public nonces;
|
||||
|
||||
event Approval(address indexed src, address indexed guy, uint256 wad);
|
||||
event Transfer(address indexed src, address indexed dst, uint256 wad);
|
||||
|
||||
// --- Math ---
|
||||
uint256 constant RAY = 10**27;
|
||||
function add(uint256 x, uint256 y) internal pure returns (uint256 z) {
|
||||
require((z = x + y) >= x);
|
||||
}
|
||||
function sub(uint256 x, uint256 y) internal pure returns (uint256 z) {
|
||||
require((z = x - y) <= x);
|
||||
}
|
||||
function mul(uint256 x, uint256 y) internal pure returns (uint256 z) {
|
||||
require(y == 0 || (z = x * y) / y == x);
|
||||
}
|
||||
function rmul(uint256 x, uint256 y) internal pure returns (uint256 z) {
|
||||
// always rounds down
|
||||
z = mul(x, y) / RAY;
|
||||
}
|
||||
function rdiv(uint256 x, uint256 y) internal pure returns (uint256 z) {
|
||||
// always rounds down
|
||||
z = mul(x, RAY) / y;
|
||||
}
|
||||
function rdivup(uint256 x, uint256 y) internal pure returns (uint256 z) {
|
||||
// always rounds up
|
||||
z = add(mul(x, RAY), sub(y, 1)) / y;
|
||||
}
|
||||
|
||||
// --- EIP712 niceties ---
|
||||
bytes32 public constant DOMAIN_SEPARATOR = 0x0b50407de9fa158c2cba01a99633329490dfd22989a150c20e8c7b4c1fb0fcc3;
|
||||
// keccak256("Permit(address holder,address spender,uint256 nonce,uint256 expiry,bool allowed)"));
|
||||
bytes32 public constant PERMIT_TYPEHASH = 0xea2aa0a1be11a07ed86d755c93467f4f82362b452371d1ba94d1715123511acb;
|
||||
|
||||
constructor(address _vat, address _pot, address _daiJoin, address _dai) public {
|
||||
vat = VatLike(_vat);
|
||||
pot = PotLike(_pot);
|
||||
daiJoin = JoinLike(_daiJoin);
|
||||
daiToken = GemLike(_dai);
|
||||
|
||||
vat.hope(address(daiJoin));
|
||||
vat.hope(address(pot));
|
||||
|
||||
daiToken.approve(address(daiJoin), uint256(-1));
|
||||
}
|
||||
|
||||
// --- Token ---
|
||||
function transfer(address dst, uint256 wad) external returns (bool) {
|
||||
return transferFrom(msg.sender, dst, wad);
|
||||
}
|
||||
// like transferFrom but dai-denominated
|
||||
function move(address src, address dst, uint256 wad) external returns (bool) {
|
||||
uint256 chi = (now > pot.rho()) ? pot.drip() : pot.chi();
|
||||
// rounding up ensures dst gets at least wad dai
|
||||
return transferFrom(src, dst, rdivup(wad, chi));
|
||||
}
|
||||
function transferFrom(address src, address dst, uint256 wad) public returns (bool) {
|
||||
require(balanceOf[src] >= wad, "chai/insufficient-balance");
|
||||
if (src != msg.sender && allowance[src][msg.sender] != uint256(-1)) {
|
||||
require(allowance[src][msg.sender] >= wad, "chai/insufficient-allowance");
|
||||
allowance[src][msg.sender] = sub(allowance[src][msg.sender], wad);
|
||||
}
|
||||
balanceOf[src] = sub(balanceOf[src], wad);
|
||||
balanceOf[dst] = add(balanceOf[dst], wad);
|
||||
emit Transfer(src, dst, wad);
|
||||
return true;
|
||||
}
|
||||
function approve(address usr, uint256 wad) external returns (bool) {
|
||||
allowance[msg.sender][usr] = wad;
|
||||
emit Approval(msg.sender, usr, wad);
|
||||
return true;
|
||||
}
|
||||
|
||||
// --- Approve by signature ---
|
||||
function permit(
|
||||
address holder,
|
||||
address spender,
|
||||
uint256 nonce,
|
||||
uint256 expiry,
|
||||
bool allowed,
|
||||
uint8 v,
|
||||
bytes32 r,
|
||||
bytes32 s
|
||||
) external {
|
||||
bytes32 digest = keccak256(
|
||||
abi.encodePacked(
|
||||
"\x19\x01",
|
||||
DOMAIN_SEPARATOR,
|
||||
keccak256(abi.encode(PERMIT_TYPEHASH, holder, spender, nonce, expiry, allowed))
|
||||
)
|
||||
);
|
||||
require(holder != address(0), "chai/invalid holder");
|
||||
require(holder == ecrecover(digest, v, r, s), "chai/invalid-permit");
|
||||
require(expiry == 0 || now <= expiry, "chai/permit-expired");
|
||||
require(nonce == nonces[holder]++, "chai/invalid-nonce");
|
||||
|
||||
uint256 can = allowed ? uint256(-1) : 0;
|
||||
allowance[holder][spender] = can;
|
||||
emit Approval(holder, spender, can);
|
||||
}
|
||||
|
||||
function dai(address usr) external returns (uint256 wad) {
|
||||
uint256 chi = (now > pot.rho()) ? pot.drip() : pot.chi();
|
||||
wad = rmul(chi, balanceOf[usr]);
|
||||
}
|
||||
// wad is denominated in dai
|
||||
function join(address dst, uint256 wad) external {
|
||||
uint256 chi = (now > pot.rho()) ? pot.drip() : pot.chi();
|
||||
uint256 pie = rdiv(wad, chi);
|
||||
balanceOf[dst] = add(balanceOf[dst], pie);
|
||||
totalSupply = add(totalSupply, pie);
|
||||
|
||||
daiToken.transferFrom(msg.sender, address(this), wad);
|
||||
daiJoin.join(address(this), wad);
|
||||
pot.join(pie);
|
||||
emit Transfer(address(0), dst, pie);
|
||||
}
|
||||
|
||||
// wad is denominated in (1/chi) * dai
|
||||
function exit(address src, uint256 wad) public {
|
||||
require(balanceOf[src] >= wad, "chai/insufficient-balance");
|
||||
if (src != msg.sender && allowance[src][msg.sender] != uint256(-1)) {
|
||||
require(allowance[src][msg.sender] >= wad, "chai/insufficient-allowance");
|
||||
allowance[src][msg.sender] = sub(allowance[src][msg.sender], wad);
|
||||
}
|
||||
balanceOf[src] = sub(balanceOf[src], wad);
|
||||
totalSupply = sub(totalSupply, wad);
|
||||
|
||||
uint256 chi = (now > pot.rho()) ? pot.drip() : pot.chi();
|
||||
pot.exit(wad);
|
||||
daiJoin.exit(msg.sender, rmul(chi, wad));
|
||||
emit Transfer(src, address(0), wad);
|
||||
}
|
||||
|
||||
// wad is denominated in dai
|
||||
function draw(address src, uint256 wad) external {
|
||||
uint256 chi = (now > pot.rho()) ? pot.drip() : pot.chi();
|
||||
// rounding up ensures usr gets at least wad dai
|
||||
exit(src, rdivup(wad, chi));
|
||||
}
|
||||
}
|
|
@ -1,51 +0,0 @@
|
|||
pragma solidity 0.4.24;
|
||||
|
||||
contract GemLike {
|
||||
function mint(address, uint256) external returns (bool);
|
||||
function transferFrom(address, address, uint256) external returns (bool);
|
||||
}
|
||||
|
||||
/**
|
||||
* @title ChaiMock2
|
||||
* @dev This contract is used for e2e tests only,
|
||||
* this mock represents a simplified version of Chai, which does not require other MakerDAO contracts to be deployed in e2e tests
|
||||
*/
|
||||
contract ChaiMock2 {
|
||||
event Transfer(address indexed src, address indexed dst, uint256 wad);
|
||||
|
||||
GemLike public daiToken;
|
||||
uint256 internal daiBalance;
|
||||
address public pot;
|
||||
|
||||
// wad is denominated in dai
|
||||
function join(address, uint256 wad) external {
|
||||
daiToken.transferFrom(msg.sender, address(this), wad);
|
||||
daiBalance += wad;
|
||||
}
|
||||
|
||||
function transfer(address to, uint256 wad) external {
|
||||
require(daiBalance >= wad);
|
||||
daiBalance -= wad;
|
||||
emit Transfer(msg.sender, to, wad);
|
||||
}
|
||||
|
||||
function exit(address, uint256 wad) external {
|
||||
require(daiBalance >= wad);
|
||||
daiBalance -= wad;
|
||||
daiToken.mint(msg.sender, wad);
|
||||
}
|
||||
|
||||
function draw(address, uint256 wad) external {
|
||||
require(daiBalance >= wad);
|
||||
daiBalance -= wad;
|
||||
daiToken.mint(msg.sender, wad);
|
||||
}
|
||||
|
||||
function dai(address) external view returns (uint256) {
|
||||
return daiBalance;
|
||||
}
|
||||
|
||||
function balanceOf(address) external view returns (uint256) {
|
||||
return daiBalance;
|
||||
}
|
||||
}
|
|
@ -1,9 +0,0 @@
|
|||
pragma solidity 0.4.24;
|
||||
|
||||
contract DaiAdapterMock {
|
||||
address public dai;
|
||||
|
||||
constructor(address _dai) public {
|
||||
dai = _dai;
|
||||
}
|
||||
}
|
|
@ -1,74 +0,0 @@
|
|||
/// join.sol -- Basic token adapters
|
||||
|
||||
// Copyright (C) 2018 Rain <rainbreak@riseup.net>
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
/* solhint-disable */
|
||||
pragma solidity 0.4.24;
|
||||
|
||||
contract DSTokenLike {
|
||||
function mint(address, uint256) external;
|
||||
function burn(address, uint256) external;
|
||||
function allowance(address, address) external returns (uint256);
|
||||
}
|
||||
|
||||
contract VatLike {
|
||||
function slip(bytes32, address, int256) external;
|
||||
function move(address, address, uint256) external;
|
||||
function setDai(address, uint256) external;
|
||||
}
|
||||
|
||||
contract DaiJoinMock {
|
||||
// --- Auth ---
|
||||
mapping(address => uint256) public wards;
|
||||
function rely(address usr) external auth {
|
||||
wards[usr] = 1;
|
||||
}
|
||||
function deny(address usr) external auth {
|
||||
wards[usr] = 0;
|
||||
}
|
||||
modifier auth {
|
||||
require(wards[msg.sender] == 1, "DaiJoin/not-authorized");
|
||||
_;
|
||||
}
|
||||
|
||||
VatLike public vat;
|
||||
DSTokenLike public dai;
|
||||
uint256 public live; // Access Flag
|
||||
|
||||
constructor(address vat_, address dai_) public {
|
||||
wards[msg.sender] = 1;
|
||||
live = 1;
|
||||
vat = VatLike(vat_);
|
||||
dai = DSTokenLike(dai_);
|
||||
vat.setDai(address(this), mul(ONE, ONE));
|
||||
}
|
||||
function cage() external auth {
|
||||
live = 0;
|
||||
}
|
||||
uint256 constant ONE = 10**27;
|
||||
function mul(uint256 x, uint256 y) internal pure returns (uint256 z) {
|
||||
require(y == 0 || (z = x * y) / y == x);
|
||||
}
|
||||
function join(address usr, uint256 wad) external {
|
||||
vat.move(address(this), usr, mul(ONE, wad));
|
||||
dai.burn(msg.sender, wad);
|
||||
}
|
||||
function exit(address usr, uint256 wad) external {
|
||||
require(live == 1, "DaiJoin/not-live");
|
||||
vat.move(msg.sender, address(this), mul(ONE, wad));
|
||||
dai.mint(usr, wad);
|
||||
}
|
||||
}
|
|
@ -1,138 +0,0 @@
|
|||
// Copyright (C) 2017, 2018, 2019 dbrock, rain, mrchico
|
||||
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
/* solhint-disable */
|
||||
pragma solidity 0.4.24;
|
||||
|
||||
contract DaiMock {
|
||||
// --- Auth ---
|
||||
mapping(address => uint256) public wards;
|
||||
function rely(address guy) external auth {
|
||||
wards[guy] = 1;
|
||||
}
|
||||
function deny(address guy) external auth {
|
||||
wards[guy] = 0;
|
||||
}
|
||||
modifier auth {
|
||||
require(wards[msg.sender] == 1, "Dai/not-authorized");
|
||||
_;
|
||||
}
|
||||
|
||||
// --- ERC20 Data ---
|
||||
string public constant name = "Dai Stablecoin";
|
||||
string public constant symbol = "DAI";
|
||||
string public constant version = "1";
|
||||
uint8 public constant decimals = 18;
|
||||
uint256 public totalSupply;
|
||||
|
||||
mapping(address => uint256) public balanceOf;
|
||||
mapping(address => mapping(address => uint256)) public allowance;
|
||||
mapping(address => uint256) public nonces;
|
||||
|
||||
event Approval(address indexed src, address indexed guy, uint256 wad);
|
||||
event Transfer(address indexed src, address indexed dst, uint256 wad);
|
||||
|
||||
// --- Math ---
|
||||
function add(uint256 x, uint256 y) internal pure returns (uint256 z) {
|
||||
require((z = x + y) >= x);
|
||||
}
|
||||
function sub(uint256 x, uint256 y) internal pure returns (uint256 z) {
|
||||
require((z = x - y) <= x);
|
||||
}
|
||||
|
||||
// --- EIP712 niceties ---
|
||||
bytes32 public DOMAIN_SEPARATOR;
|
||||
// bytes32 public constant PERMIT_TYPEHASH = keccak256("Permit(address holder,address spender,uint256 nonce,uint256 expiry,bool allowed)");
|
||||
bytes32 public constant PERMIT_TYPEHASH = 0xea2aa0a1be11a07ed86d755c93467f4f82362b452371d1ba94d1715123511acb;
|
||||
|
||||
constructor() public {
|
||||
wards[msg.sender] = 1;
|
||||
}
|
||||
|
||||
// --- Token ---
|
||||
function transfer(address dst, uint256 wad) external returns (bool) {
|
||||
return transferFrom(msg.sender, dst, wad);
|
||||
}
|
||||
function transferFrom(address src, address dst, uint256 wad) public returns (bool) {
|
||||
require(balanceOf[src] >= wad, "Dai/insufficient-balance");
|
||||
if (src != msg.sender && allowance[src][msg.sender] != uint256(-1)) {
|
||||
require(allowance[src][msg.sender] >= wad, "Dai/insufficient-allowance");
|
||||
allowance[src][msg.sender] = sub(allowance[src][msg.sender], wad);
|
||||
}
|
||||
balanceOf[src] = sub(balanceOf[src], wad);
|
||||
balanceOf[dst] = add(balanceOf[dst], wad);
|
||||
emit Transfer(src, dst, wad);
|
||||
return true;
|
||||
}
|
||||
function mint(address usr, uint256 wad) external auth {
|
||||
balanceOf[usr] = add(balanceOf[usr], wad);
|
||||
totalSupply = add(totalSupply, wad);
|
||||
emit Transfer(address(0), usr, wad);
|
||||
}
|
||||
function burn(address usr, uint256 wad) external {
|
||||
require(balanceOf[usr] >= wad, "Dai/insufficient-balance");
|
||||
if (usr != msg.sender && allowance[usr][msg.sender] != uint256(-1)) {
|
||||
require(allowance[usr][msg.sender] >= wad, "Dai/insufficient-allowance");
|
||||
allowance[usr][msg.sender] = sub(allowance[usr][msg.sender], wad);
|
||||
}
|
||||
balanceOf[usr] = sub(balanceOf[usr], wad);
|
||||
totalSupply = sub(totalSupply, wad);
|
||||
emit Transfer(usr, address(0), wad);
|
||||
}
|
||||
function approve(address usr, uint256 wad) external returns (bool) {
|
||||
allowance[msg.sender][usr] = wad;
|
||||
emit Approval(msg.sender, usr, wad);
|
||||
return true;
|
||||
}
|
||||
|
||||
// --- Alias ---
|
||||
function push(address usr, uint256 wad) external {
|
||||
transferFrom(msg.sender, usr, wad);
|
||||
}
|
||||
function pull(address usr, uint256 wad) external {
|
||||
transferFrom(usr, msg.sender, wad);
|
||||
}
|
||||
function move(address src, address dst, uint256 wad) external {
|
||||
transferFrom(src, dst, wad);
|
||||
}
|
||||
|
||||
// --- Approve by signature ---
|
||||
function permit(
|
||||
address holder,
|
||||
address spender,
|
||||
uint256 nonce,
|
||||
uint256 expiry,
|
||||
bool allowed,
|
||||
uint8 v,
|
||||
bytes32 r,
|
||||
bytes32 s
|
||||
) external {
|
||||
bytes32 digest = keccak256(
|
||||
abi.encodePacked(
|
||||
"\x19\x01",
|
||||
DOMAIN_SEPARATOR,
|
||||
keccak256(abi.encode(PERMIT_TYPEHASH, holder, spender, nonce, expiry, allowed))
|
||||
)
|
||||
);
|
||||
|
||||
require(holder != address(0), "Dai/invalid-address-0");
|
||||
require(holder == ecrecover(digest, v, r, s), "Dai/invalid-permit");
|
||||
require(expiry == 0 || now <= expiry, "Dai/permit-expired");
|
||||
require(nonce == nonces[holder]++, "Dai/invalid-nonce");
|
||||
uint256 wad = allowed ? uint256(-1) : 0;
|
||||
allowance[holder][spender] = wad;
|
||||
emit Approval(holder, spender, wad);
|
||||
}
|
||||
}
|
|
@ -9,7 +9,7 @@ import "../upgradeable_contracts/arbitrary_message/ForeignAMBWithGasToken.sol";
|
|||
contract ForeignAMBWithGasTokenMock is ForeignAMBWithGasToken {
|
||||
function gasToken() public pure returns (IGasToken) {
|
||||
// Address generated in unit test, also hardcoded in GasTokenMock
|
||||
return IGasToken(0xEC8bE1A5630364292E56D01129E8ee8A9578d7D8);
|
||||
return IGasToken(0x5b1869D9A4C187F2EAa108f3062412ecf0526b24);
|
||||
}
|
||||
|
||||
function collectGasTokens() external {
|
||||
|
|
|
@ -3,17 +3,31 @@ pragma solidity 0.4.24;
|
|||
import "../upgradeable_contracts/erc20_to_native/ForeignBridgeErcToNative.sol";
|
||||
|
||||
contract ForeignBridgeErcToNativeMock is ForeignBridgeErcToNative {
|
||||
bytes32 internal constant CHAI_TOKEN_MOCK = 0x5d6f4e61a116947624416975e8d26d4aff8f32e21ea6308dfa35cee98ed41fd8; // keccak256(abi.encodePacked("chaiTokenMock"))
|
||||
|
||||
function setChaiToken(IChai _chaiToken) external {
|
||||
addressStorage[CHAI_TOKEN_MOCK] = _chaiToken;
|
||||
/**
|
||||
* @dev Tells the address of the DAI token in the Ganache Testchain.
|
||||
*/
|
||||
function daiToken() public pure returns (ERC20) {
|
||||
return ERC20(0x0a4dBaF9656Fd88A32D087101Ee8bf399f4bd55f);
|
||||
}
|
||||
|
||||
function chaiToken() public view returns (IChai) {
|
||||
return IChai(addressStorage[CHAI_TOKEN_MOCK]);
|
||||
/**
|
||||
* @dev Tells the address of the cDAI token in the Ganache Testchain.
|
||||
*/
|
||||
function cDaiToken() public pure returns (ICToken) {
|
||||
return ICToken(0x615cba17EE82De39162BB87dBA9BcfD6E8BcF298);
|
||||
}
|
||||
|
||||
function convertChaiToDai(uint256 amount) external {
|
||||
_convertChaiToDai(amount);
|
||||
/**
|
||||
* @dev Tells the address of the Comptroller contract in the Ganache Testchain.
|
||||
*/
|
||||
function comptroller() public pure returns (IComptroller) {
|
||||
return IComptroller(0x85e855b22F01BdD33eE194490c7eB16b7EdaC019);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Tells the address of the COMP token in the Ganache Testchain.
|
||||
*/
|
||||
function compToken() public pure returns (ERC20) {
|
||||
return ERC20(0x6f51036Ec66B08cBFdb7Bd7Fb7F40b184482d724);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
pragma solidity 0.4.24;
|
||||
|
||||
interface IHarnessComptroller {
|
||||
function fastForward(uint256 blocks) external;
|
||||
}
|
|
@ -1,28 +0,0 @@
|
|||
pragma solidity 0.4.24;
|
||||
|
||||
import "../upgradeable_contracts/InterestReceiver.sol";
|
||||
|
||||
contract InterestReceiverMock is InterestReceiver {
|
||||
address private chaiTokenMock;
|
||||
address private daiTokenMock;
|
||||
|
||||
// solhint-disable no-empty-blocks
|
||||
constructor(address _owner, address _bridge, address _xDaiReceiver)
|
||||
public
|
||||
InterestReceiver(_owner, _bridge, _xDaiReceiver)
|
||||
{}
|
||||
// solhint-enable no-empty-blocks
|
||||
|
||||
function setChaiToken(IChai _chaiToken) external {
|
||||
chaiTokenMock = _chaiToken;
|
||||
daiTokenMock = _chaiToken.daiToken();
|
||||
}
|
||||
|
||||
function chaiToken() public view returns (IChai) {
|
||||
return IChai(chaiTokenMock);
|
||||
}
|
||||
|
||||
function daiToken() public view returns (ERC20) {
|
||||
return ERC20(daiTokenMock);
|
||||
}
|
||||
}
|
|
@ -1,188 +0,0 @@
|
|||
/// pot.sol -- Dai Savings Rate
|
||||
|
||||
// Copyright (C) 2018 Rain <rainbreak@riseup.net>
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
/* solhint-disable */
|
||||
pragma solidity 0.4.24;
|
||||
|
||||
/*
|
||||
"Savings Dai" is obtained when Dai is deposited into
|
||||
this contract. Each "Savings Dai" accrues Dai interest
|
||||
at the "Dai Savings Rate".
|
||||
This contract does not implement a user tradeable token
|
||||
and is intended to be used with adapters.
|
||||
--- `save` your `dai` in the `pot` ---
|
||||
- `dsr`: the Dai Savings Rate
|
||||
- `pie`: user balance of Savings Dai
|
||||
- `join`: start saving some dai
|
||||
- `exit`: remove some dai
|
||||
- `drip`: perform rate collection
|
||||
*/
|
||||
|
||||
contract VatLike {
|
||||
function move(address, address, uint256) external;
|
||||
function suck(address, address, uint256) external;
|
||||
}
|
||||
|
||||
contract PotMock {
|
||||
// --- Auth ---
|
||||
mapping(address => uint256) public wards;
|
||||
function rely(address guy) external auth {
|
||||
wards[guy] = 1;
|
||||
}
|
||||
function deny(address guy) external auth {
|
||||
wards[guy] = 0;
|
||||
}
|
||||
modifier auth {
|
||||
require(wards[msg.sender] == 1, "Pot/not-authorized");
|
||||
_;
|
||||
}
|
||||
|
||||
// --- Data ---
|
||||
mapping(address => uint256) public pie; // user Savings Dai
|
||||
|
||||
uint256 public Pie; // total Savings Dai
|
||||
uint256 public dsr; // the Dai Savings Rate
|
||||
uint256 public chi; // the Rate Accumulator
|
||||
|
||||
VatLike public vat; // CDP engine
|
||||
address public vow; // debt engine
|
||||
uint256 public rho; // time of last drip
|
||||
|
||||
uint256 public live; // Access Flag
|
||||
|
||||
// --- Init ---
|
||||
constructor(address vat_) public {
|
||||
wards[msg.sender] = 1;
|
||||
vat = VatLike(vat_);
|
||||
dsr = 1000000021979553151239153028; // 100% anually
|
||||
chi = ONE;
|
||||
rho = now;
|
||||
live = 1;
|
||||
}
|
||||
|
||||
// --- Math ---
|
||||
uint256 constant ONE = 10**27;
|
||||
function rpow(uint256 x, uint256 n, uint256 base) internal pure returns (uint256 z) {
|
||||
assembly {
|
||||
switch x
|
||||
case 0 {
|
||||
switch n
|
||||
case 0 {
|
||||
z := base
|
||||
}
|
||||
default {
|
||||
z := 0
|
||||
}
|
||||
}
|
||||
default {
|
||||
switch mod(n, 2)
|
||||
case 0 {
|
||||
z := base
|
||||
}
|
||||
default {
|
||||
z := x
|
||||
}
|
||||
let half := div(base, 2) // for rounding.
|
||||
for {
|
||||
n := div(n, 2)
|
||||
} n {
|
||||
n := div(n, 2)
|
||||
} {
|
||||
let xx := mul(x, x)
|
||||
if iszero(eq(div(xx, x), x)) {
|
||||
revert(0, 0)
|
||||
}
|
||||
let xxRound := add(xx, half)
|
||||
if lt(xxRound, xx) {
|
||||
revert(0, 0)
|
||||
}
|
||||
x := div(xxRound, base)
|
||||
if mod(n, 2) {
|
||||
let zx := mul(z, x)
|
||||
if and(iszero(iszero(x)), iszero(eq(div(zx, x), z))) {
|
||||
revert(0, 0)
|
||||
}
|
||||
let zxRound := add(zx, half)
|
||||
if lt(zxRound, zx) {
|
||||
revert(0, 0)
|
||||
}
|
||||
z := div(zxRound, base)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function rmul(uint256 x, uint256 y) internal pure returns (uint256 z) {
|
||||
z = mul(x, y) / ONE;
|
||||
}
|
||||
|
||||
function add(uint256 x, uint256 y) internal pure returns (uint256 z) {
|
||||
require((z = x + y) >= x);
|
||||
}
|
||||
|
||||
function sub(uint256 x, uint256 y) internal pure returns (uint256 z) {
|
||||
require((z = x - y) <= x);
|
||||
}
|
||||
|
||||
function mul(uint256 x, uint256 y) internal pure returns (uint256 z) {
|
||||
require(y == 0 || (z = x * y) / y == x);
|
||||
}
|
||||
|
||||
// --- Administration ---
|
||||
function file(bytes32 what, uint256 data) external auth {
|
||||
require(live == 1, "Pot/not-live");
|
||||
drip();
|
||||
require(now == rho, "Pot/rho-not-updated");
|
||||
if (what == "dsr") dsr = data;
|
||||
else revert("Pot/file-unrecognized-param");
|
||||
}
|
||||
|
||||
function file(bytes32 what, address addr) external auth {
|
||||
if (what == "vow") vow = addr;
|
||||
else revert("Pot/file-unrecognized-param");
|
||||
}
|
||||
|
||||
function cage() external auth {
|
||||
live = 0;
|
||||
dsr = ONE;
|
||||
}
|
||||
|
||||
// --- Savings Rate Accumulation ---
|
||||
function drip() public returns (uint256 tmp) {
|
||||
require(now >= rho, "Pot/invalid-now");
|
||||
tmp = rmul(rpow(dsr, now - rho, ONE), chi);
|
||||
uint256 chi_ = sub(tmp, chi);
|
||||
chi = tmp;
|
||||
rho = now;
|
||||
vat.suck(address(vow), address(this), mul(Pie, chi_));
|
||||
}
|
||||
|
||||
// --- Savings Dai Management ---
|
||||
function join(uint256 wad) external {
|
||||
require(now == rho, "Pot/rho-not-updated");
|
||||
pie[msg.sender] = add(pie[msg.sender], wad);
|
||||
Pie = add(Pie, wad);
|
||||
vat.move(msg.sender, address(this), mul(chi, wad));
|
||||
}
|
||||
|
||||
function exit(uint256 wad) external {
|
||||
pie[msg.sender] = sub(pie[msg.sender], wad);
|
||||
Pie = sub(Pie, wad);
|
||||
vat.move(address(this), msg.sender, mul(chi, wad));
|
||||
}
|
||||
}
|
|
@ -1,22 +0,0 @@
|
|||
pragma solidity 0.4.24;
|
||||
|
||||
contract GemLike {
|
||||
function transfer(address, uint256) external returns (bool);
|
||||
function transferFrom(address, address, uint256) external returns (bool);
|
||||
}
|
||||
|
||||
/**
|
||||
* @title PotMock2
|
||||
* @dev This contract is used for e2e tests only
|
||||
*/
|
||||
contract PotMock2 {
|
||||
// solhint-disable const-name-snakecase
|
||||
uint256 public constant dsr = 10**27; // the Dai Savings Rate
|
||||
uint256 public constant chi = 10**27; // the Rate Accumulator
|
||||
uint256 public constant rho = 10**27; // time of last drip
|
||||
// solhint-enable const-name-snakecase
|
||||
|
||||
function drip() external returns (uint256) {
|
||||
return chi;
|
||||
}
|
||||
}
|
|
@ -1,283 +0,0 @@
|
|||
/// vat.sol -- Dai CDP database
|
||||
|
||||
// Copyright (C) 2018 Rain <rainbreak@riseup.net>
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
/* solhint-disable */
|
||||
pragma solidity 0.4.24;
|
||||
|
||||
contract VatMock {
|
||||
// --- Auth ---
|
||||
mapping(address => uint256) public wards;
|
||||
function rely(address usr) external note auth {
|
||||
require(live == 1, "Vat/not-live");
|
||||
wards[usr] = 1;
|
||||
}
|
||||
function deny(address usr) external note auth {
|
||||
require(live == 1, "Vat/not-live");
|
||||
wards[usr] = 0;
|
||||
}
|
||||
modifier auth {
|
||||
require(wards[msg.sender] == 1, "Vat/not-authorized");
|
||||
_;
|
||||
}
|
||||
|
||||
mapping(address => mapping(address => uint256)) public can;
|
||||
function hope(address usr) external note {
|
||||
can[msg.sender][usr] = 1;
|
||||
}
|
||||
function nope(address usr) external note {
|
||||
can[msg.sender][usr] = 0;
|
||||
}
|
||||
function wish(address bit, address usr) internal view returns (bool) {
|
||||
return either(bit == usr, can[bit][usr] == 1);
|
||||
}
|
||||
|
||||
// --- Data ---
|
||||
struct Ilk {
|
||||
uint256 Art; // Total Normalised Debt [wad]
|
||||
uint256 rate; // Accumulated Rates [ray]
|
||||
uint256 spot; // Price with Safety Margin [ray]
|
||||
uint256 line; // Debt Ceiling [rad]
|
||||
uint256 dust; // Urn Debt Floor [rad]
|
||||
}
|
||||
struct Urn {
|
||||
uint256 ink; // Locked Collateral [wad]
|
||||
uint256 art; // Normalised Debt [wad]
|
||||
}
|
||||
|
||||
mapping(bytes32 => Ilk) public ilks;
|
||||
mapping(bytes32 => mapping(address => Urn)) public urns;
|
||||
mapping(bytes32 => mapping(address => uint256)) public gem; // [wad]
|
||||
mapping(address => uint256) public dai; // [rad]
|
||||
mapping(address => uint256) public sin; // [rad]
|
||||
|
||||
uint256 public debt; // Total Dai Issued [rad]
|
||||
uint256 public vice; // Total Unbacked Dai [rad]
|
||||
uint256 public Line; // Total Debt Ceiling [rad]
|
||||
uint256 public live; // Access Flag
|
||||
|
||||
// --- Logs ---
|
||||
event LogNote(bytes4 indexed sig, bytes32 indexed arg1, bytes32 indexed arg2, bytes32 indexed arg3, bytes data) anonymous;
|
||||
|
||||
modifier note {
|
||||
_;
|
||||
assembly {
|
||||
// log an 'anonymous' event with a constant 6 words of calldata
|
||||
// and four indexed topics: the selector and the first three args
|
||||
let mark := msize // end of memory ensures zero
|
||||
mstore(0x40, add(mark, 288)) // update free memory pointer
|
||||
mstore(mark, 0x20) // bytes type data offset
|
||||
mstore(add(mark, 0x20), 224) // bytes size (padded)
|
||||
calldatacopy(add(mark, 0x40), 0, 224) // bytes payload
|
||||
log4(
|
||||
mark,
|
||||
288, // calldata
|
||||
shl(224, shr(224, calldataload(0))), // msg.sig
|
||||
calldataload(4), // arg1
|
||||
calldataload(36), // arg2
|
||||
calldataload(68) // arg3
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// --- Init ---
|
||||
constructor() public {
|
||||
wards[msg.sender] = 1;
|
||||
live = 1;
|
||||
}
|
||||
|
||||
function setDai(address a, uint256 v) external {
|
||||
dai[a] = v;
|
||||
}
|
||||
|
||||
// --- Math ---
|
||||
function add(uint256 x, int256 y) internal pure returns (uint256 z) {
|
||||
z = x + uint256(y);
|
||||
require(y >= 0 || z <= x);
|
||||
require(y <= 0 || z >= x);
|
||||
}
|
||||
function sub(uint256 x, int256 y) internal pure returns (uint256 z) {
|
||||
z = x - uint256(y);
|
||||
require(y <= 0 || z <= x);
|
||||
require(y >= 0 || z >= x);
|
||||
}
|
||||
function mul(uint256 x, int256 y) internal pure returns (int256 z) {
|
||||
z = int256(x) * y;
|
||||
require(int256(x) >= 0);
|
||||
require(y == 0 || z / y == int256(x));
|
||||
}
|
||||
function add(uint256 x, uint256 y) internal pure returns (uint256 z) {
|
||||
require((z = x + y) >= x);
|
||||
}
|
||||
function sub(uint256 x, uint256 y) internal pure returns (uint256 z) {
|
||||
require((z = x - y) <= x);
|
||||
}
|
||||
function mul(uint256 x, uint256 y) internal pure returns (uint256 z) {
|
||||
require(y == 0 || (z = x * y) / y == x);
|
||||
}
|
||||
|
||||
// --- Administration ---
|
||||
function init(bytes32 ilk) external note auth {
|
||||
require(ilks[ilk].rate == 0, "Vat/ilk-already-init");
|
||||
ilks[ilk].rate = 10**27;
|
||||
}
|
||||
function file(bytes32 what, uint256 data) external note auth {
|
||||
require(live == 1, "Vat/not-live");
|
||||
if (what == "Line") Line = data;
|
||||
else revert("Vat/file-unrecognized-param");
|
||||
}
|
||||
function file(bytes32 ilk, bytes32 what, uint256 data) external note auth {
|
||||
require(live == 1, "Vat/not-live");
|
||||
if (what == "spot") ilks[ilk].spot = data;
|
||||
else if (what == "line") ilks[ilk].line = data;
|
||||
else if (what == "dust") ilks[ilk].dust = data;
|
||||
else revert("Vat/file-unrecognized-param");
|
||||
}
|
||||
function cage() external note auth {
|
||||
live = 0;
|
||||
}
|
||||
|
||||
// --- Fungibility ---
|
||||
function slip(bytes32 ilk, address usr, int256 wad) external note auth {
|
||||
gem[ilk][usr] = add(gem[ilk][usr], wad);
|
||||
}
|
||||
function flux(bytes32 ilk, address src, address dst, uint256 wad) external note {
|
||||
require(wish(src, msg.sender), "Vat/not-allowed");
|
||||
gem[ilk][src] = sub(gem[ilk][src], wad);
|
||||
gem[ilk][dst] = add(gem[ilk][dst], wad);
|
||||
}
|
||||
function move(address src, address dst, uint256 rad) external note {
|
||||
require(wish(src, msg.sender), "Vat/not-allowed");
|
||||
dai[src] = sub(dai[src], rad);
|
||||
dai[dst] = add(dai[dst], rad);
|
||||
}
|
||||
|
||||
function either(bool x, bool y) internal pure returns (bool z) {
|
||||
assembly {
|
||||
z := or(x, y)
|
||||
}
|
||||
}
|
||||
function both(bool x, bool y) internal pure returns (bool z) {
|
||||
assembly {
|
||||
z := and(x, y)
|
||||
}
|
||||
}
|
||||
|
||||
// --- CDP Manipulation ---
|
||||
function frob(bytes32 i, address u, address v, address w, int256 dink, int256 dart) external note {
|
||||
// system is live
|
||||
require(live == 1, "Vat/not-live");
|
||||
|
||||
Urn memory urn = urns[i][u];
|
||||
Ilk memory ilk = ilks[i];
|
||||
// ilk has been initialised
|
||||
require(ilk.rate != 0, "Vat/ilk-not-init");
|
||||
|
||||
urn.ink = add(urn.ink, dink);
|
||||
urn.art = add(urn.art, dart);
|
||||
ilk.Art = add(ilk.Art, dart);
|
||||
|
||||
int256 dtab = mul(ilk.rate, dart);
|
||||
uint256 tab = mul(ilk.rate, urn.art);
|
||||
debt = add(debt, dtab);
|
||||
|
||||
// either debt has decreased, or debt ceilings are not exceeded
|
||||
require(either(dart <= 0, both(mul(ilk.Art, ilk.rate) <= ilk.line, debt <= Line)), "Vat/ceiling-exceeded");
|
||||
// urn is either less risky than before, or it is safe
|
||||
require(either(both(dart <= 0, dink >= 0), tab <= mul(urn.ink, ilk.spot)), "Vat/not-safe");
|
||||
|
||||
// urn is either more safe, or the owner consents
|
||||
require(either(both(dart <= 0, dink >= 0), wish(u, msg.sender)), "Vat/not-allowed-u");
|
||||
// collateral src consents
|
||||
require(either(dink <= 0, wish(v, msg.sender)), "Vat/not-allowed-v");
|
||||
// debt dst consents
|
||||
require(either(dart >= 0, wish(w, msg.sender)), "Vat/not-allowed-w");
|
||||
|
||||
// urn has no debt, or a non-dusty amount
|
||||
require(either(urn.art == 0, tab >= ilk.dust), "Vat/dust");
|
||||
|
||||
gem[i][v] = sub(gem[i][v], dink);
|
||||
dai[w] = add(dai[w], dtab);
|
||||
|
||||
urns[i][u] = urn;
|
||||
ilks[i] = ilk;
|
||||
}
|
||||
// --- CDP Fungibility ---
|
||||
function fork(bytes32 ilk, address src, address dst, int256 dink, int256 dart) external note {
|
||||
Urn storage u = urns[ilk][src];
|
||||
Urn storage v = urns[ilk][dst];
|
||||
Ilk storage i = ilks[ilk];
|
||||
|
||||
u.ink = sub(u.ink, dink);
|
||||
u.art = sub(u.art, dart);
|
||||
v.ink = add(v.ink, dink);
|
||||
v.art = add(v.art, dart);
|
||||
|
||||
uint256 utab = mul(u.art, i.rate);
|
||||
uint256 vtab = mul(v.art, i.rate);
|
||||
|
||||
// both sides consent
|
||||
require(both(wish(src, msg.sender), wish(dst, msg.sender)), "Vat/not-allowed");
|
||||
|
||||
// both sides safe
|
||||
require(utab <= mul(u.ink, i.spot), "Vat/not-safe-src");
|
||||
require(vtab <= mul(v.ink, i.spot), "Vat/not-safe-dst");
|
||||
|
||||
// both sides non-dusty
|
||||
require(either(utab >= i.dust, u.art == 0), "Vat/dust-src");
|
||||
require(either(vtab >= i.dust, v.art == 0), "Vat/dust-dst");
|
||||
}
|
||||
// --- CDP Confiscation ---
|
||||
function grab(bytes32 i, address u, address v, address w, int256 dink, int256 dart) external note auth {
|
||||
Urn storage urn = urns[i][u];
|
||||
Ilk storage ilk = ilks[i];
|
||||
|
||||
urn.ink = add(urn.ink, dink);
|
||||
urn.art = add(urn.art, dart);
|
||||
ilk.Art = add(ilk.Art, dart);
|
||||
|
||||
int256 dtab = mul(ilk.rate, dart);
|
||||
|
||||
gem[i][v] = sub(gem[i][v], dink);
|
||||
sin[w] = sub(sin[w], dtab);
|
||||
vice = sub(vice, dtab);
|
||||
}
|
||||
|
||||
// --- Settlement ---
|
||||
function heal(uint256 rad) external note {
|
||||
address u = msg.sender;
|
||||
sin[u] = sub(sin[u], rad);
|
||||
dai[u] = sub(dai[u], rad);
|
||||
vice = sub(vice, rad);
|
||||
debt = sub(debt, rad);
|
||||
}
|
||||
function suck(address u, address v, uint256 rad) external note auth {
|
||||
sin[u] = add(sin[u], rad);
|
||||
dai[v] = add(dai[v], rad);
|
||||
vice = add(vice, rad);
|
||||
debt = add(debt, rad);
|
||||
}
|
||||
|
||||
// --- Rates ---
|
||||
function fold(bytes32 i, address u, int256 rate) external note auth {
|
||||
require(live == 1, "Vat/not-live");
|
||||
Ilk storage ilk = ilks[i];
|
||||
ilk.rate = add(ilk.rate, rate);
|
||||
int256 rad = mul(ilk.Art, rate);
|
||||
dai[u] = add(dai[u], rad);
|
||||
debt = add(debt, rad);
|
||||
}
|
||||
}
|
|
@ -56,7 +56,6 @@ contract BasicTokenBridge is EternalStorage, Ownable, DecimalShiftBridge {
|
|||
}
|
||||
|
||||
function getCurrentDay() public view returns (uint256) {
|
||||
// solhint-disable-next-line not-rely-on-time
|
||||
return now / 1 days;
|
||||
}
|
||||
|
||||
|
|
|
@ -1,316 +0,0 @@
|
|||
pragma solidity 0.4.24;
|
||||
|
||||
import "../interfaces/IChai.sol";
|
||||
import "../interfaces/ERC677Receiver.sol";
|
||||
import "./Ownable.sol";
|
||||
import "./ERC20Bridge.sol";
|
||||
import "./TokenSwapper.sol";
|
||||
import "openzeppelin-solidity/contracts/math/SafeMath.sol";
|
||||
|
||||
/**
|
||||
* @title ChaiConnector
|
||||
* @dev This logic allows to use Chai token (https://github.com/dapphub/chai)
|
||||
*/
|
||||
contract ChaiConnector is Ownable, ERC20Bridge, TokenSwapper {
|
||||
using SafeMath for uint256;
|
||||
|
||||
// emitted when specified value of Chai tokens is transfered to interest receiver
|
||||
event PaidInterest(address to, uint256 value);
|
||||
|
||||
bytes32 internal constant CHAI_TOKEN_ENABLED = 0x2ae87563606f93f71ad2adf4d62661ccdfb63f3f508f94700934d5877fb92278; // keccak256(abi.encodePacked("chaiTokenEnabled"))
|
||||
bytes32 internal constant INTEREST_RECEIVER = 0xd88509eb1a8da5d5a2fc7b9bad1c72874c9818c788e81d0bc46b29bfaa83adf6; // keccak256(abi.encodePacked("interestReceiver"))
|
||||
bytes32 internal constant INTEREST_COLLECTION_PERIOD = 0x68a6a652d193e5d6439c4309583048050a11a4cfb263a220f4cd798c61c3ad6e; // keccak256(abi.encodePacked("interestCollectionPeriod"))
|
||||
bytes32 internal constant LAST_TIME_INTEREST_PAID = 0xcabd46177a706f95f4bb3e2c2ba45ac4aa1eac9c545425a19c62ab6de4aeea26; // keccak256(abi.encodePacked("lastTimeInterestPaid"))
|
||||
bytes32 internal constant INVESTED_AMOUNT = 0xb6afb3323c9d7dc0e9dab5d34c3a1d1ae7739d2224c048d4ee7675d3c759dd1b; // keccak256(abi.encodePacked("investedAmount"))
|
||||
bytes32 internal constant MIN_DAI_TOKEN_BALANCE = 0xce70e1dac97909c26a87aa4ada3d490673a153b3a75b22ea3364c4c7df7c551f; // keccak256(abi.encodePacked("minDaiTokenBalance"))
|
||||
bytes4 internal constant ON_TOKEN_TRANSFER = 0xa4c0ed36; // onTokenTransfer(address,uint256,bytes)
|
||||
|
||||
uint256 internal constant ONE = 10**27;
|
||||
|
||||
/**
|
||||
* @dev Throws if chai token is not enabled
|
||||
*/
|
||||
modifier chaiTokenEnabled {
|
||||
require(isChaiTokenEnabled());
|
||||
/* solcov ignore next */
|
||||
_;
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Fixed point division
|
||||
* @return Ceiled value of x / y
|
||||
*/
|
||||
function rdivup(uint256 x, uint256 y) internal pure returns (uint256) {
|
||||
return x.mul(ONE).add(y.sub(1)) / y;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return true, if chai token is enabled
|
||||
*/
|
||||
function isChaiTokenEnabled() public view returns (bool) {
|
||||
return boolStorage[CHAI_TOKEN_ENABLED];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Chai token contract address
|
||||
*/
|
||||
function chaiToken() public view returns (IChai) {
|
||||
return IChai(0x06AF07097C9Eeb7fD685c692751D5C66dB49c215);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Initializes chai token
|
||||
*/
|
||||
function initializeChaiToken() public onlyOwner {
|
||||
require(!isChaiTokenEnabled());
|
||||
require(address(chaiToken().daiToken()) == address(erc20token()));
|
||||
boolStorage[CHAI_TOKEN_ENABLED] = true;
|
||||
uintStorage[MIN_DAI_TOKEN_BALANCE] = 100 ether;
|
||||
uintStorage[INTEREST_COLLECTION_PERIOD] = 1 weeks;
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Initializes chai token, with interestReceiver
|
||||
* @param _interestReceiver Receiver address
|
||||
*/
|
||||
function initializeChaiToken(address _interestReceiver) external {
|
||||
require(_interestReceiver != address(0));
|
||||
// onlyOwner condition is checked inside this call, so it can be excluded from function definition
|
||||
initializeChaiToken();
|
||||
addressStorage[INTEREST_RECEIVER] = _interestReceiver;
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Sets minimum DAI limit, needed for converting DAI into CHAI
|
||||
*/
|
||||
function setMinDaiTokenBalance(uint256 _minBalance) external onlyOwner {
|
||||
uintStorage[MIN_DAI_TOKEN_BALANCE] = _minBalance;
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Evaluates edge DAI token balance, which has an impact on the invest amounts
|
||||
* @return Value in DAI
|
||||
*/
|
||||
function minDaiTokenBalance() public view returns (uint256) {
|
||||
return uintStorage[MIN_DAI_TOKEN_BALANCE];
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Withdraws all invested tokens, pays remaining interest, removes chai token from contract storage
|
||||
*/
|
||||
function removeChaiToken() external onlyOwner chaiTokenEnabled {
|
||||
_convertChaiToDai(investedAmountInDai());
|
||||
_payInterest();
|
||||
delete boolStorage[CHAI_TOKEN_ENABLED];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Configured address of a receiver
|
||||
*/
|
||||
function interestReceiver() public view returns (ERC677Receiver) {
|
||||
return ERC677Receiver(addressStorage[INTEREST_RECEIVER]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates interest receiver address
|
||||
* @param receiver New receiver address
|
||||
*/
|
||||
function setInterestReceiver(address receiver) external onlyOwner {
|
||||
// the bridge account is not allowed to receive an interest by the following reason:
|
||||
// during the Chai to Dai convertion, the Dai is minted to the receiver account,
|
||||
// the Transfer(address(0), bridgeAddress, value) is emitted during this process,
|
||||
// something can go wrong in the oracle logic, so that it will process this event as a request to the bridge
|
||||
// Instead, the interest can be transfered to any other account, and then converted to Dai,
|
||||
// which won't be related to the oracle logic anymore
|
||||
require(receiver != address(this));
|
||||
|
||||
addressStorage[INTEREST_RECEIVER] = receiver;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Timestamp of last interest payment
|
||||
*/
|
||||
function lastInterestPayment() public view returns (uint256) {
|
||||
return uintStorage[LAST_TIME_INTEREST_PAID];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Configured minimum interest collection period
|
||||
*/
|
||||
function interestCollectionPeriod() public view returns (uint256) {
|
||||
return uintStorage[INTEREST_COLLECTION_PERIOD];
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Configures minimum interest collection period
|
||||
* @param period collection period
|
||||
*/
|
||||
function setInterestCollectionPeriod(uint256 period) external onlyOwner {
|
||||
uintStorage[INTEREST_COLLECTION_PERIOD] = period;
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Pays all available interest, in Dai tokens.
|
||||
* Upgradeability owner can call this method without time restrictions,
|
||||
* for others, the method can be called only once a specified period.
|
||||
*/
|
||||
function payInterest() external chaiTokenEnabled {
|
||||
if (
|
||||
// solhint-disable-next-line not-rely-on-time
|
||||
lastInterestPayment() + interestCollectionPeriod() < now ||
|
||||
IUpgradeabilityOwnerStorage(this).upgradeabilityOwner() == msg.sender
|
||||
) {
|
||||
_payInterest();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Internal function for paying all available interest, in Dai tokens
|
||||
*/
|
||||
function _payInterest() internal {
|
||||
address receiver = address(interestReceiver());
|
||||
require(receiver != address(0));
|
||||
|
||||
// since investedAmountInChai() returns a ceiled value,
|
||||
// the value of chaiBalance() - investedAmountInChai() will be floored,
|
||||
// leading to excess remaining chai balance
|
||||
|
||||
// solhint-disable-next-line not-rely-on-time
|
||||
uintStorage[LAST_TIME_INTEREST_PAID] = now;
|
||||
|
||||
uint256 interest = chaiBalance().sub(investedAmountInChai());
|
||||
// interest is paid in Chai, paying interest directly in Dai can cause an unwanter Transfer event
|
||||
// see a comment in setInterestReceiver describing why we cannot pay interest to the bridge directly
|
||||
chaiToken().transfer(receiver, interest);
|
||||
|
||||
receiver.call(abi.encodeWithSelector(ON_TOKEN_TRANSFER, address(this), interest, ""));
|
||||
|
||||
// Additional constant to tolerate the DAI balance deposited to the Chai token is not needed here, since we allow to withdraw only extra part of chai balance,
|
||||
// which is not necessary to cover 100% dai balance.
|
||||
// It is guaranteed that the withdrawal of interest won't left the bridge balance uncovered.
|
||||
require(dsrBalance() >= investedAmountInDai());
|
||||
|
||||
emit PaidInterest(receiver, interest);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Evaluates bridge balance for tokens, holded in DSR
|
||||
* @return Balance in dai, truncated
|
||||
*/
|
||||
function dsrBalance() public view returns (uint256) {
|
||||
return chaiToken().dai(address(this));
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Evaluates bridge balance in Chai tokens
|
||||
* @return Balance in chai, exact
|
||||
*/
|
||||
function chaiBalance() public view returns (uint256) {
|
||||
return chaiToken().balanceOf(address(this));
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Evaluates bridge balance in Dai tokens
|
||||
* @return Balance in Dai
|
||||
*/
|
||||
function daiBalance() internal view returns (uint256) {
|
||||
return erc20token().balanceOf(address(this));
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Evaluates exact current invested amount, in DAI
|
||||
* @return Value in DAI
|
||||
*/
|
||||
function investedAmountInDai() public view returns (uint256) {
|
||||
return uintStorage[INVESTED_AMOUNT];
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Updates current invested amount, in DAI
|
||||
* @return Value in DAI
|
||||
*/
|
||||
function setInvestedAmountInDai(uint256 amount) internal {
|
||||
uintStorage[INVESTED_AMOUNT] = amount;
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Evaluates amount of chai tokens that is sufficent to cover 100% of the invested DAI
|
||||
* @return Amount in chai, ceiled
|
||||
*/
|
||||
function investedAmountInChai() internal returns (uint256) {
|
||||
IPot pot = chaiToken().pot();
|
||||
// solhint-disable-next-line not-rely-on-time
|
||||
uint256 chi = (now > pot.rho()) ? pot.drip() : pot.chi();
|
||||
return rdivup(investedAmountInDai(), chi);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Checks if DAI balance is high enough to be partially converted to Chai
|
||||
* Twice limit is used in order to decrease frequency of convertDaiToChai calls,
|
||||
* In case of high bridge utilization in DAI => xDAI direction,
|
||||
* convertDaiToChai() will be called as soon as DAI balance reaches 2 * limit,
|
||||
* limit DAI will be left as a buffer for future operations.
|
||||
* @return true if convertDaiToChai() call is needed to be performed by the oracle
|
||||
*/
|
||||
function isDaiNeedsToBeInvested() public view returns (bool) {
|
||||
// chai token needs to be initialized, DAI balance should be at least twice greater than minDaiTokenBalance
|
||||
return isChaiTokenEnabled() && daiBalance() > 2 * minDaiTokenBalance();
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Converts all DAI into Chai tokens, keeping minDaiTokenBalance() DAI as a buffer
|
||||
*/
|
||||
function convertDaiToChai() public chaiTokenEnabled {
|
||||
// there is not need to consider overflow when performing a + operation,
|
||||
// since both values are controlled by the bridge and can't take extremely high values
|
||||
uint256 amount = daiBalance().sub(minDaiTokenBalance());
|
||||
|
||||
require(amount > 0); // revert and save gas if there is nothing to convert
|
||||
|
||||
uint256 newInvestedAmountInDai = investedAmountInDai() + amount;
|
||||
setInvestedAmountInDai(newInvestedAmountInDai);
|
||||
erc20token().approve(chaiToken(), amount);
|
||||
chaiToken().join(address(this), amount);
|
||||
|
||||
// When evaluating the amount of DAI kept in Chai using dsrBalance(), there are some fixed point truncations.
|
||||
// The dependency between invested amount of DAI - value and returned value of dsrBalance() - res is the following:
|
||||
// res = floor(floor(value / K) * K)), where K is the fixed-point coefficient
|
||||
// from MakerDAO Pot contract (K = pot.chi() / 10**27).
|
||||
// This can lead up to losses of ceil(K) DAI in this balance evaluation.
|
||||
// The constant is needed here for making sure that everything works fine, and this error is small enough
|
||||
// The 10000 constant is considered to be small enough when decimals = 18, however,
|
||||
// it is not recommended to use it for smaller values of decimals, since it won't be negligible anymore
|
||||
require(dsrBalance() + 10000 >= newInvestedAmountInDai);
|
||||
|
||||
emit TokensSwapped(erc20token(), chaiToken(), amount);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Redeems DAI from Chai, the total redeemed amount will be at least equal to specified amount
|
||||
* @param amount Amount of DAI to redeem
|
||||
*/
|
||||
function _convertChaiToDai(uint256 amount) internal {
|
||||
if (amount == 0) return;
|
||||
|
||||
uint256 invested = investedAmountInDai();
|
||||
uint256 initialDaiBalance = daiBalance();
|
||||
|
||||
// onExecuteMessage can call a convert operation with argument greater than the current invested amount,
|
||||
// in this case bridge should withdraw all invested funds
|
||||
uint256 withdrawal = amount >= invested ? invested : amount;
|
||||
|
||||
chaiToken().draw(address(this), withdrawal);
|
||||
uint256 redeemed = daiBalance() - initialDaiBalance;
|
||||
|
||||
// Make sure that at least withdrawal amount was withdrawn
|
||||
require(redeemed >= withdrawal);
|
||||
|
||||
uint256 newInvested = invested > redeemed ? invested - redeemed : 0;
|
||||
setInvestedAmountInDai(newInvested);
|
||||
|
||||
// see comment in convertDaiToChai() for similar statement
|
||||
require(dsrBalance() + 10000 >= newInvested);
|
||||
|
||||
emit TokensSwapped(chaiToken(), erc20token(), redeemed);
|
||||
}
|
||||
}
|
|
@ -1,111 +0,0 @@
|
|||
pragma solidity 0.4.24;
|
||||
|
||||
import "openzeppelin-solidity/contracts/token/ERC20/ERC20.sol";
|
||||
import "openzeppelin-solidity/contracts/ownership/Ownable.sol";
|
||||
import "openzeppelin-solidity/contracts/AddressUtils.sol";
|
||||
import "../interfaces/IChai.sol";
|
||||
import "../interfaces/ERC677Receiver.sol";
|
||||
import "./Claimable.sol";
|
||||
import "./TokenSwapper.sol";
|
||||
|
||||
/**
|
||||
* @title InterestReceiver
|
||||
* @dev Сontract for receiving Chai interest and immediatly converting it into Dai.
|
||||
* Contract also will try to automaticaly relay tokens to configured xDai receiver
|
||||
*/
|
||||
contract InterestReceiver is ERC677Receiver, Ownable, Claimable, TokenSwapper {
|
||||
bytes4 internal constant RELAY_TOKENS = 0x01e4f53a; // relayTokens(address,uint256)
|
||||
|
||||
address public bridgeContract;
|
||||
address public receiverInXDai;
|
||||
|
||||
event RelayTokensFailed(address receiver, uint256 amount);
|
||||
|
||||
/**
|
||||
* @dev Initializes interest receiver, sets an owner of a contract
|
||||
* @param _owner address of owner account, only owner can withdraw Dai tokens from contract
|
||||
* @param _bridgeContract address of the bridge contract in the foreign chain
|
||||
* @param _receiverInXDai address of the receiver account, in the xDai chain
|
||||
*/
|
||||
constructor(address _owner, address _bridgeContract, address _receiverInXDai) public {
|
||||
require(AddressUtils.isContract(_bridgeContract));
|
||||
require(_receiverInXDai != address(0));
|
||||
_transferOwnership(_owner);
|
||||
bridgeContract = _bridgeContract;
|
||||
receiverInXDai = _receiverInXDai;
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Updates bridge contract from which interest is expected to come from,
|
||||
* the incoming tokens will be relayed through this bridge also
|
||||
* @param _bridgeContract address of new contract in the foreign chain
|
||||
*/
|
||||
function setBridgeContract(address _bridgeContract) external onlyOwner {
|
||||
require(AddressUtils.isContract(_bridgeContract));
|
||||
bridgeContract = _bridgeContract;
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Updates receiver address in the xDai chain
|
||||
* @param _receiverInXDai address of new receiver account in the xDai chain
|
||||
*/
|
||||
function setReceiverInXDai(address _receiverInXDai) external onlyOwner {
|
||||
require(_receiverInXDai != address(0));
|
||||
receiverInXDai = _receiverInXDai;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Chai token contract address
|
||||
*/
|
||||
function chaiToken() public view returns (IChai) {
|
||||
return IChai(0x06AF07097C9Eeb7fD685c692751D5C66dB49c215);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Dai token contract address
|
||||
*/
|
||||
function daiToken() public view returns (ERC20) {
|
||||
return ERC20(0x6B175474E89094C44Da98b954EedeAC495271d0F);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev ERC677 transfer callback function, received interest is converted from Chai token into Dai
|
||||
* and then relayed via bridge to xDai receiver
|
||||
*/
|
||||
function onTokenTransfer(address, uint256, bytes) external returns (bool) {
|
||||
uint256 chaiBalance = chaiToken().balanceOf(address(this));
|
||||
uint256 initialDaiBalance = daiToken().balanceOf(address(this));
|
||||
uint256 finalDaiBalance = initialDaiBalance;
|
||||
|
||||
if (chaiBalance > 0) {
|
||||
chaiToken().exit(address(this), chaiBalance);
|
||||
|
||||
finalDaiBalance = daiToken().balanceOf(address(this));
|
||||
// Dai balance cannot decrease here, so SafeMath is not needed
|
||||
uint256 redeemed = finalDaiBalance - initialDaiBalance;
|
||||
|
||||
emit TokensSwapped(chaiToken(), daiToken(), redeemed);
|
||||
|
||||
// chi is always >= 10**27, so chai/dai rate is always >= 1
|
||||
require(redeemed >= chaiBalance);
|
||||
}
|
||||
|
||||
daiToken().approve(address(bridgeContract), finalDaiBalance);
|
||||
if (!bridgeContract.call(abi.encodeWithSelector(RELAY_TOKENS, receiverInXDai, finalDaiBalance))) {
|
||||
daiToken().approve(address(bridgeContract), 0);
|
||||
emit RelayTokensFailed(receiverInXDai, finalDaiBalance);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Claims tokens from receiver account
|
||||
* @param _token address of claimed token, address(0) for native
|
||||
* @param _to address of tokens receiver
|
||||
*/
|
||||
function claimTokens(address _token, address _to) external onlyOwner {
|
||||
// Only tokens other than CHAI/DAI can be claimed from this contract.
|
||||
require(_token != address(chaiToken()) && _token != address(daiToken()));
|
||||
claimValues(_token, _to);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,47 @@
|
|||
pragma solidity 0.4.24;
|
||||
|
||||
import "openzeppelin-solidity/contracts/token/ERC20/ERC20.sol";
|
||||
import "openzeppelin-solidity/contracts/ownership/Ownable.sol";
|
||||
import "../interfaces/IInterestReceiver.sol";
|
||||
import "../interfaces/IUniswapRouterV2.sol";
|
||||
import "./Claimable.sol";
|
||||
|
||||
/**
|
||||
* @title InterestReceiverBase
|
||||
* @dev Base abstract contract for common functionality of interest receivers in erc-to-native bridge.
|
||||
* Contracts inherited from the contract can receive DAI and COMP tokens.
|
||||
*/
|
||||
contract InterestReceiverBase is IInterestReceiver, Ownable, Claimable {
|
||||
ERC20 public constant daiToken = ERC20(0x6B175474E89094C44Da98b954EedeAC495271d0F);
|
||||
ERC20 public constant compToken = ERC20(0xc00e94Cb662C3520282E6f5717214004A7f26888);
|
||||
ERC20 public constant wethToken = ERC20(0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2);
|
||||
IUniswapRouterV2 public constant uniswapRouterV2 = IUniswapRouterV2(0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D);
|
||||
|
||||
uint256 public minReceivedFraction = 0;
|
||||
|
||||
constructor() public {
|
||||
daiToken.approve(address(uniswapRouterV2), uint256(-1));
|
||||
compToken.approve(address(uniswapRouterV2), uint256(-1));
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Updates the slippage parameter for the Uniswap operations.
|
||||
* Only owner can call this method.
|
||||
* @param _minFraction minimum percentage allowed to be received w.r.t. 1 ether (0.9 ether = 90%),
|
||||
* slippage = 1 ether - minReceivedFraction.
|
||||
*/
|
||||
function setMinFractionReceived(uint256 _minFraction) external onlyOwner {
|
||||
require(_minFraction < 1 ether);
|
||||
minReceivedFraction = _minFraction;
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Allows to transfer any locked token from this contract.
|
||||
* Only owner can call this method.
|
||||
* @param _token address of the token, if it is not provided (0x00..00), native coins will be transferred.
|
||||
* @param _to address that will receive the locked tokens on this contract.
|
||||
*/
|
||||
function claimTokens(address _token, address _to) external onlyOwner {
|
||||
claimValues(_token, _to);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,39 @@
|
|||
pragma solidity 0.4.24;
|
||||
|
||||
import "./InterestReceiverBase.sol";
|
||||
|
||||
/**
|
||||
* @title InterestReceiverStakeBuyback
|
||||
* @dev This contract is intended to be used together with InterestConnector module of the erc-to-native bridge.
|
||||
* Contract receives DAI and COMP tokens. All received tokens are swapped to STAKE token and burnt.
|
||||
*/
|
||||
contract InterestReceiverStakeBuyback is InterestReceiverBase {
|
||||
ERC20 public constant stakeToken = ERC20(0x0Ae055097C6d159879521C384F1D2123D1f195e6);
|
||||
|
||||
address public constant burnAddress = 0x000000000000000000000000000000000000dEaD;
|
||||
|
||||
/**
|
||||
* @dev Callback function for notifying this contract about received interest.
|
||||
* @param _token address of the token contract. Should be COMP or DAI token address.
|
||||
*/
|
||||
function onInterestReceived(address _token) external {
|
||||
address[] memory path = new address[](3);
|
||||
path[0] = _token;
|
||||
path[1] = wethToken;
|
||||
path[2] = address(stakeToken);
|
||||
uint256 amount = ERC20(_token).balanceOf(address(this));
|
||||
|
||||
// (min received %) * (amount / 1 DAI) * (STAKE per 1 DAI)
|
||||
uint256 minAmount = (minReceivedFraction * amount * uniswapRouterV2.getAmountsOut(1 ether, path)[2]) / 10**36;
|
||||
|
||||
bytes memory data = abi.encodeWithSelector(
|
||||
uniswapRouterV2.swapExactTokensForTokens.selector,
|
||||
amount,
|
||||
minAmount,
|
||||
path,
|
||||
burnAddress,
|
||||
now
|
||||
);
|
||||
address(uniswapRouterV2).call(data);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,40 @@
|
|||
pragma solidity 0.4.24;
|
||||
|
||||
import "./InterestReceiverBase.sol";
|
||||
|
||||
/**
|
||||
* @title InterestReceiverSwapToETH
|
||||
* @dev This contract is intended to be used together with InterestConnector module of the erc-to-native bridge.
|
||||
* Contract receives DAI and COMP tokens. All received tokens are swapped to ETH and kept on this contract.
|
||||
* Later, they can be withdrawn by the owner.
|
||||
*/
|
||||
contract InterestReceiverSwapToETH is InterestReceiverBase {
|
||||
/**
|
||||
* @dev Callback function for notifying this contract about received interest.
|
||||
* @param _token address of the token contract. Should be COMP or DAI token address.
|
||||
*/
|
||||
function onInterestReceived(address _token) external {
|
||||
address[] memory path = new address[](2);
|
||||
path[0] = _token;
|
||||
path[1] = wethToken;
|
||||
uint256 amount = ERC20(_token).balanceOf(address(this));
|
||||
|
||||
// (min received %) * (amount / 1 DAI) * (ETH per 1 DAI)
|
||||
uint256 minAmount = (minReceivedFraction * amount * uniswapRouterV2.getAmountsOut(1 ether, path)[1]) / 10**36;
|
||||
|
||||
bytes memory data = abi.encodeWithSelector(
|
||||
uniswapRouterV2.swapExactTokensForETH.selector,
|
||||
amount,
|
||||
minAmount,
|
||||
path,
|
||||
address(this),
|
||||
now
|
||||
);
|
||||
address(uniswapRouterV2).call(data);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Fallback function for receiving native coins from the Uniswap Router contract when using swapExactTokensForETH.
|
||||
*/
|
||||
function() external payable {}
|
||||
}
|
|
@ -16,9 +16,7 @@ contract BlockReward is EternalStorage {
|
|||
bytes32 internal constant MINTED_IN_BLOCK = 0x3840e646f7ce9b3210f5440e2dbd6b36451169bfdac65ef00a161729eded81bd; // keccak256(abi.encodePacked("mintedInBlock"))
|
||||
bytes32 internal constant MINTED_TOTALLY_BY_BRIDGE = 0x12e71282a577e2b463da2c18bc96b6122db29bcef9065ed5a7f0f9316c11c08e; // keccak256(abi.encodePacked("mintedTotallyByBridge"))
|
||||
|
||||
// solhint-disable const-name-snakecase
|
||||
uint256 public constant bridgesAllowedLength = 1;
|
||||
// solhint-enable const-name-snakecase
|
||||
|
||||
event AddedReceiver(uint256 amount, address indexed receiver, address indexed bridge);
|
||||
|
||||
|
|
|
@ -18,16 +18,12 @@ contract HomeFeeManagerAMBNativeToErc20 is BaseMediatorFeeManager {
|
|||
constructor(address _owner, uint256 _fee, address[] _rewardAccountList, address _mediatorContract)
|
||||
public
|
||||
BaseMediatorFeeManager(_owner, _fee, _rewardAccountList, _mediatorContract)
|
||||
{
|
||||
// solhint-disable-previous-line no-empty-blocks
|
||||
}
|
||||
{}
|
||||
|
||||
/**
|
||||
* @dev Fallback method to receive the fees.
|
||||
*/
|
||||
function() public payable {
|
||||
// solhint-disable-previous-line no-empty-blocks
|
||||
}
|
||||
function() public payable {}
|
||||
|
||||
/**
|
||||
* @dev Transfer the fee as native tokens to the reward account.
|
||||
|
|
|
@ -0,0 +1,101 @@
|
|||
pragma solidity 0.4.24;
|
||||
|
||||
import "./InterestConnector.sol";
|
||||
import "../../interfaces/ICToken.sol";
|
||||
import "../../interfaces/IComptroller.sol";
|
||||
|
||||
/**
|
||||
* @title CompoundConnector
|
||||
* @dev This contract allows to partially invest locked Dai tokens into Compound protocol.
|
||||
*/
|
||||
contract CompoundConnector is InterestConnector {
|
||||
uint256 internal constant SUCCESS = 0;
|
||||
|
||||
/**
|
||||
* @dev Tells the address of the DAI token in the Ethereum Mainnet.
|
||||
*/
|
||||
function daiToken() public pure returns (ERC20) {
|
||||
return ERC20(0x6B175474E89094C44Da98b954EedeAC495271d0F);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Tells the address of the cDAI token in the Ethereum Mainnet.
|
||||
*/
|
||||
function cDaiToken() public pure returns (ICToken) {
|
||||
return ICToken(0x5d3a536E4D6DbD6114cc1Ead35777bAB948E3643);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Tells the address of the Comptroller contract in the Ethereum Mainnet.
|
||||
*/
|
||||
function comptroller() public pure returns (IComptroller) {
|
||||
return IComptroller(0x3d9819210A31b4961b30EF54bE2aeD79B9c9Cd3B);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Tells the address of the COMP token in the Ethereum Mainnet.
|
||||
*/
|
||||
function compToken() public pure returns (ERC20) {
|
||||
return ERC20(0xc00e94Cb662C3520282E6f5717214004A7f26888);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Tells the current earned interest amount.
|
||||
* @param _token address of the underlying token contract.
|
||||
* @return total amount of interest that can be withdrawn now.
|
||||
*/
|
||||
function interestAmount(address _token) public view returns (uint256) {
|
||||
uint256 underlyingBalance = cDaiToken().balanceOfUnderlying(address(this));
|
||||
// 1 DAI is reserved for possible truncation/round errors
|
||||
uint256 invested = investedAmount(_token) + 1 ether;
|
||||
return underlyingBalance > invested ? underlyingBalance - invested : 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Tells if interest earning is supported for the specific token contract.
|
||||
* @param _token address of the token contract.
|
||||
* @return true, if interest earning is supported.
|
||||
*/
|
||||
function _isInterestSupported(address _token) internal pure returns (bool) {
|
||||
return _token == address(daiToken());
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Invests the given amount of tokens to the Compound protocol.
|
||||
* Converts _amount of TOKENs into X cTOKENs.
|
||||
* @param _token address of the token contract.
|
||||
* @param _amount amount of tokens to invest.
|
||||
*/
|
||||
function _invest(address _token, uint256 _amount) internal {
|
||||
(_token);
|
||||
daiToken().approve(address(cDaiToken()), _amount);
|
||||
require(cDaiToken().mint(_amount) == SUCCESS);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Withdraws at least the given amount of tokens from the Compound protocol.
|
||||
* Converts X cTOKENs into _amount of TOKENs.
|
||||
* @param _token address of the token contract.
|
||||
* @param _amount minimal amount of tokens to withdraw.
|
||||
*/
|
||||
function _withdrawTokens(address _token, uint256 _amount) internal {
|
||||
(_token);
|
||||
require(cDaiToken().redeemUnderlying(_amount) == SUCCESS);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Claims Comp token and transfers it to the associated interest receiver.
|
||||
*/
|
||||
function claimCompAndPay() external {
|
||||
address[] memory holders = new address[](1);
|
||||
holders[0] = address(this);
|
||||
address[] memory markets = new address[](1);
|
||||
markets[0] = address(cDaiToken());
|
||||
comptroller().claimComp(holders, markets, false, true);
|
||||
|
||||
address comp = address(compToken());
|
||||
uint256 interest = _selfBalance(comp);
|
||||
require(interest >= minInterestPaid(comp));
|
||||
_transferInterest(comp, interest);
|
||||
}
|
||||
}
|
|
@ -3,9 +3,9 @@ pragma solidity 0.4.24;
|
|||
import "../BasicForeignBridge.sol";
|
||||
import "../ERC20Bridge.sol";
|
||||
import "../OtherSideBridgeStorage.sol";
|
||||
import "../ChaiConnector.sol";
|
||||
import "./CompoundConnector.sol";
|
||||
|
||||
contract ForeignBridgeErcToNative is BasicForeignBridge, ERC20Bridge, OtherSideBridgeStorage, ChaiConnector {
|
||||
contract ForeignBridgeErcToNative is BasicForeignBridge, ERC20Bridge, OtherSideBridgeStorage, CompoundConnector {
|
||||
function initialize(
|
||||
address _validatorContract,
|
||||
address _erc20token,
|
||||
|
@ -39,6 +39,26 @@ contract ForeignBridgeErcToNative is BasicForeignBridge, ERC20Bridge, OtherSideB
|
|||
return 0x18762d46; // bytes4(keccak256(abi.encodePacked("erc-to-native-core")))
|
||||
}
|
||||
|
||||
function upgradeTo530(address _interestReceiver) external {
|
||||
require(msg.sender == address(this));
|
||||
|
||||
address dai = address(daiToken());
|
||||
address comp = address(compToken());
|
||||
_setInterestEnabled(dai, true);
|
||||
_setMinCashThreshold(dai, 1000000 ether);
|
||||
_setMinInterestPaid(dai, 1000 ether);
|
||||
_setInterestReceiver(dai, _interestReceiver);
|
||||
|
||||
_setMinInterestPaid(comp, 1 ether);
|
||||
_setInterestReceiver(comp, _interestReceiver);
|
||||
|
||||
invest(dai);
|
||||
}
|
||||
|
||||
function investDai() external {
|
||||
invest(address(daiToken()));
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Withdraws the erc20 tokens or native coins from this contract.
|
||||
* @param _token address of the claimed token or address(0) for native coins.
|
||||
|
@ -46,9 +66,10 @@ contract ForeignBridgeErcToNative is BasicForeignBridge, ERC20Bridge, OtherSideB
|
|||
*/
|
||||
function claimTokens(address _token, address _to) external onlyIfUpgradeabilityOwner {
|
||||
// Since bridged tokens are locked at this contract, it is not allowed to claim them with the use of claimTokens function
|
||||
require(_token != address(erc20token()));
|
||||
// Chai token is not claimable if investing into Chai is enabled
|
||||
require(_token != address(chaiToken()) || !isChaiTokenEnabled());
|
||||
address bridgedToken = address(erc20token());
|
||||
require(_token != address(bridgedToken));
|
||||
require(_token != address(cDaiToken()) || !isInterestEnabled(bridgedToken));
|
||||
require(_token != address(compToken()) || !isInterestEnabled(bridgedToken));
|
||||
claimValues(_token, _to);
|
||||
}
|
||||
|
||||
|
@ -60,29 +81,21 @@ contract ForeignBridgeErcToNative is BasicForeignBridge, ERC20Bridge, OtherSideB
|
|||
addTotalExecutedPerDay(getCurrentDay(), _amount);
|
||||
uint256 amount = _unshiftValue(_amount);
|
||||
|
||||
uint256 currentBalance = tokenBalance(erc20token());
|
||||
ERC20 token = erc20token();
|
||||
uint256 currentBalance = token.balanceOf(address(this));
|
||||
|
||||
// Convert part of Chai tokens back to DAI, if DAI balance is insufficient.
|
||||
// If Chai token is disabled, bridge will keep all funds directly in DAI token,
|
||||
// so it will have enough funds to cover any xDai => Dai transfer,
|
||||
// and currentBalance >= amount will always hold.
|
||||
if (currentBalance < amount) {
|
||||
_convertChaiToDai(amount.sub(currentBalance).add(minDaiTokenBalance()));
|
||||
uint256 withdrawAmount = (amount - currentBalance).add(minCashThreshold(address(token)));
|
||||
_withdraw(address(token), withdrawAmount);
|
||||
}
|
||||
|
||||
bool res = erc20token().transfer(_recipient, amount);
|
||||
|
||||
return res;
|
||||
return token.transfer(_recipient, amount);
|
||||
}
|
||||
|
||||
function onFailedMessage(address, uint256, bytes32) internal {
|
||||
revert();
|
||||
}
|
||||
|
||||
function tokenBalance(ERC20 _token) internal view returns (uint256) {
|
||||
return _token.balanceOf(address(this));
|
||||
}
|
||||
|
||||
function relayTokens(address _receiver, uint256 _amount) external {
|
||||
require(_receiver != bridgeContractOnOtherSide());
|
||||
require(_receiver != address(0));
|
||||
|
@ -94,9 +107,5 @@ contract ForeignBridgeErcToNative is BasicForeignBridge, ERC20Bridge, OtherSideB
|
|||
|
||||
erc20token().transferFrom(msg.sender, address(this), _amount);
|
||||
emit UserRequestForAffirmation(_receiver, _amount);
|
||||
|
||||
if (isDaiNeedsToBeInvested()) {
|
||||
convertDaiToChai();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,277 @@
|
|||
pragma solidity 0.4.24;
|
||||
|
||||
import "../Ownable.sol";
|
||||
import "../ERC20Bridge.sol";
|
||||
import "../TokenSwapper.sol";
|
||||
import "../../interfaces/IInterestReceiver.sol";
|
||||
|
||||
/**
|
||||
* @title InterestConnector
|
||||
* @dev This contract gives an abstract way of receiving interest on locked tokens.
|
||||
*/
|
||||
contract InterestConnector is Ownable, ERC20Bridge, TokenSwapper {
|
||||
event PaidInterest(address indexed token, address to, uint256 value);
|
||||
|
||||
/**
|
||||
* @dev Throws if interest is bearing not enabled.
|
||||
* @param token address, for which interest should be enabled.
|
||||
*/
|
||||
modifier interestEnabled(address token) {
|
||||
require(isInterestEnabled(token));
|
||||
/* solcov ignore next */
|
||||
_;
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Tells if interest earning was enabled for particular token.
|
||||
* @return true, if interest bearing is enabled.
|
||||
*/
|
||||
function isInterestEnabled(address _token) public view returns (bool) {
|
||||
return boolStorage[keccak256(abi.encodePacked("interestEnabled", _token))];
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Initializes interest receiving functionality.
|
||||
* Only owner can call this method.
|
||||
* @param _token address of the token for interest earning.
|
||||
* @param _minCashThreshold minimum amount of underlying tokens that are not invested.
|
||||
* @param _minInterestPaid minimum amount of interest that can be paid in a single call.
|
||||
*/
|
||||
function initializeInterest(
|
||||
address _token,
|
||||
uint256 _minCashThreshold,
|
||||
uint256 _minInterestPaid,
|
||||
address _interestReceiver
|
||||
) external onlyOwner {
|
||||
require(_isInterestSupported(_token));
|
||||
require(!isInterestEnabled(_token));
|
||||
|
||||
_setInterestEnabled(_token, true);
|
||||
_setMinCashThreshold(_token, _minCashThreshold);
|
||||
_setMinInterestPaid(_token, _minInterestPaid);
|
||||
_setInterestReceiver(_token, _interestReceiver);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Sets minimum amount of tokens that cannot be invested.
|
||||
* Only owner can call this method.
|
||||
* @param _token address of the token contract.
|
||||
* @param _minCashThreshold minimum amount of underlying tokens that are not invested.
|
||||
*/
|
||||
function setMinCashThreshold(address _token, uint256 _minCashThreshold) external onlyOwner {
|
||||
_setMinCashThreshold(_token, _minCashThreshold);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Tells minimum amount of tokens that are not being invested.
|
||||
* @param _token address of the invested token contract.
|
||||
* @return amount of tokens.
|
||||
*/
|
||||
function minCashThreshold(address _token) public view returns (uint256) {
|
||||
return uintStorage[keccak256(abi.encodePacked("minCashThreshold", _token))];
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Sets lower limit for the paid interest amount.
|
||||
* Only owner can call this method.
|
||||
* @param _token address of the token contract.
|
||||
* @param _minInterestPaid minimum amount of interest paid in a single call.
|
||||
*/
|
||||
function setMinInterestPaid(address _token, uint256 _minInterestPaid) external onlyOwner {
|
||||
_setMinInterestPaid(_token, _minInterestPaid);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Tells minimum amount of paid interest in a single call.
|
||||
* @param _token address of the invested token contract.
|
||||
* @return paid interest minimum limit.
|
||||
*/
|
||||
function minInterestPaid(address _token) public view returns (uint256) {
|
||||
return uintStorage[keccak256(abi.encodePacked("minInterestPaid", _token))];
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Internal function that disables interest for locked funds.
|
||||
* Only owner can call this method.
|
||||
* @param _token of token to disable interest for.
|
||||
*/
|
||||
function disableInterest(address _token) external onlyOwner {
|
||||
_withdraw(_token, uint256(-1));
|
||||
_setInterestEnabled(_token, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Tells configured address of the interest receiver.
|
||||
* @param _token address of the invested token contract.
|
||||
* @return address of the interest receiver.
|
||||
*/
|
||||
function interestReceiver(address _token) public view returns (address) {
|
||||
return addressStorage[keccak256(abi.encodePacked("interestReceiver", _token))];
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the interest receiver address.
|
||||
* Only owner can call this method.
|
||||
* @param _token address of the invested token contract.
|
||||
* @param _receiver new receiver address.
|
||||
*/
|
||||
function setInterestReceiver(address _token, address _receiver) external onlyOwner {
|
||||
_setInterestReceiver(_token, _receiver);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Pays collected interest for the specific underlying token.
|
||||
* Requires interest for the given token to be enabled.
|
||||
* @param _token address of the token contract.
|
||||
*/
|
||||
function payInterest(address _token) external interestEnabled(_token) {
|
||||
uint256 interest = interestAmount(_token);
|
||||
require(interest >= minInterestPaid(_token));
|
||||
|
||||
uint256 redeemed = _safeWithdrawTokens(_token, interest);
|
||||
_transferInterest(_token, redeemed);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Tells the amount of underlying tokens that are currently invested.
|
||||
* @param _token address of the token contract.
|
||||
* @return amount of underlying tokens.
|
||||
*/
|
||||
function investedAmount(address _token) public view returns (uint256) {
|
||||
return uintStorage[keccak256(abi.encodePacked("investedAmount", _token))];
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Invests all excess tokens.
|
||||
* Requires interest for the given token to be enabled.
|
||||
* @param _token address of the token contract considered.
|
||||
*/
|
||||
function invest(address _token) public interestEnabled(_token) {
|
||||
uint256 balance = _selfBalance(_token);
|
||||
uint256 minCash = minCashThreshold(_token);
|
||||
|
||||
require(balance > minCash);
|
||||
uint256 amount = balance - minCash;
|
||||
|
||||
_setInvestedAmount(_token, investedAmount(_token).add(amount));
|
||||
|
||||
_invest(_token, amount);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Internal function for transferring interest.
|
||||
* Calls a callback on the receiver, if it is a contract.
|
||||
* @param _token address of the underlying token contract.
|
||||
* @param _amount amount of collected tokens that should be sent.
|
||||
*/
|
||||
function _transferInterest(address _token, uint256 _amount) internal {
|
||||
address receiver = interestReceiver(_token);
|
||||
require(receiver != address(0));
|
||||
|
||||
ERC20(_token).transfer(receiver, _amount);
|
||||
|
||||
if (AddressUtils.isContract(receiver)) {
|
||||
IInterestReceiver(receiver).onInterestReceived(_token);
|
||||
}
|
||||
|
||||
emit PaidInterest(_token, receiver, _amount);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Internal function for setting interest enabled flag for some token.
|
||||
* @param _token address of the token contract.
|
||||
* @param _enabled true to enable interest earning, false to disable.
|
||||
*/
|
||||
function _setInterestEnabled(address _token, bool _enabled) internal {
|
||||
boolStorage[keccak256(abi.encodePacked("interestEnabled", _token))] = _enabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Internal function for setting the amount of underlying tokens that are currently invested.
|
||||
* @param _token address of the token contract.
|
||||
* @param _amount new amount of invested tokens.
|
||||
*/
|
||||
function _setInvestedAmount(address _token, uint256 _amount) internal {
|
||||
uintStorage[keccak256(abi.encodePacked("investedAmount", _token))] = _amount;
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Internal function for withdrawing some amount of the invested tokens.
|
||||
* Reverts if given amount cannot be withdrawn.
|
||||
* @param _token address of the token contract withdrawn.
|
||||
* @param _amount amount of requested tokens to be withdrawn.
|
||||
*/
|
||||
function _withdraw(address _token, uint256 _amount) internal {
|
||||
if (_amount == 0) return;
|
||||
|
||||
uint256 invested = investedAmount(_token);
|
||||
uint256 withdrawal = _amount > invested ? invested : _amount;
|
||||
uint256 redeemed = _safeWithdrawTokens(_token, withdrawal);
|
||||
|
||||
_setInvestedAmount(_token, invested > redeemed ? invested - redeemed : 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Internal function for safe withdrawal of invested tokens.
|
||||
* Reverts if given amount cannot be withdrawn.
|
||||
* Additionally verifies that at least _amount of tokens were withdrawn.
|
||||
* @param _token address of the token contract withdrawn.
|
||||
* @param _amount amount of requested tokens to be withdrawn.
|
||||
*/
|
||||
function _safeWithdrawTokens(address _token, uint256 _amount) private returns (uint256) {
|
||||
uint256 balance = _selfBalance(_token);
|
||||
|
||||
_withdrawTokens(_token, _amount);
|
||||
|
||||
uint256 redeemed = _selfBalance(_token) - balance;
|
||||
|
||||
require(redeemed >= _amount);
|
||||
|
||||
return redeemed;
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Internal function for setting minimum amount of tokens that cannot be invested.
|
||||
* @param _token address of the token contract.
|
||||
* @param _minCashThreshold minimum amount of underlying tokens that are not invested.
|
||||
*/
|
||||
function _setMinCashThreshold(address _token, uint256 _minCashThreshold) internal {
|
||||
uintStorage[keccak256(abi.encodePacked("minCashThreshold", _token))] = _minCashThreshold;
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Internal function for setting lower limit for paid interest amount.
|
||||
* @param _token address of the token contract.
|
||||
* @param _minInterestPaid minimum amount of interest paid in a single call.
|
||||
*/
|
||||
function _setMinInterestPaid(address _token, uint256 _minInterestPaid) internal {
|
||||
uintStorage[keccak256(abi.encodePacked("minInterestPaid", _token))] = _minInterestPaid;
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Internal function for setting interest receiver address.
|
||||
* @param _token address of the invested token contract.
|
||||
* @param _receiver address of the interest receiver.
|
||||
*/
|
||||
function _setInterestReceiver(address _token, address _receiver) internal {
|
||||
require(_receiver != address(this));
|
||||
addressStorage[keccak256(abi.encodePacked("interestReceiver", _token))] = _receiver;
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Tells this contract balance of some specific token contract
|
||||
* @param _token address of the token contract.
|
||||
* @return contract balance.
|
||||
*/
|
||||
function _selfBalance(address _token) internal view returns (uint256) {
|
||||
return ERC20(_token).balanceOf(address(this));
|
||||
}
|
||||
|
||||
function _isInterestSupported(address _token) internal pure returns (bool);
|
||||
|
||||
function _invest(address _token, uint256 _amount) internal;
|
||||
|
||||
function _withdrawTokens(address _token, uint256 _amount) internal;
|
||||
|
||||
function interestAmount(address _token) public view returns (uint256);
|
||||
}
|
|
@ -123,7 +123,6 @@ contract BasicMultiTokenBridge is EternalStorage, Ownable {
|
|||
* @return day number.
|
||||
*/
|
||||
function getCurrentDay() public view returns (uint256) {
|
||||
// solhint-disable-next-line not-rely-on-time
|
||||
return now / 1 days;
|
||||
}
|
||||
|
||||
|
|
File diff suppressed because it is too large
Load Diff
11
package.json
11
package.json
|
@ -4,7 +4,7 @@
|
|||
"description": "Bridge",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"test": "scripts/test.sh",
|
||||
"test": "test/test.sh",
|
||||
"test:gasreport": "GASREPORT=true npm run test",
|
||||
"test:gasreport:ci": "npm run test:gasreport && npx codechecks",
|
||||
"compile": "truffle compile",
|
||||
|
@ -15,17 +15,17 @@
|
|||
"lint:sol": "solhint --max-warnings 0 \"contracts/**/*.sol\"",
|
||||
"lint:sol:prettier:fix": "prettier --write \"contracts/**/*.sol\"",
|
||||
"watch-tests": "./node_modules/.bin/nodemon ./node_modules/.bin/truffle test --network test",
|
||||
"coverage": "SOLIDITY_COVERAGE=true scripts/test.sh"
|
||||
"coverage": "SOLIDITY_COVERAGE=true npm run test"
|
||||
},
|
||||
"author": "POA network",
|
||||
"license": "GPLv3",
|
||||
"dependencies": {
|
||||
"@0x/sol-coverage": "^4.0.10",
|
||||
"@0x/sol-trace": "^3.0.10",
|
||||
"@0x/subproviders": "^6.1.1",
|
||||
"openzeppelin-solidity": "1.12.0",
|
||||
"truffle": "^5.0.35",
|
||||
"truffle-flattener": "^1.4.2"
|
||||
"truffle": "^5.3.2",
|
||||
"truffle-flattener": "^1.4.2",
|
||||
"web3-provider-engine": "^14.0.6"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@codechecks/client": "^0.1.9",
|
||||
|
@ -41,7 +41,6 @@
|
|||
"eth-gas-reporter": "^0.2.11",
|
||||
"ethereumjs-abi": "0.6.8",
|
||||
"ethereumjs-util": "5.2.0",
|
||||
"ganache-cli": "^6.6.0",
|
||||
"istanbul": "^0.4.5",
|
||||
"nodemon": "^1.17.3",
|
||||
"prettier": "^1.18.2",
|
||||
|
|
|
@ -1,62 +0,0 @@
|
|||
#!/usr/bin/env bash
|
||||
|
||||
# Exit script as soon as a command fails.
|
||||
set -o errexit
|
||||
node_modules/.bin/truffle version
|
||||
# Executes cleanup function at script exit.
|
||||
trap cleanup EXIT
|
||||
|
||||
cleanup() {
|
||||
# Kill the ganache instance that we started (if we started one and if it's still running).
|
||||
if [ -n "$ganache_pid" ] && ps -p $ganache_pid > /dev/null; then
|
||||
kill -9 $ganache_pid
|
||||
fi
|
||||
}
|
||||
|
||||
if [ "$SOLIDITY_COVERAGE" = true ]; then
|
||||
ganache_port=8555
|
||||
else
|
||||
ganache_port=8545
|
||||
fi
|
||||
|
||||
ganache_running() {
|
||||
nc -z localhost "$ganache_port"
|
||||
}
|
||||
|
||||
start_ganache() {
|
||||
# We define 10 accounts with balance 1M ether, needed for high-value tests.
|
||||
local accounts=(
|
||||
--account="0x2bdd21761a483f71054e14f5b827213567971c676928d9a1808cbfa4b7501200,1000000000000000000000000"
|
||||
--account="0x2bdd21761a483f71054e14f5b827213567971c676928d9a1808cbfa4b7501201,1000000000000000000000000"
|
||||
--account="0x2bdd21761a483f71054e14f5b827213567971c676928d9a1808cbfa4b7501202,1000000000000000000000000"
|
||||
--account="0x2bdd21761a483f71054e14f5b827213567971c676928d9a1808cbfa4b7501203,1000000000000000000000000"
|
||||
--account="0x2bdd21761a483f71054e14f5b827213567971c676928d9a1808cbfa4b7501204,1000000000000000000000000"
|
||||
--account="0x2bdd21761a483f71054e14f5b827213567971c676928d9a1808cbfa4b7501205,1000000000000000000000000"
|
||||
--account="0x2bdd21761a483f71054e14f5b827213567971c676928d9a1808cbfa4b7501206,1000000000000000000000000"
|
||||
--account="0x2bdd21761a483f71054e14f5b827213567971c676928d9a1808cbfa4b7501207,1000000000000000000000000"
|
||||
--account="0x2bdd21761a483f71054e14f5b827213567971c676928d9a1808cbfa4b7501208,1000000000000000000000000"
|
||||
--account="0x2bdd21761a483f71054e14f5b827213567971c676928d9a1808cbfa4b7501209,1000000000000000000000000"
|
||||
--account="0x19fba401d77e4113b15095e9aa7117bcd25adcfac7f6111f8298894eef443600,1000000000000000000000000"
|
||||
)
|
||||
|
||||
if [ "$SOLIDITY_COVERAGE" != true ]; then
|
||||
node_modules/.bin/ganache-cli --gasLimit 0xfffffffffff "${accounts[@]}" > /dev/null &
|
||||
fi
|
||||
|
||||
ganache_pid=$!
|
||||
}
|
||||
|
||||
if [ "$SOLIDITY_COVERAGE" != true ]; then
|
||||
if ganache_running; then
|
||||
echo "Using existing ganache instance"
|
||||
else
|
||||
echo "Starting our own ganache instance"
|
||||
start_ganache
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ "$SOLIDITY_COVERAGE" = true ]; then
|
||||
node --max-old-space-size=4096 node_modules/.bin/truffle test 2>/dev/null; istanbul report lcov
|
||||
else
|
||||
node_modules/.bin/truffle test --network ganache "$@"
|
||||
fi
|
|
@ -0,0 +1,22 @@
|
|||
FROM node:13
|
||||
|
||||
RUN wget https://github.com/ethereum/solidity/releases/download/v0.5.16/solc-static-linux -O /usr/local/bin/solc
|
||||
RUN chmod +x /usr/local/bin/solc
|
||||
|
||||
RUN git clone https://github.com/compound-finance/compound-protocol.git
|
||||
|
||||
WORKDIR /compound-protocol
|
||||
|
||||
RUN yarn
|
||||
RUN cd scenario && yarn
|
||||
|
||||
RUN yarn compile
|
||||
RUN scenario/script/tsc
|
||||
|
||||
COPY entrypoint.scen ./
|
||||
|
||||
ENV PROVIDER='http://ganache:8545'
|
||||
ENV NO_TSC=1
|
||||
|
||||
ENTRYPOINT ["yarn", "repl"]
|
||||
CMD ["-s", "entrypoint.scen", "c", "t"]
|
|
@ -0,0 +1,13 @@
|
|||
const Comptroller = artifacts.require('IHarnessComptroller')
|
||||
const ERC20Mock = artifacts.require('ERC20Mock')
|
||||
const ICToken = artifacts.require('ICToken')
|
||||
|
||||
async function getCompoundContracts() {
|
||||
const comptroller = await Comptroller.at('0x85e855b22F01BdD33eE194490c7eB16b7EdaC019')
|
||||
const dai = await ERC20Mock.at('0x0a4dBaF9656Fd88A32D087101Ee8bf399f4bd55f')
|
||||
const cDai = await ICToken.at('0x615cba17EE82De39162BB87dBA9BcfD6E8BcF298')
|
||||
const comp = await ERC20Mock.at('0x6f51036Ec66B08cBFdb7Bd7Fb7F40b184482d724')
|
||||
return { comptroller, dai, cDai, comp }
|
||||
}
|
||||
|
||||
module.exports = getCompoundContracts
|
|
@ -0,0 +1,33 @@
|
|||
MyAddress 0x3E5e9111Ae8eB78Fe1CC3bb8915d5D461F3Ef9A9
|
||||
|
||||
-- Deploy contract
|
||||
NewComptroller
|
||||
ListedEtherToken cETH
|
||||
ListedCToken DAI cDAI
|
||||
Erc20 Deploy Standard COMP "COMP Token" 18
|
||||
|
||||
-- Set contracts params
|
||||
Comptroller SetCollateralFactor cETH 0.9
|
||||
Comptroller SetCompSpeed cDAI 1
|
||||
Comptroller Send "setCompAddress(address)" (Address COMP)
|
||||
Give (Address Comptroller) 5000000e18 COMP
|
||||
|
||||
-- Put ETH collateral
|
||||
SendMintEth Me 20e18 cETH
|
||||
EnterMarkets Me cETH
|
||||
|
||||
-- Mint some DAI
|
||||
Give Me 500000e18 DAI
|
||||
|
||||
-- Mint some cDAI
|
||||
Allow Me cDAI
|
||||
Mint Me 10e18 cDAI
|
||||
|
||||
-- Print addresses
|
||||
Read Comptroller Address
|
||||
Read ERC20 DAI Address
|
||||
Read ERC20 Comp Address
|
||||
Read CToken cETH Address
|
||||
Read CToken cDAI Address
|
||||
|
||||
Exit
|
|
@ -0,0 +1,12 @@
|
|||
version: '3.8'
|
||||
services:
|
||||
ganache:
|
||||
image: trufflesuite/ganache-cli
|
||||
command: --deterministic --gasLimit 20000000 --allowUnlimitedContractSize
|
||||
ports:
|
||||
- 8545:8545
|
||||
compound:
|
||||
image: kirillfedoseev/compound-test-deploy
|
||||
# build: compound
|
||||
stdin_open: true
|
||||
tty: true
|
File diff suppressed because it is too large
Load Diff
|
@ -13,7 +13,7 @@ const PermittableTokenMock = artifacts.require('PermittableTokenMock.sol')
|
|||
const { expect } = require('chai')
|
||||
const ethUtil = require('ethereumjs-util')
|
||||
|
||||
const { ERROR_MSG, ERROR_MSG_OPCODE, ZERO_ADDRESS, BN } = require('./setup')
|
||||
const { ERROR_MSG, ERROR_MSG_OPCODE, ZERO_ADDRESS, BN, toBN } = require('./setup')
|
||||
const { ether, expectEventInLogs } = require('./helpers/helpers')
|
||||
const permitSign = require('./helpers/eip712.sign.permit')
|
||||
|
||||
|
@ -774,7 +774,7 @@ function testERC677BridgeToken(accounts, rewardable, permittable, createToken) {
|
|||
await token.permit(holder, spender, nonce, expiry, allowed, signature.v, signature.r, signature.s).should.be
|
||||
.fulfilled
|
||||
;(await token.expirations.call(holder, spender)).should.be.bignumber.equal(new BN(expiry))
|
||||
const data = await token.contract.methods.approve(spender, -1).encodeABI()
|
||||
const data = await token.contract.methods.approve(spender, infinite).encodeABI()
|
||||
await web3.eth.sendTransaction({ from: holder, to: token.address, data, gas: 100000 }).should.be.fulfilled
|
||||
;(await token.expirations.call(holder, spender)).should.be.bignumber.equal(ZERO)
|
||||
})
|
||||
|
@ -800,7 +800,7 @@ function testERC677BridgeToken(accounts, rewardable, permittable, createToken) {
|
|||
await token.permit(holder, spender, nonce, expiry, allowed, signature.v, signature.r, signature.s).should.be
|
||||
.fulfilled
|
||||
;(await token.expirations.call(holder, spender)).should.be.bignumber.equal(new BN(expiry))
|
||||
let data = await token.contract.methods.approve(spender, -2).encodeABI()
|
||||
let data = await token.contract.methods.approve(spender, infinite.sub(toBN(1))).encodeABI()
|
||||
await web3.eth.sendTransaction({ from: holder, to: token.address, data, gas: 100000 }).should.be.fulfilled
|
||||
;(await token.expirations.call(holder, spender)).should.be.bignumber.equal(new BN(expiry))
|
||||
data = await token.contract.methods.increaseAllowance(spender, 1).encodeABI()
|
||||
|
|
|
@ -0,0 +1,32 @@
|
|||
#!/usr/bin/env bash
|
||||
|
||||
set -e
|
||||
|
||||
trap cleanup EXIT
|
||||
|
||||
cleanup() {
|
||||
if [ "$KEEP_RUNNING" != true ]; then
|
||||
docker-compose -f test/docker-compose.yml down
|
||||
fi
|
||||
}
|
||||
cleanup
|
||||
|
||||
ganache_running() {
|
||||
nc -z localhost 8545
|
||||
}
|
||||
|
||||
if ganache_running; then
|
||||
echo "Using existing ganache instance"
|
||||
else
|
||||
echo "Starting our own ganache instance"
|
||||
docker-compose -f test/docker-compose.yml up -d ganache
|
||||
sleep 5
|
||||
echo "Deploy Compound protocol contracts"
|
||||
docker-compose -f test/docker-compose.yml up compound || true
|
||||
fi
|
||||
|
||||
if [ "$SOLIDITY_COVERAGE" = true ]; then
|
||||
node --max-old-space-size=4096 node_modules/.bin/truffle test 2>/dev/null; istanbul report lcov
|
||||
else
|
||||
node_modules/.bin/truffle test --network ganache "$@"
|
||||
fi
|
|
@ -1,6 +1,6 @@
|
|||
const { CoverageSubprovider, Web3ProviderEngine } = require('@0x/sol-coverage')
|
||||
const { TruffleArtifactAdapter } = require('@0x/sol-trace')
|
||||
const { GanacheSubprovider } = require('@0x/subproviders')
|
||||
const RPCSubprovider = require('web3-provider-engine/subproviders/rpc')
|
||||
|
||||
const contractsBuildDirectory = './build/contracts'
|
||||
const evmVersion = 'byzantium'
|
||||
|
@ -27,57 +27,7 @@ if (process.env.SOLIDITY_COVERAGE === 'true') {
|
|||
ignoreFilesGlobs: ['**/Migrations.sol', '**/node_modules/**', '**/mocks/**', '**/interfaces/**', '**/helpers/**']
|
||||
})
|
||||
provider.addProvider(global.coverageSubprovider)
|
||||
const ganacheSubprovider = new GanacheSubprovider({
|
||||
accounts: [
|
||||
{
|
||||
secretKey: '0x2bdd21761a483f71054e14f5b827213567971c676928d9a1808cbfa4b7501200',
|
||||
balance: '1000000000000000000000000'
|
||||
},
|
||||
{
|
||||
secretKey: '0x2bdd21761a483f71054e14f5b827213567971c676928d9a1808cbfa4b7501201',
|
||||
balance: '1000000000000000000000000'
|
||||
},
|
||||
{
|
||||
secretKey: '0x2bdd21761a483f71054e14f5b827213567971c676928d9a1808cbfa4b7501202',
|
||||
balance: '1000000000000000000000000'
|
||||
},
|
||||
{
|
||||
secretKey: '0x2bdd21761a483f71054e14f5b827213567971c676928d9a1808cbfa4b7501203',
|
||||
balance: '1000000000000000000000000'
|
||||
},
|
||||
{
|
||||
secretKey: '0x2bdd21761a483f71054e14f5b827213567971c676928d9a1808cbfa4b7501204',
|
||||
balance: '1000000000000000000000000'
|
||||
},
|
||||
{
|
||||
secretKey: '0x2bdd21761a483f71054e14f5b827213567971c676928d9a1808cbfa4b7501205',
|
||||
balance: '1000000000000000000000000'
|
||||
},
|
||||
{
|
||||
secretKey: '0x2bdd21761a483f71054e14f5b827213567971c676928d9a1808cbfa4b7501206',
|
||||
balance: '1000000000000000000000000'
|
||||
},
|
||||
{
|
||||
secretKey: '0x2bdd21761a483f71054e14f5b827213567971c676928d9a1808cbfa4b7501207',
|
||||
balance: '1000000000000000000000000'
|
||||
},
|
||||
{
|
||||
secretKey: '0x2bdd21761a483f71054e14f5b827213567971c676928d9a1808cbfa4b7501208',
|
||||
balance: '1000000000000000000000000'
|
||||
},
|
||||
{
|
||||
secretKey: '0x2bdd21761a483f71054e14f5b827213567971c676928d9a1808cbfa4b7501209',
|
||||
balance: '1000000000000000000000000'
|
||||
},
|
||||
{
|
||||
secretKey: '0x19fba401d77e4113b15095e9aa7117bcd25adcfac7f6111f8298894eef443600',
|
||||
balance: '1000000000000000000000000'
|
||||
}
|
||||
],
|
||||
port: 8545,
|
||||
gasLimit: 10000000
|
||||
})
|
||||
provider.addProvider(ganacheSubprovider)
|
||||
provider.addProvider(new RPCSubprovider({ rpcUrl: 'http://localhost:8545' }))
|
||||
provider.start(err => {
|
||||
if (err !== undefined) {
|
||||
process.exit(1)
|
||||
|
@ -92,14 +42,16 @@ module.exports = {
|
|||
development: {
|
||||
provider,
|
||||
network_id: '*',
|
||||
gas: 10000000
|
||||
gas: 10000000,
|
||||
disableConfirmationListener: true
|
||||
},
|
||||
ganache: {
|
||||
host: '127.0.0.1',
|
||||
port: 8545,
|
||||
network_id: '*', // eslint-disable-line camelcase
|
||||
gasPrice: 100000000000,
|
||||
gas: 10000000
|
||||
gas: 10000000,
|
||||
disableConfirmationListener: true
|
||||
}
|
||||
},
|
||||
compilers: {
|
||||
|
|
Loading…
Reference in New Issue