/
MantleTokenMigrator.sol
319 lines (251 loc) · 13.9 KB
/
MantleTokenMigrator.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
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
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
// SPDX-License-Identifier: MIT
pragma solidity 0.8.13;
import {ERC20} from "solmate/src/tokens/ERC20.sol";
import {SafeTransferLib} from "solmate/src/utils/SafeTransferLib.sol";
/// @title Mantle Token Migrator
/// @author 0xMantle
/// @notice Token migration contract for the BIT to MNT token migration
contract MantleTokenMigrator {
using SafeTransferLib for ERC20;
/* ========== STATE VARIABLES ========== */
/// @dev The address of the BIT token contract
address public immutable BIT_TOKEN_ADDRESS;
/// @dev The address of the MNT token contract
address public immutable MNT_TOKEN_ADDRESS;
/// @dev The numerator of the token conversion rate
uint256 public immutable TOKEN_CONVERSION_NUMERATOR;
/// @dev The denominator of the token conversion rate
uint256 public immutable TOKEN_CONVERSION_DENOMINATOR;
/// @dev The address of the treasury contract that receives defunded tokens
address public treasury;
/// @dev The address of the owner of the contract
address public owner;
/// @dev Boolean indicating if this contract is halted
bool public halted;
/* ========== EVENTS ========== */
// TokenSwap Events
/// @dev Emitted when a user swaps BIT for MNT
/// @param to The address of the user that swapped BIT for MNT
/// @param amountOfBitSwapped The amount of BIT swapped
/// @param amountOfMntReceived The amount of MNT received
event TokensMigrated(address indexed to, uint256 amountOfBitSwapped, uint256 amountOfMntReceived);
// Contract State Events
/// @dev Emitted when the owner of the contract is changed
/// @param previousOwner The address of the previous owner of this contract
/// @param newOwner The address of the new owner of this contract
event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
/// @dev Emitted when the contract is halted
/// @param halter The address of the caller that halted this contract
event ContractHalted(address indexed halter);
/// @dev Emitted when the contract is unhalted
/// @param halter The address of the caller that unhalted this contract
event ContractUnhalted(address indexed halter);
/// @dev Emitted when the treasury address is changed
/// @param previousTreasury The address of the previous treasury
/// @param newTreasury The address of the new treasury
event TreasuryChanged(address indexed previousTreasury, address indexed newTreasury);
// Admin Events
/// @dev Emitted when non BIT/MNT tokens are swept from the contract by the owner to the recipient address
/// @param token The address of the token contract that was swept
/// @param recipient The address of the recipient of the swept tokens
/// @param amount The amount of tokens swept
event TokensSwept(address indexed token, address indexed recipient, uint256 amount);
/// @dev Emitted when BIT/MNT tokens are defunded from the contract by the owner to the treasury
/// @param defunder The address of the defunder
/// @param token The address of the token contract that was defunded
/// @param amount The amount of tokens defunded
event ContractDefunded(address indexed defunder, address indexed token, uint256 amount);
/* ========== ERRORS ========== */
/// @notice Thrown when the caller is not the owner and the function being called uses the {onlyOwner} modifier
/// @param caller The address of the caller
error MantleTokenMigrator_OnlyOwner(address caller);
/// @notice Thrown when the contract is halted and the function being called uses the {onlyWhenNotHalted} modifier
error MantleTokenMigrator_OnlyWhenNotHalted();
/// @notice Thrown when the input passed into the {_migrateTokens} function is zero
error MantleTokenMigrator_ZeroSwap();
/// @notice Thrown when at least one of the inputs passed into the constructor is a zero value
error MantleTokenMigrator_ImproperlyInitialized();
/// @notice Thrown when the {_tokenAddress} passed into the {sweepTokens} function is the BIT or MNT token address
/// @param token The address of the token contract
error MantleTokenMigrator_SweepNotAllowed(address token);
/// @notice Thrown when the {_tokenAddress} passed into the {defundContract} function is NOT the BIT or MNT token address
/// @param token The address of the token contract
error MantleTokenMigrator_InvalidFundingToken(address token);
/// @notice Thrown when the contract receives a call with an invalid {msg}.data payload
/// @param data The msg.data payload
error MantleTokenMigrator_InvalidMessageData(bytes data);
/// @notice Thrown when the contract receives a call with a non-zero {msg.value}
error MantleTokenMigrator_EthNotAccepted();
/* ========== MODIFIERS ========== */
/// @notice Modifier that checks that the caller is the owner of the contract
/// @dev Throws {MantleTokenMigrator_OnlyOwner} if the caller is not the owner
modifier onlyOwner() {
if (msg.sender != owner) revert MantleTokenMigrator_OnlyOwner(msg.sender);
_;
}
/// @notice Modifier that checks that the contract is not halted
/// @dev Throws {MantleTokenMigrator_OnlyWhenNotHalted} if the contract is halted
modifier onlyWhenNotHalted() {
if (halted) revert MantleTokenMigrator_OnlyWhenNotHalted();
_;
}
/// @notice Initializes the MantleTokenMigrator contract, setting the initial deployer as the contract owner
/// @dev _bitTokenAddress, _mntTokenAddress, _tokenConversionNumerator, and _tokenConversionDenominator are immutable: they can only be set once during construction
/// @dev the contract is initialized in a halted state
/// @dev Requirements:
/// - all parameters must be non-zero
/// - _bitTokenAddress and _mntTokenAddress are assumed to have the same number of decimals
/// @param _bitTokenAddress The address of the BIT token contract
/// @param _mntTokenAddress The address of the MNT token contract
/// @param _treasury The address of the treasury contract that receives defunded tokens
/// @param _tokenConversionNumerator The numerator of the token conversion rate
/// @param _tokenConversionDenominator The denominator of the token conversion rate
constructor(
address _bitTokenAddress,
address _mntTokenAddress,
address _treasury,
uint256 _tokenConversionNumerator,
uint256 _tokenConversionDenominator
) {
if (
_bitTokenAddress == address(0) || _mntTokenAddress == address(0) || _treasury == address(0)
|| _tokenConversionNumerator == 0 || _tokenConversionDenominator == 0
) revert MantleTokenMigrator_ImproperlyInitialized();
owner = msg.sender;
halted = true;
BIT_TOKEN_ADDRESS = _bitTokenAddress;
MNT_TOKEN_ADDRESS = _mntTokenAddress;
treasury = _treasury;
TOKEN_CONVERSION_NUMERATOR = _tokenConversionNumerator;
TOKEN_CONVERSION_DENOMINATOR = _tokenConversionDenominator;
}
/* ========== FALLBACKS ========== */
/// @notice Fallback function that reverts if non-valid calldata is sent to the contract
fallback() external payable {
if (msg.data.length != 0) revert MantleTokenMigrator_InvalidMessageData(msg.data);
}
/// @notice Receive function that reverts if ETH is sent to the contract with a call
/// @dev This function is called whenever the contract receives ETH
/// @dev ETH can still be forced into this contract with a selfdestruct, but it has no impact on the contract state
receive() external payable {
revert MantleTokenMigrator_EthNotAccepted();
}
/* ========== TOKEN SWAPPING ========== */
/// @notice Swaps all of the caller's BIT tokens for MNT tokens
/// @dev emits a {TokensMigrated} event
/// @dev Requirements:
/// - The caller must have approved this contract to spend their BIT tokens
/// - The caller must have a non-zero balance of BIT tokens
/// - The contract must not be halted
function migrateAllBIT() external onlyWhenNotHalted {
uint256 amount = ERC20(BIT_TOKEN_ADDRESS).balanceOf(msg.sender);
_migrateTokens(amount);
}
/// @notice Swaps a specified amount of the caller's BIT tokens for MNT tokens
/// @dev emits a {TokensMigrated} event
/// @dev Requirements:
/// - The caller must have approved this contract to spend at least {_amount} of their BIT tokens
/// - The caller must have a balance of at least {_amount} of BIT tokens
/// - The contract must not be halted
/// @param _amount The amount of BIT tokens to swap
function migrateBIT(uint256 _amount) external onlyWhenNotHalted {
_migrateTokens(_amount);
}
/// @notice Calculates the amount of MNT tokens to be recieved for a given amount of BIT tokens
/// @param _amount The amount of BIT tokens to swap
/// @return The amount of MNT tokens to be recieved
function tokenMigrationAmountToReceive(uint256 _amount) external view returns (uint256) {
return _tokenSwapCalculation(_amount);
}
/// @notice Internal function that swaps a specified amount of the caller's BIT tokens for MNT tokens
/// @dev emits a {TokensMigrated} event
/// @dev Requirements:
/// - The caller must have approved this contract to spend at least {_amount} of their BIT tokens
/// - The caller must have a balance of at least {_amount} of BIT tokens
/// @param _amount The amount of BIT tokens to swap
function _migrateTokens(uint256 _amount) internal {
if (_amount == 0) revert MantleTokenMigrator_ZeroSwap();
uint256 amountToSwap = _tokenSwapCalculation(_amount);
// transfer user's BIT tokens to this contract
ERC20(BIT_TOKEN_ADDRESS).safeTransferFrom(msg.sender, address(this), _amount);
// transfer MNT tokens to user, if there are insufficient tokens, in the contract this will revert
ERC20(MNT_TOKEN_ADDRESS).safeTransfer(msg.sender, amountToSwap);
emit TokensMigrated(msg.sender, _amount, amountToSwap);
}
/// @notice Internal function that calculates the amount of MNT tokens to be recieved for a given amount of BIT tokens
/// @param _amount The amount of BIT tokens to swap
/// @return The amount of MNT tokens to be recieved
function _tokenSwapCalculation(uint256 _amount) internal view returns (uint256) {
return (_amount * TOKEN_CONVERSION_NUMERATOR) / TOKEN_CONVERSION_DENOMINATOR;
}
/* ========== ADMIN UTILS ========== */
// Ownership Functions
/// @notice Transfers ownership of the contract to a new address
/// @dev emits an {OwnershipTransferred} event
/// @dev Requirements:
/// - The caller must be the contract owner
function transferOwnership(address _newOwner) public onlyOwner {
owner = _newOwner;
emit OwnershipTransferred(msg.sender, _newOwner);
}
// Contract State Functions
/// @notice Halts the contract, preventing token migrations
/// @dev emits a {ContractHalted} event
/// @dev Requirements:
/// - The caller must be the contract owner
function haltContract() public onlyOwner {
halted = true;
emit ContractHalted(msg.sender);
}
/// @notice Unhalts the contract, allowing token migrations
/// @dev emits a {ContractUnhalted} event
/// @dev Requirements:
/// - The caller must be the contract owner
function unhaltContract() public onlyOwner {
halted = false;
emit ContractUnhalted(msg.sender);
}
/// @notice Sets the treasury address
/// @dev emits a {TreasuryChanged} event
/// @dev Requirements:
/// - The caller must be the contract owner
function setTreasury(address _treasury) public onlyOwner {
address oldTreasury = treasury;
treasury = _treasury;
emit TreasuryChanged(oldTreasury, _treasury);
}
// Token Management Functions
/// @notice Defunds the contract by transferring a specified amount of BIT or MNT tokens to the treasury address
/// @dev emits a {ContractDefunded} event
/// @dev Requirements:
/// - The caller must be the contract owner
/// - {_tokenAddress} must be either the BIT or the MNT token address
/// - The contract must have a balance of at least {_amount} of {_tokenAddress} tokens
/// @param _tokenAddress The address of the token to defund
/// @param _amount The amount of tokens to defund
function defundContract(address _tokenAddress, uint256 _amount) public onlyOwner {
if (_tokenAddress != BIT_TOKEN_ADDRESS && _tokenAddress != MNT_TOKEN_ADDRESS) {
revert MantleTokenMigrator_InvalidFundingToken(_tokenAddress);
}
// we can only defund BIT or MNT into the predefined treasury address
ERC20(_tokenAddress).safeTransfer(treasury, _amount);
emit ContractDefunded(treasury, _tokenAddress, _amount);
}
/// @notice Sweeps a specified amount of tokens to an arbitrary address
/// @dev emits a {TokensSwept} event
/// @dev Requirements:
/// - The caller must be the contract owner
/// - {_tokenAddress} must not the BIT or the MNT token address
/// - The contract must have a balance of at least {_amount} of {_tokenAddress} tokens
/// @param _tokenAddress The address of the token to sweep
/// @param _recipient The address to sweep the tokens to
/// @param _amount The amount of tokens to sweep
function sweepTokens(address _tokenAddress, address _recipient, uint256 _amount) public onlyOwner {
// we can only sweep tokens that are not BIT or MNT to an arbitrary addres
if ((_tokenAddress == address(BIT_TOKEN_ADDRESS)) || (_tokenAddress == address(MNT_TOKEN_ADDRESS))) {
revert MantleTokenMigrator_SweepNotAllowed(_tokenAddress);
}
ERC20(_tokenAddress).safeTransfer(_recipient, _amount);
emit TokensSwept(_tokenAddress, _recipient, _amount);
}
}