Last active
June 16, 2020 01:13
-
-
Save k06a/74dfeb70a974efe1ee9c1a4e88f6adf8 to your computer and use it in GitHub Desktop.
rDAI 2.0 Concept
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
pragma solidity ^0.5.0; | |
import "github.com/openzeppelin/openzeppelin-contracts/contracts/token/ERC20/ERC20.sol"; | |
import "github.com/openzeppelin/openzeppelin-contracts/contracts/token/ERC20/SafeERC20.sol"; | |
import "github.com/openzeppelin/openzeppelin-contracts/contracts/token/ERC20/ERC20Mintable.sol"; | |
import "github.com/openzeppelin/openzeppelin-contracts/contracts/token/ERC20/ERC20Detailed.sol"; | |
contract IFulcrum is IERC20 { | |
function tokenPrice() external view returns (uint256 price); | |
function mint(address receiver, uint256 amount) external returns (uint256 minted); | |
function burn(address receiver, uint256 amount) external returns (uint256 burned); | |
} | |
library Interest { | |
struct Data { | |
int256 startPrice; | |
} | |
function earnedInterest(Data storage self, uint256 balance, uint256 currentPrice) internal view returns(uint256) { | |
if (self.startPrice == 0) { | |
return 0; | |
} | |
if (balance == 0) { | |
return uint256(self.startPrice); | |
} | |
return balance * uint256(int256(currentPrice) - self.startPrice) / 1e18; | |
} | |
function setEarnedInterest(Data storage self, uint256 earned, uint256 balance, uint256 currentPrice) internal { | |
if (balance == 0) { | |
self.startPrice = int256(earned); | |
return; | |
} | |
self.startPrice = int256(currentPrice) - int256(earned * 1e18 / balance); | |
} | |
} | |
contract rDAI_ERC20 is ERC20 { | |
using SafeERC20 for IERC20; | |
using Interest for Interest.Data; | |
mapping (address => Interest.Data) internal _interests; | |
IERC20 public dai = IERC20(0x89d24A6b4CcB1B6fAA2625fE562bDD9a23260359); | |
IFulcrum public fulcrum = IFulcrum(0x14094949152EDDBFcd073717200DA82fEd8dC960); | |
modifier keepEarnedInterest(address account) { | |
uint256 currentPrice = fulcrum.tokenPrice(); | |
uint256 earned = _interests[account].earnedInterest(balanceOf(account), currentPrice); | |
_; | |
_restoreAccountInterest(account, earned, currentPrice); | |
} | |
constructor(IERC20 _dai, IFulcrum _fulcrum) public { | |
dai = _dai; | |
fulcrum = _fulcrum; | |
} | |
// View methods | |
function interestOf(address account) public view returns (uint256) { | |
uint256 currentPrice = fulcrum.tokenPrice(); | |
return _interests[account].earnedInterest(balanceOf(account), currentPrice); | |
} | |
// Non-view methods | |
function mint(uint256 mintAmount) public keepEarnedInterest(msg.sender) { | |
dai.safeTransferFrom(msg.sender, address(this), mintAmount); | |
_depositToLendingPool(mintAmount); | |
_mint(msg.sender, mintAmount); | |
} | |
function redeem(uint256 redeemTokens) public keepEarnedInterest(msg.sender) { | |
_burn(msg.sender, redeemTokens); | |
_redeemFromLendingPool(redeemTokens); | |
dai.safeTransfer(msg.sender, redeemTokens); | |
} | |
function redeemEarnedInterest() public { | |
uint256 currentPrice = fulcrum.tokenPrice(); | |
uint256 earned = _interests[msg.sender].earnedInterest(balanceOf(msg.sender), currentPrice); | |
_mint(msg.sender, earned); | |
_interests[msg.sender].setEarnedInterest(0, balanceOf(msg.sender), currentPrice); | |
} | |
// Overrided methods | |
function _transfer(address sender, address recipient, uint256 amount) | |
internal | |
keepEarnedInterest(sender) | |
keepEarnedInterest(recipient) | |
{ | |
super._transfer(sender, recipient, amount); | |
} | |
// Internal methods | |
function _restoreAccountInterest(address account, uint256 earned, uint256 currentPrice) internal { | |
_interests[account].setEarnedInterest(earned, balanceOf(account), currentPrice); | |
} | |
function _depositToLendingPool(uint256 amount) internal { | |
// ... | |
} | |
function _redeemFromLendingPool(uint256 amount) internal { | |
// ... | |
} | |
} | |
library Hat { | |
using SafeMath for uint256; | |
struct Recepint { | |
address account; | |
uint96 proportion; | |
uint256 lastFetchedAccumulatedBalance; | |
} | |
struct Data { | |
uint256 accumulatedBalance; | |
uint256 proportionsSum; | |
Recepint[] recipients; | |
} | |
function earnedBalanceForAccount(Data storage self, address account, uint256 index) internal view returns(uint256) { | |
if (self.recipients[index].account == account) { | |
return self.accumulatedBalance | |
.mul(self.recipients[index].proportion) | |
.div(self.proportionsSum) | |
.sub(self.recipients[index].lastFetchedAccumulatedBalance); | |
} | |
} | |
function popupBalance(Data storage self, uint256 amount) internal { | |
self.accumulatedBalance = self.accumulatedBalance.add(amount); | |
} | |
function grabEarnedBalanceForAccount(Data storage self, address account, uint256 index) internal returns(uint256) { | |
uint256 earned = earnedBalanceForAccount(self, account, index); | |
if (earned > 0) { | |
self.recipients[index].lastFetchedAccumulatedBalance = self.accumulatedBalance; | |
} | |
return earned; | |
} | |
} | |
contract rDAI is rDAI_ERC20, ERC20Detailed { | |
using Hat for Hat.Data; | |
Hat.Data[] private _hats; | |
mapping(address => uint256) private _hatId; | |
event HatCreated(address indexed user, uint256 indexed hat); | |
event HatChanged(address indexed user, uint256 indexed oldHat, uint256 indexed newHat); | |
event HatIncludes(uint256 indexed hat, address indexed account, uint256 proportion, uint256 proportionsSum); | |
constructor(IERC20 _dai, IFulcrum _fulcrum) | |
public | |
rDAI_ERC20(_dai, _fulcrum) | |
ERC20Detailed("Redeemable DAI", "rDAI", 18) | |
{ | |
// HatId = 0 - non-existing Hat | |
_hats.length++; | |
} | |
// View methods | |
function hats(uint256 hatId) | |
public | |
view | |
returns( | |
uint256 accumulatedBalance, | |
address[] memory accounts, | |
uint256[] memory proportions, | |
uint256[] memory lastFetchedAccumulatedBalances | |
) | |
{ | |
Hat.Data storage hat = _hats[hatId]; | |
accounts = new address[](hat.recipients.length); | |
proportions = new uint256[](hat.recipients.length); | |
lastFetchedAccumulatedBalances = new uint256[](hat.recipients.length); | |
accumulatedBalance = hat.accumulatedBalance; | |
for (uint i = 0; i < hat.recipients.length; i++) { | |
accounts[i] = hat.recipients[i].account; | |
proportions[i] = hat.recipients[i].proportion; | |
lastFetchedAccumulatedBalances[i] = hat.recipients[i].lastFetchedAccumulatedBalance; | |
} | |
} | |
function hatId(address user) public view returns(uint256) { | |
return _hatId[user]; | |
} | |
// Non-view methods | |
function createHat(address[] memory accounts, uint256[] memory proportions) public { | |
require(accounts.length == proportions.length, "rDAI: Arrays lengths should be equal"); | |
uint256 proportionsSum = 0; | |
for (uint i = 0; i < proportions.length; i++) { | |
require(proportions[i] < 2 ** 32, "rDAI: Proportion should be less than 2**32"); | |
proportionsSum = proportionsSum.add(proportions[i]); | |
} | |
uint256 hatIndex = _hats.length++; | |
for (uint i = 0; i < accounts.length; i++) { | |
require(accounts[i] != address(0), "rDAI: Account should not be 0x0"); | |
_hats[hatIndex].recipients.push(Hat.Recepint({ | |
account: accounts[i], | |
proportion: uint96(proportions[i]), | |
lastFetchedAccumulatedBalance: 0 | |
})); | |
emit HatIncludes(hatIndex, accounts[i], proportions[i], proportionsSum); | |
} | |
_hats[hatIndex].proportionsSum = proportionsSum; | |
} | |
function setHatId(uint256 senderHatId) | |
public | |
keepEarnedInterest(msg.sender) | |
{ | |
require(_hatId[msg.sender] != senderHatId, "rDAI: HatId is already the same"); | |
emit HatChanged(msg.sender, _hatId[msg.sender], senderHatId); | |
_hatId[msg.sender] = senderHatId; | |
} | |
function fetchAccountInterestToHat(address[] memory accounts) public { | |
uint256 currentPrice = fulcrum.tokenPrice(); | |
for (uint i = 0; i < accounts.length; i++) { | |
require(_hatId[accounts[i]] != 0, "rDAI: This method could not be used for users with hatId = 0"); | |
uint256 earned = _interests[accounts[i]].earnedInterest(balanceOf(accounts[i]), currentPrice); | |
_restoreAccountInterest(accounts[i], earned, currentPrice); | |
} | |
} | |
function redeemEarnedInterest() public { | |
require(_hatId[msg.sender] == 0, "rDAI: This method could be used for users with hatId = 0"); | |
super.redeemEarnedInterest(); | |
} | |
function redeemHatsInterest(uint256[] memory hatIds, uint256[] memory subindexes) | |
public | |
keepEarnedInterest(msg.sender) | |
returns(uint256 total) | |
{ | |
for (uint256 i = 0; i < hatIds.length; i++) { | |
require(hatIds[i] != 0, "rDAI: Use redeemEarnedInterest() method for HatId = 0"); | |
total = total.add(_hats[hatIds[i]].grabEarnedBalanceForAccount(msg.sender, subindexes[i])); | |
} | |
_mint(msg.sender, total); | |
} | |
// Overrided methods | |
function _restoreAccountInterest(address account, uint256 earned, uint256 currentPrice) internal { | |
if (_hatId[account] != 0) { | |
_interests[account].setEarnedInterest(0, balanceOf(account), currentPrice); | |
_hats[_hatId[account]].popupBalance(earned); | |
} else { | |
_interests[account].setEarnedInterest(earned, balanceOf(account), currentPrice); | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment