-
Notifications
You must be signed in to change notification settings - Fork 30
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge the develop branch into the master branch, v1.1.0-rc1
This merge contains the following set of changes: * Add burnFrom function into the token implementation (#41) * Add useful minting middlewares for CDP and other purposes (#48)
- Loading branch information
Showing
14 changed files
with
1,492 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,58 @@ | ||
// SPDX-License-Identifier: CC0-1.0 | ||
|
||
pragma solidity 0.8.15; | ||
|
||
import "./BaseMinter.sol"; | ||
|
||
/** | ||
* @title BalancedMinter | ||
* BOB minting/burning middleware with simple usage quotas. | ||
*/ | ||
contract BalancedMinter is BaseMinter { | ||
int128 public mintQuota; // remaining minting quota | ||
int128 public burnQuota; // remaining burning quota | ||
|
||
event UpdateQuotas(int128 mintQuota, int128 burnQuota); | ||
|
||
constructor(address _token, uint128 _mintQuota, uint128 _burnQuota) BaseMinter(_token) { | ||
mintQuota = int128(_mintQuota); | ||
burnQuota = int128(_burnQuota); | ||
} | ||
|
||
/** | ||
* @dev Adjusts mint/burn quotas for the given address. | ||
* Callable only by the contract owner. | ||
* @param _dMint delta for minting quota. | ||
* @param _dBurn delta for burning quota. | ||
*/ | ||
function adjustQuotas(int128 _dMint, int128 _dBurn) external onlyOwner { | ||
(int128 newMintQuota, int128 newBurnQuota) = (mintQuota + _dMint, burnQuota + _dBurn); | ||
(mintQuota, burnQuota) = (newMintQuota, newBurnQuota); | ||
|
||
emit UpdateQuotas(newBurnQuota, newBurnQuota); | ||
} | ||
|
||
/** | ||
* @dev Internal function for adjusting quotas on tokens mint. | ||
* @param _amount amount of minted tokens. | ||
*/ | ||
function _beforeMint(uint256 _amount) internal override { | ||
int128 amount = int128(uint128(_amount)); | ||
unchecked { | ||
require(mintQuota >= amount, "BalancedMinter: exceeds minting quota"); | ||
(mintQuota, burnQuota) = (mintQuota - amount, burnQuota + amount); | ||
} | ||
} | ||
|
||
/** | ||
* @dev Internal function for adjusting quotas on tokens burn. | ||
* @param _amount amount of burnt tokens. | ||
*/ | ||
function _beforeBurn(uint256 _amount) internal override { | ||
int128 amount = int128(uint128(_amount)); | ||
unchecked { | ||
require(burnQuota >= amount, "BalancedMinter: exceeds burning quota"); | ||
(mintQuota, burnQuota) = (mintQuota + amount, burnQuota - amount); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,104 @@ | ||
// SPDX-License-Identifier: CC0-1.0 | ||
|
||
pragma solidity 0.8.15; | ||
|
||
import "../utils/Ownable.sol"; | ||
import "../interfaces/IMintableERC20.sol"; | ||
import "../interfaces/IBurnableERC20.sol"; | ||
import "../interfaces/IERC677Receiver.sol"; | ||
|
||
/** | ||
* @title BaseMinter | ||
* Base contract for BOB minting/burning middleware | ||
*/ | ||
abstract contract BaseMinter is IMintableERC20, IBurnableERC20, IERC677Receiver, Ownable { | ||
address public immutable token; | ||
|
||
mapping(address => bool) public isMinter; | ||
|
||
event Mint(address minter, address to, uint256 amount); | ||
event Burn(address burner, address from, uint256 amount); | ||
|
||
constructor(address _token) { | ||
token = _token; | ||
} | ||
|
||
/** | ||
* @dev Updates mint/burn permissions for the given address. | ||
* Callable only by the contract owner. | ||
* @param _account managed minter account address. | ||
* @param _enabled true, if enabling minting/burning, false otherwise. | ||
*/ | ||
function setMinter(address _account, bool _enabled) external onlyOwner { | ||
isMinter[_account] = _enabled; | ||
} | ||
|
||
/** | ||
* @dev Mints the specified amount of tokens. | ||
* This contract should have minting permissions assigned to it in the token contract. | ||
* Callable only by one of the minter addresses. | ||
* @param _to address of the tokens receiver. | ||
* @param _amount amount of tokens to mint. | ||
*/ | ||
function mint(address _to, uint256 _amount) external override { | ||
require(isMinter[msg.sender], "BaseMinter: not a minter"); | ||
|
||
_beforeMint(_amount); | ||
IMintableERC20(token).mint(_to, _amount); | ||
|
||
emit Mint(msg.sender, _to, _amount); | ||
} | ||
|
||
/** | ||
* @dev Burns tokens sent to the address. | ||
* Callable only by one of the minter addresses. | ||
* Caller should send specified amount of tokens to this contract, prior to calling burn. | ||
* @param _amount amount of tokens to burn. | ||
*/ | ||
function burn(uint256 _amount) external override { | ||
require(isMinter[msg.sender], "BaseMinter: not a burner"); | ||
|
||
_beforeBurn(_amount); | ||
IBurnableERC20(token).burn(_amount); | ||
|
||
emit Burn(msg.sender, msg.sender, _amount); | ||
} | ||
|
||
/** | ||
* @dev Burns pre-approved tokens from the other address. | ||
* Callable only by one of the burner addresses. | ||
* Minters should handle with extra care cases when first argument is not msg.sender. | ||
* @param _from account to burn tokens from. | ||
* @param _amount amount of tokens to burn. Should be less than or equal to account balance. | ||
*/ | ||
function burnFrom(address _from, uint256 _amount) external override { | ||
require(isMinter[msg.sender], "BaseMinter: not a burner"); | ||
|
||
_beforeBurn(_amount); | ||
IBurnableERC20(token).burnFrom(_from, _amount); | ||
|
||
emit Burn(msg.sender, _from, _amount); | ||
} | ||
|
||
/** | ||
* @dev ERC677 callback for burning tokens atomically. | ||
* @param _from tokens sender, should correspond to one of the minting addresses. | ||
* @param _amount amount of sent/burnt tokens. | ||
* @param _data extra data, not used. | ||
*/ | ||
function onTokenTransfer(address _from, uint256 _amount, bytes calldata _data) external override returns (bool) { | ||
require(msg.sender == address(token), "BaseMinter: not a token"); | ||
require(isMinter[_from], "BaseMinter: not a burner"); | ||
|
||
_beforeBurn(_amount); | ||
IBurnableERC20(token).burn(_amount); | ||
|
||
emit Burn(_from, _from, _amount); | ||
|
||
return true; | ||
} | ||
|
||
function _beforeMint(uint256 _amount) internal virtual; | ||
|
||
function _beforeBurn(uint256 _amount) internal virtual; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,145 @@ | ||
// SPDX-License-Identifier: CC0-1.0 | ||
|
||
pragma solidity 0.8.15; | ||
|
||
import "./BaseMinter.sol"; | ||
|
||
/** | ||
* @title DebtMinter | ||
* BOB minting/burning middleware for generic debt-minting use-cases. | ||
*/ | ||
contract DebtMinter is BaseMinter { | ||
struct Parameters { | ||
uint104 maxDebtLimit; // max possible debt limit | ||
uint104 minDebtLimit; // min possible debt limit | ||
uint48 raiseDelay; // min delay between debt limit raises | ||
uint96 raise; // debt limit raising step | ||
address treasury; // receiver of repaid debt surplus | ||
} | ||
|
||
struct State { | ||
uint104 debtLimit; // current debt limit, minDebtLimit <= debtLimit <= maxDebtLimit | ||
uint104 debt; // current debt value | ||
uint48 lastRaise; // timestamp of last debt limit raise | ||
} | ||
|
||
Parameters internal parameters; | ||
State internal state; | ||
|
||
event UpdateDebt(uint104 debt, uint104 debtLimit); | ||
|
||
constructor( | ||
address _token, | ||
uint104 _maxDebtLimit, | ||
uint104 _minDebtLimit, | ||
uint48 _raiseDelay, | ||
uint96 _raise, | ||
address _treasury | ||
) | ||
BaseMinter(_token) | ||
{ | ||
require(_minDebtLimit + uint104(_raise) <= _maxDebtLimit, "DebtMinter: invalid raise"); | ||
parameters = Parameters(_maxDebtLimit, _minDebtLimit, _raiseDelay, _raise, _treasury); | ||
state = State(_minDebtLimit, 0, uint48(block.timestamp)); | ||
} | ||
|
||
function getState() external view returns (State memory) { | ||
return state; | ||
} | ||
|
||
function getParameters() external view returns (Parameters memory) { | ||
return parameters; | ||
} | ||
|
||
/** | ||
* @dev Tells remaining mint amount subject to immediate debt limit. | ||
* @return available mint amount. | ||
*/ | ||
function maxDebtIncrease() external view returns (uint256) { | ||
Parameters memory p = parameters; | ||
State memory s = state; | ||
_updateDebtLimit(p, s); | ||
return s.debtLimit - s.debt; | ||
} | ||
|
||
/** | ||
* @dev Updates limit configuration. | ||
* Callable only by the contract owner. | ||
* @param _params new parameters to apply. | ||
*/ | ||
function updateParameters(Parameters calldata _params) external onlyOwner { | ||
require(_params.minDebtLimit + uint104(_params.raise) <= _params.maxDebtLimit, "DebtMinter: invalid raise"); | ||
parameters = _params; | ||
|
||
State memory s = state; | ||
_updateDebtLimit(_params, s); | ||
state = s; | ||
|
||
emit UpdateDebt(s.debt, s.debtLimit); | ||
} | ||
|
||
/** | ||
* @dev Internal function for adjusting debt limits on tokens mint. | ||
* @param _amount amount of minted tokens. | ||
*/ | ||
function _beforeMint(uint256 _amount) internal override { | ||
Parameters memory p = parameters; | ||
State memory s = state; | ||
|
||
_updateDebtLimit(p, s); | ||
uint256 newDebt = uint256(s.debt) + _amount; | ||
require(newDebt <= s.debtLimit, "DebtMinter: exceeds debt limit"); | ||
s.debt = uint104(newDebt); | ||
|
||
state = s; | ||
|
||
emit UpdateDebt(s.debt, s.debtLimit); | ||
} | ||
|
||
/** | ||
* @dev Internal function for adjusting debt limits on tokens burn. | ||
* @param _amount amount of burnt tokens. | ||
*/ | ||
function _beforeBurn(uint256 _amount) internal override { | ||
Parameters memory p = parameters; | ||
State memory s = state; | ||
|
||
unchecked { | ||
if (_amount <= s.debt) { | ||
s.debt -= uint104(_amount); | ||
} else { | ||
IMintableERC20(token).mint(p.treasury, _amount - s.debt); | ||
s.debt = 0; | ||
} | ||
} | ||
_updateDebtLimit(p, s); | ||
state = s; | ||
|
||
emit UpdateDebt(s.debt, s.debtLimit); | ||
} | ||
|
||
/** | ||
* @dev Internal function for recalculating immediate debt limit. | ||
*/ | ||
function _updateDebtLimit(Parameters memory p, State memory s) internal view { | ||
if (s.debt >= p.maxDebtLimit) { | ||
s.debtLimit = s.debt; | ||
} else { | ||
uint104 newDebtLimit = s.debt + p.raise; | ||
if (newDebtLimit < p.minDebtLimit) { | ||
s.debtLimit = p.minDebtLimit; | ||
return; | ||
} | ||
|
||
if (newDebtLimit > p.maxDebtLimit) { | ||
newDebtLimit = p.maxDebtLimit; | ||
} | ||
if (newDebtLimit <= s.debtLimit) { | ||
s.debtLimit = newDebtLimit; | ||
} else if (s.lastRaise + p.raiseDelay < block.timestamp) { | ||
s.debtLimit = newDebtLimit; | ||
s.lastRaise = uint48(block.timestamp); | ||
} | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
// SPDX-License-Identifier: CC0-1.0 | ||
|
||
pragma solidity 0.8.15; | ||
|
||
import "../interfaces/IMintableERC20.sol"; | ||
|
||
/** | ||
* @title FaucetMinter | ||
* Simplest contract for faucet minting. | ||
*/ | ||
contract FaucetMinter is IMintableERC20 { | ||
address public immutable token; | ||
uint256 public immutable limit; | ||
|
||
event Mint(address minter, address to, uint256 amount); | ||
|
||
constructor(address _token, uint256 _limit) { | ||
token = _token; | ||
limit = _limit; | ||
} | ||
|
||
/** | ||
* @dev Mints the specified amount of tokens. | ||
* This contract should have minting permissions assigned to it in the token contract. | ||
* @param _to address of the tokens receiver. | ||
* @param _amount amount of tokens to mint. | ||
*/ | ||
function mint(address _to, uint256 _amount) external override { | ||
require(_amount <= limit, "FaucetMinter: too much"); | ||
|
||
IMintableERC20(token).mint(_to, _amount); | ||
|
||
emit Mint(msg.sender, _to, _amount); | ||
} | ||
} |
Oops, something went wrong.