Skip to content

Commit

Permalink
feat: allow burning more than minting in token controller
Browse files Browse the repository at this point in the history
  • Loading branch information
GitGuru7 committed Feb 20, 2024
1 parent 52c53b9 commit f333995
Show file tree
Hide file tree
Showing 3 changed files with 270 additions and 11 deletions.
11 changes: 6 additions & 5 deletions contracts/Token/MultichainToken.sol
Original file line number Diff line number Diff line change
Expand Up @@ -3,21 +3,22 @@ pragma solidity 0.8.13;

import { ERC20 } from "@openzeppelin/contracts/token/ERC20/ERC20.sol";

import { TokenController } from "./utils/TokenController.sol";
import { MultichainTokenController } from "./utils/MultichainTokenController.sol";

/**
* @title MultichainToken
* @author Venus
* @notice MultichainToken contract serves as a customized ERC-20 token with additional minting and burning functionality.
* It also incorporates access control features provided by the "TokenController" contract to ensure proper governance and restrictions on minting and burning operations.
* It also incorporates access control features provided by the "MultichainTokenController" contract to ensure proper governance and
* restrictions on minting and burning operations.
*/

contract MultichainToken is ERC20, TokenController {
contract MultichainToken is ERC20, MultichainTokenController {
constructor(
address accessControlManager_,
string memory name_,
string memory symbol_
) ERC20(name_, symbol_) TokenController(accessControlManager_) {}
) ERC20(name_, symbol_) MultichainTokenController(accessControlManager_) {}

/**
* @notice Creates `amount_` tokens and assigns them to `account_`, increasing
Expand All @@ -30,7 +31,7 @@ contract MultichainToken is ERC20, TokenController {
*/
function mint(address account_, uint256 amount_) external whenNotPaused {
_ensureAllowed("mint(address,uint256)");
_isEligibleToMint(msg.sender, account_, amount_);
_isEligibleToMint(msg.sender, amount_);
_mint(account_, amount_);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,23 +2,23 @@
pragma solidity 0.8.13;

import { IMultichainToken } from "./../interfaces/IMultichainToken.sol";
import { TokenController } from "./utils/TokenController.sol";
import { MultichainTokenController } from "./utils/MultichainTokenController.sol";
import { ensureNonzeroAddress } from "@venusprotocol/solidity-utilities/contracts/validators.sol";

/**
* @title TokenControllerSrc
* @title TokenBridgeController
* @author Venus
* @notice TokenControllerSrc contract serves as a intermidiary contract between bridge and token. It controls the mint and burn operation via bridge contract.
* @notice TokenBridgeController contract serves as a intermidiary contract between bridge and token. It controls the mint and burn operation via bridge contract.
* It also incorporates access control features provided by the "TokenController" contract to ensure proper governance and restrictions on minting and burning operations.
*/

contract TokenControllerSrc is TokenController {
contract TokenBridgeController is MultichainTokenController {
/**
* @notice Address of the token which is controlled by this contract.
*/
IMultichainToken public immutable INNER_TOKEN;

constructor(address accessControlManager_, address innerToken_) TokenController(accessControlManager_) {
constructor(address accessControlManager_, address innerToken_) MultichainTokenController(accessControlManager_) {
ensureNonzeroAddress(innerToken_);
INNER_TOKEN = IMultichainToken(innerToken_);
}
Expand All @@ -35,7 +35,7 @@ contract TokenControllerSrc is TokenController {
function mint(address account_, uint256 amount_) external whenNotPaused {
_ensureAllowed("mint(address,uint256)");
_beforeTokenTransfer(msg.sender, account_);
_isEligibleToMint(msg.sender, account_, amount_);
_isEligibleToMint(msg.sender, amount_);
INNER_TOKEN.mint(account_, amount_);
}

Expand Down
258 changes: 258 additions & 0 deletions contracts/Token/utils/MultichainTokenController.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,258 @@
// SPDX-License-Identifier: BSD-3-Clause
pragma solidity 0.8.13;
import { IAccessControlManagerV8 } from "@venusprotocol/governance-contracts/contracts/Governance/IAccessControlManagerV8.sol";
import { Pausable } from "@openzeppelin/contracts/security/Pausable.sol";
import { Ownable } from "@openzeppelin/contracts/access/Ownable.sol";
import { ensureNonzeroAddress } from "@venusprotocol/solidity-utilities/contracts/validators.sol";

/**
* @title MultichainTokenController
* @author Venus
* @notice MultichainTokenController contract acts as a governance and access control mechanism,
* allowing the owner to manage minting restrictions and blacklist certain addresses to maintain control and security within the token ecosystem.
* It provides a flexible framework for token-related operations.
*/

contract MultichainTokenController is Ownable, Pausable {
/**
* @notice Access control manager contract address.
*/
address public accessControlManager;
/**
* @notice A Mapping used to keep track of the blacklist status of addresses.
*/
mapping(address => bool) internal _blacklist;
/**
* @notice A mapping is used to keep track of the maximum amount a minter is permitted to mint.
*/
mapping(address => uint256) public minterToCap;
/**
* @notice A Mapping used to keep track of the amount i.e already minted by minter.
*/
mapping(address => uint256) public minterToMintedAmount;

/**
* @notice Emitted when the blacklist status of a user is updated.
*/
event BlacklistUpdated(address indexed user, bool value);
/**
* @notice Emitted when the minting limit for a minter is increased.
*/
event MintLimitIncreased(address indexed minter, uint256 newLimit);
/**
* @notice Emitted when the minting limit for a minter is decreased.
*/
event MintLimitDecreased(address indexed minter, uint256 newLimit);
/**
* @notice Emitted when the minting cap for a minter is changed.
*/
event MintCapChanged(address indexed minter, uint256 amount);
/**
* @notice Emitted when the address of the access control manager of the contract is updated.
*/
event NewAccessControlManager(address indexed oldAccessControlManager, address indexed newAccessControlManager);
/**
* @notice Emitted when all minted tokens are migrated from one minter to another.
*/
event MintedTokensMigrated(address indexed source, address indexed destination);

/**
* @notice This error is used to indicate that the minting limit has been exceeded. It is typically thrown when a minting operation would surpass the defined cap.
*/
error MintLimitExceed();
/**
* @notice This error is used to indicate that `mint` `burn` and `transfer` actions are not allowed for the user address.
*/
error AccountBlacklisted(address user);
/**
* @notice This error is used to indicate that sender is not allowed to perform this action.
*/
error Unauthorized();
/**
* @notice This error is used to indicate that the new cap is greater than the previously minted tokens for the minter.
*/
error NewCapNotGreaterThanMintedTokens();
/**
* @notice This error is used to indicate that the addresses must be different.
*/
error AddressesMustDiffer();
/**
* @notice This error is used to indicate that the minter did not mint the required amount of tokens.
*/
error MintedAmountExceed();

/**
* @param accessControlManager_ Address of access control manager contract.
* @custom:error ZeroAddressNotAllowed is thrown when accessControlManager contract address is zero.
*/
constructor(address accessControlManager_) {
ensureNonzeroAddress(accessControlManager_);
accessControlManager = accessControlManager_;
}

/**
* @notice Pauses Token
* @custom:access Controlled by AccessControlManager.
*/
function pause() external {
_ensureAllowed("pause()");
_pause();
}

/**
* @notice Resumes Token
* @custom:access Controlled by AccessControlManager.
*/
function unpause() external {
_ensureAllowed("unpause()");
_unpause();
}

/**
* @notice Function to update blacklist.
* @param user_ User address to be affected.
* @param value_ Boolean to toggle value.
* @custom:access Controlled by AccessControlManager.
* @custom:event Emits BlacklistUpdated event.
*/
function updateBlacklist(address user_, bool value_) external {
_ensureAllowed("updateBlacklist(address,bool)");
_blacklist[user_] = value_;
emit BlacklistUpdated(user_, value_);
}

/**
* @notice Sets the minting cap for minter.
* @param minter_ Minter address.
* @param amount_ Cap for the minter.
* @custom:access Controlled by AccessControlManager.
* @custom:event Emits MintCapChanged.
*/
function setMintCap(address minter_, uint256 amount_) external {
_ensureAllowed("setMintCap(address,uint256)");

if (amount_ < minterToMintedAmount[minter_]) {
revert NewCapNotGreaterThanMintedTokens();
}

minterToCap[minter_] = amount_;
emit MintCapChanged(minter_, amount_);
}

/**
* @notice Sets the address of the access control manager of this contract.
* @dev Admin function to set the access control address.
* @param newAccessControlAddress_ New address for the access control.
* @custom:access Only owner.
* @custom:event Emits NewAccessControlManager.
* @custom:error ZeroAddressNotAllowed is thrown when newAccessControlAddress_ contract address is zero.
*/
function setAccessControlManager(address newAccessControlAddress_) external onlyOwner {
ensureNonzeroAddress(newAccessControlAddress_);
emit NewAccessControlManager(accessControlManager, newAccessControlAddress_);
accessControlManager = newAccessControlAddress_;
}

/**
* @notice Migrates all minted tokens from one minter to another. This function is useful when we want to permanent take down a bridge.
* @param source_ Minter address to migrate tokens from.
* @param destination_ Minter address to migrate tokens to.
* @custom:access Controlled by AccessControlManager.
* @custom:error MintLimitExceed is thrown when the minting limit exceeds the cap after migration.
* @custom:error AddressesMustDiffer is thrown when the source_ and destination_ addresses are the same.
* @custom:event Emits MintLimitIncreased and MintLimitDecreased events for 'source' and 'destination'.
* @custom:event Emits MintedTokensMigrated.
*/
function migrateMinterTokens(address source_, address destination_) external {
_ensureAllowed("migrateMinterTokens(address,address)");

if (source_ == destination_) {
revert AddressesMustDiffer();
}

uint256 sourceCap = minterToCap[source_];
uint256 destinationCap = minterToCap[destination_];

uint256 sourceMinted = minterToMintedAmount[source_];
uint256 destinationMinted = minterToMintedAmount[destination_];
uint256 newDestinationMinted = destinationMinted + sourceMinted;

if (newDestinationMinted > destinationCap) {
revert MintLimitExceed();
}

minterToMintedAmount[source_] = 0;
minterToMintedAmount[destination_] = newDestinationMinted;
uint256 availableLimit;
unchecked {
availableLimit = destinationCap - newDestinationMinted;
}

emit MintLimitDecreased(destination_, availableLimit);
emit MintLimitIncreased(source_, sourceCap);
emit MintedTokensMigrated(source_, destination_);
}

/**
* @notice Returns the blacklist status of the address.
* @param user_ Address of user to check blacklist status.
* @return bool status of blacklist.
*/
function isBlackListed(address user_) external view returns (bool) {
return _blacklist[user_];
}

/**
* @dev Checks the minter cap and eligibility of receiver to receive tokens.
* @param from_ Minter address.
* @param amount_ Amount to be mint.
* @custom:error MintLimitExceed is thrown when minting limit exceeds the cap.
* @custom:event Emits MintLimitDecreased with minter address and available limits.
*/
function _isEligibleToMint(address from_, uint256 amount_) internal {
uint256 mintingCap = minterToCap[from_];
uint256 totalMintedOld = minterToMintedAmount[from_];
uint256 totalMintedNew = totalMintedOld + amount_;

if (totalMintedNew > mintingCap) {
revert MintLimitExceed();
}
minterToMintedAmount[from_] = totalMintedNew;
uint256 availableLimit;
unchecked {
availableLimit = mintingCap - totalMintedNew;
}
emit MintLimitDecreased(from_, availableLimit);
}

/**
* @dev This is post hook of burn function, increases minting limit of the minter.
* @param from_ Minter address.
* @param amount_ Amount burned.
* @custom:error MintedAmountExceed is thrown when `amount_` is greater than the tokens minted by `from_`.
* @custom:event Emits MintLimitIncreased with minter address and availabe limit.
*/
function _increaseMintLimit(address from_, uint256 amount_) internal {
uint256 totalMintedOld = minterToMintedAmount[msg.sender];
uint256 amountToIncrease = totalMintedOld >= amount_ ? amount_ : totalMintedOld;

uint256 totalMintedNew;
unchecked {
totalMintedNew = totalMintedOld - amountToIncrease;
}
minterToMintedAmount[from_] = totalMintedNew;
uint256 availableLimit = minterToCap[from_] - totalMintedNew;
emit MintLimitIncreased(from_, availableLimit);
}

/**
* @dev Checks the caller is allowed to call the specified fuction.
* @param functionSig_ Function signatureon which access is to be checked.
* @custom:error Unauthorized, thrown when unauthorised user try to access function.
*/
function _ensureAllowed(string memory functionSig_) internal view {
if (!IAccessControlManagerV8(accessControlManager).isAllowedToCall(msg.sender, functionSig_)) {
revert Unauthorized();
}
}
}

0 comments on commit f333995

Please sign in to comment.