-
Notifications
You must be signed in to change notification settings - Fork 10
/
Copy pathRenzoOracle.sol
165 lines (134 loc) · 6.87 KB
/
RenzoOracle.sol
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
// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.19;
import "@openzeppelin/contracts-upgradeable/security/ReentrancyGuardUpgradeable.sol";
import "../Permissions/IRoleManager.sol";
import "./RenzoOracleStorage.sol";
import "./IRenzoOracle.sol";
import "../Errors/Errors.sol";
/// @dev This contract will be responsible for looking up values via Chainlink
/// Data retrieved will be verified for liveness via a max age on the oracle lookup.
/// All tokens should be denominated in the same base currency and contain the same decimals on the price lookup.
contract RenzoOracle is
IRenzoOracle,
Initializable,
ReentrancyGuardUpgradeable,
RenzoOracleStorageV1
{
/// @dev Error for invalid 0x0 address
string constant INVALID_0_INPUT = "Invalid 0 input";
// Scale factor for all values of prices
uint256 constant SCALE_FACTOR = 10 ** 18;
/// @dev The maxmimum staleness allowed for a price feed from chainlink
uint256 constant MAX_TIME_WINDOW = 86400 + 60; // 24 hours + 60 seconds
/// @dev Allows only a whitelisted address to configure the contract
modifier onlyOracleAdmin() {
if (!roleManager.isOracleAdmin(msg.sender)) revert NotOracleAdmin();
_;
}
/// @dev Event emitted when a token's oracle address is updated
event OracleAddressUpdated(IERC20 token, AggregatorV3Interface oracleAddress);
/// @dev Prevents implementation contract from being initialized.
/// @custom:oz-upgrades-unsafe-allow constructor
constructor() {
_disableInitializers();
}
/// @dev Initializes the contract with initial vars
function initialize(IRoleManager _roleManager) public initializer {
if (address(_roleManager) == address(0x0)) revert InvalidZeroInput();
__ReentrancyGuard_init();
roleManager = _roleManager;
}
/// @dev Sets addresses for oracle lookup. Permission gated to oracel admins only.
/// Set to address 0x0 to disable lookups for the token.
function setOracleAddress(
IERC20 _token,
AggregatorV3Interface _oracleAddress
) external nonReentrant onlyOracleAdmin {
if (address(_token) == address(0x0)) revert InvalidZeroInput();
// Verify that the pricing of the oracle is 18 decimals - pricing calculations will be off otherwise
if (_oracleAddress.decimals() != 18)
revert InvalidTokenDecimals(18, _oracleAddress.decimals());
tokenOracleLookup[_token] = _oracleAddress;
emit OracleAddressUpdated(_token, _oracleAddress);
}
/// @dev Given a single token and balance, return value of the asset in underlying currency
/// The value returned will be denominated in the decimal precision of the lookup oracle
/// (e.g. a value of 100 would return as 100 * 10^18)
function lookupTokenValue(IERC20 _token, uint256 _balance) public view returns (uint256) {
AggregatorV3Interface oracle = tokenOracleLookup[_token];
if (address(oracle) == address(0x0)) revert OracleNotFound();
(, int256 price, , uint256 timestamp, ) = oracle.latestRoundData();
if (timestamp < block.timestamp - MAX_TIME_WINDOW) revert OraclePriceExpired();
if (price <= 0) revert InvalidOraclePrice();
// Price is times 10**18 ensure value amount is scaled
return (uint256(price) * _balance) / SCALE_FACTOR;
}
/// @dev Given a single token and value, return amount of tokens needed to represent that value
/// Assumes the token value is already denominated in the same decimal precision as the oracle
function lookupTokenAmountFromValue(
IERC20 _token,
uint256 _value
) external view returns (uint256) {
AggregatorV3Interface oracle = tokenOracleLookup[_token];
if (address(oracle) == address(0x0)) revert OracleNotFound();
(, int256 price, , uint256 timestamp, ) = oracle.latestRoundData();
if (timestamp < block.timestamp - MAX_TIME_WINDOW) revert OraclePriceExpired();
if (price <= 0) revert InvalidOraclePrice();
// Price is times 10**18 ensure token amount is scaled
return (_value * SCALE_FACTOR) / uint256(price);
}
// @dev Given list of tokens and balances, return total value (assumes all lookups are denomintated in same underlying currency)
/// The value returned will be denominated in the decimal precision of the lookup oracle
/// (e.g. a value of 100 would return as 100 * 10^18)
function lookupTokenValues(
IERC20[] memory _tokens,
uint256[] memory _balances
) external view returns (uint256) {
if (_tokens.length != _balances.length) revert MismatchedArrayLengths();
uint256 totalValue = 0;
uint256 tokenLength = _tokens.length;
for (uint256 i = 0; i < tokenLength; ) {
totalValue += lookupTokenValue(_tokens[i], _balances[i]);
unchecked {
++i;
}
}
return totalValue;
}
/// @dev Given amount of current protocol value, new value being added, and supply of ezETH, determine amount to mint
/// Values should be denominated in the same underlying currency with the same decimal precision
function calculateMintAmount(
uint256 _currentValueInProtocol,
uint256 _newValueAdded,
uint256 _existingEzETHSupply
) external pure returns (uint256) {
// For first mint, just return the new value added.
// Checking both current value and existing supply to guard against gaming the initial mint
if (_currentValueInProtocol == 0 || _existingEzETHSupply == 0) {
return _newValueAdded; // value is priced in base units, so divide by scale factor
}
// Calculate the percentage of value after the deposit
uint256 inflationPercentaage = (SCALE_FACTOR * _newValueAdded) /
(_currentValueInProtocol + _newValueAdded);
// Calculate the new supply
uint256 newEzETHSupply = (_existingEzETHSupply * SCALE_FACTOR) /
(SCALE_FACTOR - inflationPercentaage);
// Subtract the old supply from the new supply to get the amount to mint
uint256 mintAmount = newEzETHSupply - _existingEzETHSupply;
// Sanity check
if (mintAmount == 0) revert InvalidTokenAmount();
return mintAmount;
}
// Given the amount of ezETH to burn, the supply of ezETH, and the total value in the protocol, determine amount of value to return to user
function calculateRedeemAmount(
uint256 _ezETHBeingBurned,
uint256 _existingEzETHSupply,
uint256 _currentValueInProtocol
) external pure returns (uint256) {
// This is just returning the percentage of TVL that matches the percentage of ezETH being burned
uint256 redeemAmount = (_currentValueInProtocol * _ezETHBeingBurned) / _existingEzETHSupply;
// Sanity check
if (redeemAmount == 0) revert InvalidTokenAmount();
return redeemAmount;
}
}