Skip to content

Instantly share code, notes, and snippets.

@k06a
Last active June 16, 2020 01:13
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save k06a/74dfeb70a974efe1ee9c1a4e88f6adf8 to your computer and use it in GitHub Desktop.
Save k06a/74dfeb70a974efe1ee9c1a4e88f6adf8 to your computer and use it in GitHub Desktop.
rDAI 2.0 Concept
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