Earn interest on locked tokens using Compound Protocol (#590)

This commit is contained in:
Kirill Fedoseev 2021-04-23 20:26:41 +03:00 committed by GitHub
parent c9377114f7
commit e7f7fae726
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
42 changed files with 49447 additions and 10408 deletions

View File

@ -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"]
}
}

View File

@ -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);
}

View File

@ -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;
}

View File

@ -0,0 +1,5 @@
pragma solidity 0.4.24;
interface IComptroller {
function claimComp(address[] holders, address[] cTokens, bool borrowers, bool suppliers) external;
}

View File

@ -0,0 +1,5 @@
pragma solidity 0.4.24;
interface IInterestReceiver {
function onInterestReceived(address _token) external;
}

View File

@ -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);
}

View File

@ -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);
}

View File

@ -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));
}
}

View File

@ -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;
}
}

View File

@ -1,9 +0,0 @@
pragma solidity 0.4.24;
contract DaiAdapterMock {
address public dai;
constructor(address _dai) public {
dai = _dai;
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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 {

View File

@ -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);
}
}

View File

@ -0,0 +1,5 @@
pragma solidity 0.4.24;
interface IHarnessComptroller {
function fastForward(uint256 blocks) external;
}

View File

@ -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);
}
}

View File

@ -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));
}
}

View File

@ -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;
}
}

View File

@ -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);
}
}

View File

@ -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;
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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 {}
}

View File

@ -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);

View File

@ -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.

View File

@ -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);
}
}

View File

@ -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();
}
}
}

View File

@ -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);
}

View File

@ -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;
}

56507
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -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",

View File

@ -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

22
test/compound/Dockerfile Normal file
View File

@ -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"]

View File

@ -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

View File

@ -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

12
test/docker-compose.yml Normal file
View File

@ -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

View File

@ -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()

32
test/test.sh Executable file
View File

@ -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

View File

@ -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: {