-
Notifications
You must be signed in to change notification settings - Fork 20
/
Copy pathBaseV2Minter.sol
168 lines (134 loc) · 6.07 KB
/
BaseV2Minter.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
166
167
168
// SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity ^0.8.0;
import {Ownable} from "solady/auth/Ownable.sol";
import {SafeTransferLib} from "solady/utils/SafeTransferLib.sol";
import {ERC4626} from "@ERC4626/ERC4626.sol";
import {HERMES} from "@hermes/tokens/HERMES.sol";
import {FlywheelGaugeRewards} from "@rewards/rewards/FlywheelGaugeRewards.sol";
import {IBaseV2Minter} from "../interfaces/IBaseV2Minter.sol";
/// @title Base V2 Minter - Mints HERMES tokens for the B(3,3) system
contract BaseV2Minter is Ownable, IBaseV2Minter {
using SafeTransferLib for address;
/*//////////////////////////////////////////////////////////////
MINTER STATE
//////////////////////////////////////////////////////////////*/
/// @dev allows minting once per week (reset every Thursday 00:00 UTC)
uint256 internal constant week = 86400 * 7;
/// @dev 2% per week target emission
uint256 internal constant base = 1000;
uint256 internal constant max_tail_emission = 100;
uint256 internal constant max_dao_share = 300;
/// @inheritdoc IBaseV2Minter
address public immutable override underlying;
/// @inheritdoc IBaseV2Minter
ERC4626 public immutable override vault;
/// @inheritdoc IBaseV2Minter
FlywheelGaugeRewards public override flywheelGaugeRewards;
/// @inheritdoc IBaseV2Minter
address public override dao;
/// @inheritdoc IBaseV2Minter
uint256 public override daoShare = 100;
uint256 public override tailEmission = 20;
/// @inheritdoc IBaseV2Minter
/// @inheritdoc IBaseV2Minter
uint256 public override weekly;
/// @inheritdoc IBaseV2Minter
uint256 public override activePeriod;
address internal initializer;
constructor(
address _vault, // the B(3,3) system that will be locked into
address _dao,
address _owner
) {
_initializeOwner(_owner);
initializer = msg.sender;
dao = _dao;
underlying = address(ERC4626(_vault).asset());
vault = ERC4626(_vault);
}
/*//////////////////////////////////////////////////////////////
FALLBACK LOGIC
//////////////////////////////////////////////////////////////*/
fallback() external {
updatePeriod();
}
/*//////////////////////////////////////////////////////////////
ADMIN LOGIC
//////////////////////////////////////////////////////////////*/
/// @inheritdoc IBaseV2Minter
function initialize(FlywheelGaugeRewards _flywheelGaugeRewards) external {
if (initializer != msg.sender) revert NotInitializer();
flywheelGaugeRewards = _flywheelGaugeRewards;
initializer = address(0);
activePeriod = (block.timestamp / week) * week;
}
/// @inheritdoc IBaseV2Minter
function setDao(address _dao) external onlyOwner {
/// @dev DAO can be set to address(0) to disable DAO rewards.
dao = _dao;
}
/// @inheritdoc IBaseV2Minter
function setDaoShare(uint256 _daoShare) external onlyOwner {
if (_daoShare > max_dao_share) revert DaoShareTooHigh();
daoShare = _daoShare;
}
/// @inheritdoc IBaseV2Minter
function setTailEmission(uint256 _tail_emission) external onlyOwner {
if (_tail_emission > max_tail_emission) revert TailEmissionTooHigh();
tailEmission = _tail_emission;
}
/*//////////////////////////////////////////////////////////////
EMISSION LOGIC
//////////////////////////////////////////////////////////////*/
/// @inheritdoc IBaseV2Minter
function circulatingSupply() public view returns (uint256) {
return HERMES(underlying).totalSupply() - vault.totalAssets();
}
/// @inheritdoc IBaseV2Minter
function weeklyEmission() public view returns (uint256) {
return (circulatingSupply() * tailEmission) / base;
}
/// @inheritdoc IBaseV2Minter
function calculateGrowth(uint256 _minted) public view returns (uint256) {
return (vault.totalAssets() * _minted) / HERMES(underlying).totalSupply();
}
/// @inheritdoc IBaseV2Minter
function updatePeriod() public returns (uint256) {
uint256 _period = activePeriod;
// only trigger if new week
if (block.timestamp >= _period + week && initializer == address(0)) {
_period = (block.timestamp / week) * week;
activePeriod = _period;
uint256 newWeeklyEmission = weeklyEmission();
weekly += newWeeklyEmission;
uint256 _circulatingSupply = circulatingSupply();
uint256 _growth = calculateGrowth(newWeeklyEmission);
uint256 _required = _growth + newWeeklyEmission;
/// @dev share of newWeeklyEmission emissions sent to DAO.
uint256 share = (_required * daoShare) / base;
_required += share;
uint256 _balanceOf = underlying.balanceOf(address(this));
if (_balanceOf < _required) {
HERMES(underlying).mint(address(this), _required - _balanceOf);
}
underlying.safeTransfer(address(vault), _growth);
if (dao != address(0)) underlying.safeTransfer(dao, share);
emit Mint(msg.sender, newWeeklyEmission, _circulatingSupply, _growth, share);
/// @dev queue rewards for the cycle, anyone can call if fails
/// queueRewardsForCycle will call this function but won't enter
/// here because activePeriod was updated
try flywheelGaugeRewards.queueRewardsForCycle() {} catch {}
}
return _period;
}
/*//////////////////////////////////////////////////////////////
REWARDS STREAM LOGIC
//////////////////////////////////////////////////////////////*/
/// @inheritdoc IBaseV2Minter
function getRewards() external returns (uint256 totalQueuedForCycle) {
if (address(flywheelGaugeRewards) != msg.sender) revert NotFlywheelGaugeRewards();
totalQueuedForCycle = weekly;
weekly = 0;
underlying.safeTransfer(msg.sender, totalQueuedForCycle);
}
}