forked from timeless-fi/options-token
-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathOptionsToken.sol
More file actions
207 lines (171 loc) · 8.38 KB
/
OptionsToken.sol
File metadata and controls
207 lines (171 loc) · 8.38 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
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
// SPDX-License-Identifier: AGPL-3.0
pragma solidity ^0.8.13;
import {OwnableUpgradeable} from "oz-upgradeable/access/OwnableUpgradeable.sol";
import {ERC20Upgradeable} from "oz-upgradeable/token/ERC20/ERC20Upgradeable.sol";
import {UUPSUpgradeable} from "oz-upgradeable/proxy/utils/UUPSUpgradeable.sol";
import {PausableUpgradeable} from "oz-upgradeable/security/PausableUpgradeable.sol";
import {IOptionsToken} from "./interfaces/IOptionsToken.sol";
import {IOracle} from "./interfaces/IOracle.sol";
import {IExercise} from "./interfaces/IExercise.sol";
/// @title Options Token
/// @author Eidolon & lookee
/// @notice Options token representing the right to perform an advantageous action,
/// such as purchasing the underlying token at a discount to the market price.
contract OptionsToken is IOptionsToken, ERC20Upgradeable, OwnableUpgradeable, UUPSUpgradeable, PausableUpgradeable {
/// -----------------------------------------------------------------------
/// Errors
/// -----------------------------------------------------------------------
error OptionsToken__NotTokenAdmin();
error OptionsToken__NotExerciseContract();
error Upgradeable__Unauthorized();
/// -----------------------------------------------------------------------
/// Events
/// -----------------------------------------------------------------------
event Exercise(address indexed sender, address indexed recipient, uint256 amount, address data0, uint256 data1, uint256 data2);
event SetOracle(IOracle indexed newOracle);
event SetExerciseContract(address indexed _address, bool _isExercise);
/// -----------------------------------------------------------------------
/// Constant parameters
/// -----------------------------------------------------------------------
uint256 public constant UPGRADE_TIMELOCK = 48 hours;
uint256 public constant FUTURE_NEXT_PROPOSAL_TIME = 365 days * 100;
/// -----------------------------------------------------------------------
/// Storage variables
/// -----------------------------------------------------------------------
/// @notice The contract that has the right to mint options tokens
address public tokenAdmin;
mapping(address => bool) public isExerciseContract;
uint256 public upgradeProposalTime;
address public nextImplementation;
/// @custom:oz-upgrades-unsafe-allow constructor
constructor() {
_disableInitializers();
}
/// -----------------------------------------------------------------------
/// Initializer
/// -----------------------------------------------------------------------
function initialize(string memory name_, string memory symbol_, address tokenAdmin_) external initializer {
__UUPSUpgradeable_init();
__ERC20_init(name_, symbol_);
__Ownable_init();
__Pausable_init();
tokenAdmin = tokenAdmin_;
_clearUpgradeCooldown();
}
/// -----------------------------------------------------------------------
/// External functions
/// -----------------------------------------------------------------------
/// @notice Called by the token admin to mint options tokens
/// @param to The address that will receive the minted options tokens
/// @param amount The amount of options tokens that will be minted
function mint(address to, uint256 amount) external virtual override {
/// -----------------------------------------------------------------------
/// Verification
/// -----------------------------------------------------------------------
if (msg.sender != tokenAdmin) revert OptionsToken__NotTokenAdmin();
/// -----------------------------------------------------------------------
/// State updates
/// -----------------------------------------------------------------------
// skip if amount is zero
if (amount == 0) return;
// mint options tokens
_mint(to, amount);
}
/// @notice Exercises options tokens, burning them and giving the reward to the recipient.
/// @param amount The amount of options tokens to exercise
/// @param recipient The recipient of the reward
/// @param option The address of the Exercise contract with the redemption logic
/// @param params Extra parameters to be used by the exercise function
/// @return paymentAmount token amount paid for exercising
/// @return data0 address data to return by different exerciser contracts
/// @return data1 integer data to return by different exerciser contracts
/// @return data2 additional integer data to return by different exerciser contracts
function exercise(uint256 amount, address recipient, address option, bytes calldata params)
external
virtual
whenNotPaused
returns (
uint256 paymentAmount,
address,
uint256,
uint256 // misc data
)
{
return _exercise(amount, recipient, option, params);
}
/// -----------------------------------------------------------------------
/// Owner functions
/// -----------------------------------------------------------------------
/// @notice Adds a new Exercise contract to the available options.
/// @param _address Address of the Exercise contract, that implements BaseExercise.
/// @param _isExercise Whether oToken holders should be allowed to exercise using this option.
function setExerciseContract(address _address, bool _isExercise) external onlyOwner {
isExerciseContract[_address] = _isExercise;
emit SetExerciseContract(_address, _isExercise);
}
/// @notice Pauses functionality related to exercises of contracts.
function pause() external onlyOwner {
_pause();
}
/// @notice Unpauses functionality related to exercises of contracts.
function unpause() external onlyOwner {
_unpause();
}
/// -----------------------------------------------------------------------
/// Internal functions
/// -----------------------------------------------------------------------
function _exercise(uint256 amount, address recipient, address option, bytes calldata params)
internal
virtual
returns (
uint256 paymentAmount,
address data0,
uint256 data1,
uint256 data2 // misc data
)
{
// skip if amount is zero
if (amount == 0) return (0, address(0), 0, 0);
// revert if the exercise contract is not whitelisted
if (!isExerciseContract[option]) revert OptionsToken__NotExerciseContract();
// burn options tokens
_burn(msg.sender, amount);
// give rewards to recipient
(paymentAmount, data0, data1, data2) = IExercise(option).exercise(msg.sender, amount, recipient, params);
// emit event
emit Exercise(msg.sender, recipient, amount, data0, data1, data2);
}
/// -----------------------------------------------------------------------
/// UUPS functions
/// -----------------------------------------------------------------------
/**
* @dev This function must be called prior to upgrading the implementation.
* It's required to wait UPGRADE_TIMELOCK seconds before executing the upgrade.
*/
function initiateUpgradeCooldown(address _nextImplementation) external onlyOwner {
upgradeProposalTime = block.timestamp;
nextImplementation = _nextImplementation;
}
/**
* @dev This function is called:
* - in initialize()
* - as part of a successful upgrade
* - manually to clear the upgrade cooldown.
*/
function _clearUpgradeCooldown() internal {
upgradeProposalTime = block.timestamp + FUTURE_NEXT_PROPOSAL_TIME;
}
function clearUpgradeCooldown() external onlyOwner {
_clearUpgradeCooldown();
}
/**
* @dev This function must be overriden simply for access control purposes.
* Only the owner can upgrade the implementation once the timelock
* has passed.
*/
function _authorizeUpgrade(address _nextImplementation) internal override onlyOwner {
require(upgradeProposalTime + UPGRADE_TIMELOCK < block.timestamp, "Upgrade cooldown not initiated or still ongoing");
require(_nextImplementation == nextImplementation, "Incorrect implementation");
_clearUpgradeCooldown();
}
}