Skip to content

Commit

Permalink
Merge the develop branch into the master branch, v1.1.0-rc1
Browse files Browse the repository at this point in the history
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
akolotov committed Mar 29, 2023
2 parents b69d3d5 + 97ee6aa commit 0072688
Show file tree
Hide file tree
Showing 14 changed files with 1,492 additions and 1 deletion.
1 change: 1 addition & 0 deletions src/interfaces/IBurnableERC20.sol
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,5 @@ pragma solidity 0.8.15;

interface IBurnableERC20 {
function burn(uint256 amount) external;
function burnFrom(address user, uint256 amount) external;
}
58 changes: 58 additions & 0 deletions src/minters/BalancedMinter.sol
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);
}
}
}
104 changes: 104 additions & 0 deletions src/minters/BaseMinter.sol
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;
}
145 changes: 145 additions & 0 deletions src/minters/DebtMinter.sol
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);
}
}
}
}
35 changes: 35 additions & 0 deletions src/minters/FaucetMinter.sol
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);
}
}
Loading

0 comments on commit 0072688

Please sign in to comment.