Skip to content

Commit 3056d28

Browse files
feat: introduce token reserve supply (#94)
* feat: update `IERC20Mintable` interface * feat: update `IERC20Mintable` interface * feat: implement new functions in `ERC20Mintable` contract * test: cover new functions with tests * fix: camel case typo * feat: update `MintFromReserve` and `BurnToReserve` events
1 parent 5faf92f commit 3056d28

File tree

3 files changed

+315
-0
lines changed

3 files changed

+315
-0
lines changed

contracts/base/ERC20Mintable.sol

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ abstract contract ERC20Mintable is ERC20Base, IERC20Mintable {
2323
uint16 maxPendingPremintsCount;
2424
mapping(uint256 => uint256) premintReschedulings;
2525
mapping(uint256 => uint256) premintReschedulingCounters;
26+
uint256 totalReserveSupply;
2627
}
2728

2829
/// @notice The structure that represents an array of premint records
@@ -106,6 +107,9 @@ abstract contract ERC20Mintable is ERC20Base, IERC20Mintable {
106107
/// @notice The provided value cannot be cast to uint64 type
107108
error InappropriateUint64Value(uint256 value);
108109

110+
/// @notice The amount of tokens to burn is greater than the total reserve supply
111+
error InsufficientReserveSupply();
112+
109113
// -------------------- Modifiers --------------------------------
110114

111115
/**
@@ -231,6 +235,25 @@ abstract contract ERC20Mintable is ERC20Base, IERC20Mintable {
231235
return _mintInternal(account, amount);
232236
}
233237

238+
/**
239+
* @inheritdoc IERC20Mintable
240+
*
241+
* @dev The contract must not be paused
242+
* @dev Can only be called by a minter account
243+
* @dev The message sender must not be blocklisted
244+
*/
245+
function mintFromReserve(
246+
address account,
247+
uint256 amount
248+
) external whenNotPaused onlyMinter notBlocklisted(_msgSender()) {
249+
_mintInternal(account, amount);
250+
251+
ExtendedStorageSlot storage storageSlot = _getExtendedStorageSlot();
252+
storageSlot.totalReserveSupply += amount;
253+
254+
emit MintFromReserve(_msgSender(), account, amount, storageSlot.totalReserveSupply);
255+
}
256+
234257
/**
235258
* @inheritdoc IERC20Mintable
236259
*
@@ -308,6 +331,30 @@ abstract contract ERC20Mintable is ERC20Base, IERC20Mintable {
308331
_burnInternal(_msgSender(), amount);
309332
}
310333

334+
/**
335+
* @inheritdoc IERC20Mintable
336+
*
337+
* @dev The contract must not be paused
338+
* @dev Can only be called by a minter account
339+
* @dev The message sender must not be blocklisted
340+
* @dev The amount of tokens to burn must be less than or equal to the total reserve supply
341+
*/
342+
function burnToReserve(uint256 amount) external whenNotPaused onlyMinter notBlocklisted(_msgSender()) {
343+
_burnInternal(_msgSender(), amount);
344+
345+
ExtendedStorageSlot storage storageSlot = _getExtendedStorageSlot();
346+
347+
if (storageSlot.totalReserveSupply < amount) {
348+
revert InsufficientReserveSupply();
349+
}
350+
351+
unchecked {
352+
storageSlot.totalReserveSupply -= amount;
353+
}
354+
355+
emit BurnToReserve(_msgSender(), amount, storageSlot.totalReserveSupply);
356+
}
357+
311358
/**
312359
* @notice Returns the total amount of preminted tokens
313360
* @param account The account to check the preminted balance for
@@ -385,6 +432,14 @@ abstract contract ERC20Mintable is ERC20Base, IERC20Mintable {
385432
return _mintersAllowance[minter];
386433
}
387434

435+
/**
436+
* @inheritdoc IERC20Mintable
437+
*/
438+
function totalReserveSupply() external view returns (uint256) {
439+
ExtendedStorageSlot storage storageSlot = _getExtendedStorageSlot();
440+
return storageSlot.totalReserveSupply;
441+
}
442+
388443
function _mintInternal(address account, uint256 amount) internal returns (bool) {
389444
if (amount == 0) {
390445
revert ZeroMintAmount();

contracts/base/interfaces/IERC20Mintable.sol

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,21 @@ interface IERC20Mintable {
3939
*/
4040
event Mint(address indexed minter, address indexed to, uint256 amount);
4141

42+
/**
43+
* @notice Emitted when tokens are minted from reserve
44+
*
45+
* @param minter The address of the minter
46+
* @param to The address of the tokens recipient
47+
* @param amount The amount of tokens being minted
48+
* @param newReserveSupply The new total reserve supply
49+
*/
50+
event MintFromReserve(
51+
address indexed minter,
52+
address indexed to,
53+
uint256 amount,
54+
uint256 newReserveSupply
55+
);
56+
4257
/**
4358
* @notice Emitted when tokens are preminted
4459
*
@@ -79,6 +94,19 @@ interface IERC20Mintable {
7994
*/
8095
event Burn(address indexed burner, uint256 amount);
8196

97+
/**
98+
* @notice Emitted when tokens are burned to reserve
99+
*
100+
* @param burner The address of the tokens burner
101+
* @param amount The amount of tokens being burned
102+
* @param newReserveSupply The new total reserve supply
103+
*/
104+
event BurnToReserve(
105+
address indexed burner,
106+
uint256 amount,
107+
uint256 newReserveSupply
108+
);
109+
82110
/**
83111
* @notice Emitted when the limit of premints is configured
84112
*
@@ -107,6 +135,13 @@ interface IERC20Mintable {
107135
*/
108136
function minterAllowance(address minter) external view returns (uint256);
109137

138+
/**
139+
* @notice Returns the total reserve supply
140+
*
141+
* @return The total reserve supply
142+
*/
143+
function totalReserveSupply() external view returns (uint256);
144+
110145
/**
111146
* @notice Updates the main minter address
112147
*
@@ -189,6 +224,20 @@ interface IERC20Mintable {
189224
*/
190225
function reschedulePremintRelease(uint256 originalRelease, uint256 targetRelease) external;
191226

227+
/**
228+
* @notice Mints tokens from reserve
229+
*
230+
* @dev Minting from reserve means that the tokens are minted in a regular way, but we also
231+
* increase the total reserve supply by the amount of tokens minted
232+
*
233+
* Emits a {Mint} event
234+
* Emits a {MintFromReserve} event
235+
*
236+
* @param account The address of a tokens recipient
237+
* @param amount The amount of tokens to mint
238+
*/
239+
function mintFromReserve(address account, uint256 amount) external;
240+
192241
/**
193242
* @notice Burns tokens
194243
*
@@ -197,4 +246,17 @@ interface IERC20Mintable {
197246
* @param amount The amount of tokens to burn
198247
*/
199248
function burn(uint256 amount) external;
249+
250+
/**
251+
* @notice Burns tokens to reserve
252+
*
253+
* @dev Burning to reserve means that the tokens are burned in a regular way, but we also
254+
* decrease the total reserve supply by the amount of tokens burned
255+
*
256+
* Emits a {Burn} event
257+
* Emits a {BurnToReserve} event
258+
*
259+
* @param amount The amount of tokens to burn
260+
*/
261+
function burnToReserve(uint256 amount) external;
200262
}

0 commit comments

Comments
 (0)