-
Notifications
You must be signed in to change notification settings - Fork 250
Expand file tree
/
Copy pathRocketTokenRETH.sol
More file actions
173 lines (153 loc) · 8.33 KB
/
RocketTokenRETH.sol
File metadata and controls
173 lines (153 loc) · 8.33 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
pragma solidity 0.7.6;
// SPDX-License-Identifier: GPL-3.0-only
import "../util/ERC20.sol";
import "../RocketBase.sol";
import "../../interface/deposit/RocketDepositPoolInterface.sol";
import "../../interface/network/RocketNetworkBalancesInterface.sol";
import "../../interface/token/RocketTokenRETHInterface.sol";
import "../../interface/dao/protocol/settings/RocketDAOProtocolSettingsNetworkInterface.sol";
// rETH is a tokenised stake in the Rocket Pool network
// rETH is backed by ETH (subject to liquidity) at a variable exchange rate
contract RocketTokenRETH is RocketBase, ERC20, RocketTokenRETHInterface {
// Libs
using SafeMath for uint;
// Events
event EtherDeposited(address indexed from, uint256 amount, uint256 time);
event TokensMinted(address indexed to, uint256 amount, uint256 ethAmount, uint256 time);
event TokensBurned(address indexed from, uint256 amount, uint256 ethAmount, uint256 time);
// Construct with our token details
constructor(RocketStorageInterface _rocketStorageAddress) RocketBase(_rocketStorageAddress) ERC20("Rocket Pool ETH", "rETH") {
// Version
version = 1;
}
// Receive an ETH deposit from a minipool or generous individual
receive() external payable {
// Emit ether deposited event
emit EtherDeposited(msg.sender, msg.value, block.timestamp);
}
// Calculate the amount of ETH backing an amount of rETH
function getEthValue(uint256 _rethAmount) override public view returns (uint256) {
// Get network balances
RocketNetworkBalancesInterface rocketNetworkBalances = RocketNetworkBalancesInterface(getContractAddress("rocketNetworkBalances"));
uint256 totalEthBalance = rocketNetworkBalances.getTotalETHBalance();
uint256 rethSupply = rocketNetworkBalances.getTotalRETHSupply();
// Use 1:1 ratio if no rETH is minted
if (rethSupply == 0) { return _rethAmount; }
// Calculate and return
return _rethAmount.mul(totalEthBalance).div(rethSupply);
}
// Calculate the amount of rETH backed by an amount of ETH
function getRethValue(uint256 _ethAmount) override public view returns (uint256) {
// Get network balances
RocketNetworkBalancesInterface rocketNetworkBalances = RocketNetworkBalancesInterface(getContractAddress("rocketNetworkBalances"));
uint256 totalEthBalance = rocketNetworkBalances.getTotalETHBalance();
uint256 rethSupply = rocketNetworkBalances.getTotalRETHSupply();
// Use 1:1 ratio if no rETH is minted
if (rethSupply == 0) { return _ethAmount; }
// Check network ETH balance
require(totalEthBalance > 0, "Cannot calculate rETH token amount while total network balance is zero");
// Calculate and return
return _ethAmount.mul(rethSupply).div(totalEthBalance);
}
// Get the current rETH / ETH exchange rate
// Returns the amount of ETH backing 1 rETH
function getExchangeRate() override external view returns (uint256) {
return getEthValue(1 ether);
}
// Get the total amount of collateral available
// Includes rETH contract balance & excess deposit pool balance
function getTotalCollateral() override public view returns (uint256) {
RocketDepositPoolInterface rocketDepositPool = RocketDepositPoolInterface(getContractAddress("rocketDepositPool"));
return rocketDepositPool.getExcessBalance().add(address(this).balance);
}
// Get the current ETH collateral rate
// Returns the portion of rETH backed by ETH in the contract as a fraction of 1 ether
function getCollateralRate() override public view returns (uint256) {
uint256 totalEthValue = getEthValue(totalSupply());
if (totalEthValue == 0) { return calcBase; }
return calcBase.mul(address(this).balance).div(totalEthValue);
}
// Deposit excess ETH from deposit pool
// Only accepts calls from the RocketDepositPool contract
function depositExcess() override external payable onlyLatestContract("rocketDepositPool", msg.sender) {
// Emit ether deposited event
emit EtherDeposited(msg.sender, msg.value, block.timestamp);
}
// Mint rETH
// Only accepts calls from the RocketDepositPool contract
function mint(uint256 _ethAmount, address _to) override external onlyLatestContract("rocketDepositPool", msg.sender) {
// Get rETH amount
uint256 rethAmount = getRethValue(_ethAmount);
// Check rETH amount
require(rethAmount > 0, "Invalid token mint amount");
// Update balance & supply
_mint(_to, rethAmount);
// Emit tokens minted event
emit TokensMinted(_to, rethAmount, _ethAmount, block.timestamp);
}
// Burn rETH for ETH
function burn(uint256 _rethAmount) override external {
// Check rETH amount
require(_rethAmount > 0, "Invalid token burn amount");
require(balanceOf(msg.sender) >= _rethAmount, "Insufficient rETH balance");
// Get ETH amount
uint256 ethAmount = getEthValue(_rethAmount);
// Get & check ETH balance
uint256 ethBalance = getTotalCollateral();
require(ethBalance >= ethAmount, "Insufficient ETH balance for exchange");
// Update balance & supply
_burn(msg.sender, _rethAmount);
// Withdraw ETH from deposit pool if required
withdrawDepositCollateral(ethAmount);
// Transfer ETH to sender
msg.sender.transfer(ethAmount);
// Emit tokens burned event
emit TokensBurned(msg.sender, _rethAmount, ethAmount, block.timestamp);
}
// Withdraw ETH from the deposit pool for collateral if required
function withdrawDepositCollateral(uint256 _ethRequired) private {
// Check rETH contract balance
uint256 ethBalance = address(this).balance;
if (ethBalance >= _ethRequired) { return; }
// Withdraw
RocketDepositPoolInterface rocketDepositPool = RocketDepositPoolInterface(getContractAddress("rocketDepositPool"));
rocketDepositPool.withdrawExcessBalance(_ethRequired.sub(ethBalance));
}
// Sends any excess ETH from this contract to the deposit pool (as determined by target collateral rate)
function depositExcessCollateral() external override {
// Load contracts
RocketDAOProtocolSettingsNetworkInterface rocketDAOProtocolSettingsNetwork = RocketDAOProtocolSettingsNetworkInterface(getContractAddress("rocketDAOProtocolSettingsNetwork"));
RocketDepositPoolInterface rocketDepositPool = RocketDepositPoolInterface(getContractAddress("rocketDepositPool"));
// Get collateral and target collateral rate
uint256 collateralRate = getCollateralRate();
uint256 targetCollateralRate = rocketDAOProtocolSettingsNetwork.getTargetRethCollateralRate();
// Check if we are in excess
if (collateralRate > targetCollateralRate) {
// Calculate our target collateral in ETH
uint256 targetCollateral = address(this).balance.mul(targetCollateralRate).div(collateralRate);
// If we have excess
if (address(this).balance > targetCollateral) {
// Send that excess to deposit pool
uint256 excessCollateral = address(this).balance.sub(targetCollateral);
rocketDepositPool.recycleExcessCollateral{value: excessCollateral}();
}
}
}
// This is called by the base ERC20 contract before all transfer, mint, and burns
function _beforeTokenTransfer(address from, address, uint256) internal override {
// Don't run check if this is a mint transaction
if (from != address(0)) {
// Check which block the user's last deposit was
bytes32 key = keccak256(abi.encodePacked("user.deposit.block", from));
uint256 lastDepositBlock = getUint(key);
if (lastDepositBlock > 0) {
// Ensure enough blocks have passed
uint256 depositDelay = getUint(keccak256(abi.encodePacked(keccak256("dao.protocol.setting.network"), "network.reth.deposit.delay")));
uint256 blocksPassed = block.number.sub(lastDepositBlock);
require(blocksPassed > depositDelay, "Not enough time has passed since deposit");
// Clear the state as it's no longer necessary to check this until another deposit is made
deleteUint(key);
}
}
}
}