Description:
Multi-signature wallet contract requiring multiple confirmations for transaction execution.
Blockchain: Ethereum
Source Code: View Code On The Blockchain
Solidity Source Code:
{{
"language": "Solidity",
"sources": {
"contracts/stablecoins/BAUSD.sol": {
"content": "// SPDX-License-Identifier: MIT
pragma solidity ^0.8.17;
import { PaxosTokenV2 } from "./../PaxosTokenV2.sol";
/**
* @title BAUSD Smart contract
* @dev This contract is a {PaxosTokenV2-PaxosTokenV2} ERC20 token.
* This is just a test token, it's only used for test purposes by Blockaid
* Do not interact with this token
* @custom:security-contact hamzab@blockaid.co
*/
contract BAUSD is PaxosTokenV2 {
/*
* @dev Returns the name of the token.
*/
function name() public view virtual override returns (string memory) {
return "Blockaid USD";
}
/*
* @dev Returns the symbol of the token.
*/
function symbol() public view virtual override returns (string memory) {
return "BAUSD";
}
/*
* @dev Returns the decimal count of the token.
*/
function decimals() public view virtual override returns (uint8) {
return 6;
}
}"
},
"contracts/PaxosTokenV2.sol": {
"content": "// SPDX-License-Identifier: MIT
pragma solidity ^0.8.17;
import { BaseStorage } from "./BaseStorage.sol";
import { SupplyControl } from "./SupplyControl.sol";
import { EIP2612 } from "./lib/EIP2612.sol";
import { EIP3009 } from "./lib/EIP3009.sol";
import { EIP712 } from "./lib/EIP712.sol";
import { AccessControlDefaultAdminRulesUpgradeable } from "./openzeppelin/contracts-upgradeable/access/AccessControlDefaultAdminRulesUpgradeable.sol";
/**
* @title PaxosTokenV2
* @dev this contract is a Pausable ERC20 token with Burn and Mint
* controlled by a `SupplyControl` contract.
* NOTE: The storage defined here will actually be held in the Proxy
* contract and all calls to this contract should be made through
* the proxy, including admin actions done as owner or supplyController.
* Any call to transfer against this contract should fail
* with insufficient funds since no tokens will be issued there.
* @custom:security-contact smart-contract-security@paxos.com
*/
contract PaxosTokenV2 is BaseStorage, EIP2612, EIP3009, AccessControlDefaultAdminRulesUpgradeable {
/**
* EVENTS
*/
// ERC20 BASIC EVENTS
event Transfer(address indexed from, address indexed to, uint256 value);
// ERC20 EVENTS
event Approval(address indexed owner, address indexed spender, uint256 value);
// PAUSABLE EVENTS
event Pause();
event Unpause();
// ASSET PROTECTION EVENTS
event FrozenAddressWiped(address indexed addr);
event FreezeAddress(address indexed addr);
event UnfreezeAddress(address indexed addr);
// SUPPLY CONTROL EVENTS
event SupplyIncreased(address indexed to, uint256 value);
event SupplyDecreased(address indexed from, uint256 value);
event SupplyControlSet(address supplyControlAddress);
// Event when sanction address changes.
event SanctionedAddressListUpdate(address newSanctionedAddress);
/**
* ERRORS
*/
error OnlySupplyController();
error InsufficientFunds();
error AddressNotFrozen();
error ZeroValue();
error AlreadyPaused();
error AlreadyUnPaused();
error InsufficientAllowance();
error SupplyControllerUnchanged();
error OnlySupplyControllerOrOwner();
/// @custom:oz-upgrades-unsafe-allow constructor
constructor() {
_disableInitializers();
}
/**
* External Functions
*/
/**
* @notice Reclaim all tokens at the contract address
* @dev Transfers the tokens this contract holds, to the owner of smart contract.
* Note: This is not affected by freeze constraints.
*/
function reclaimToken() external onlyRole(DEFAULT_ADMIN_ROLE) {
uint256 _balance = balances[address(this)];
address owner = owner();
balances[address(this)] = 0;
balances[owner] += _balance;
emit Transfer(address(this), owner, _balance);
}
/**
* @dev Update the supply control contract which controls minting and burning for this token.
* @param supplyControlAddress Supply control contract address
*/
function setSupplyControl(
address supplyControlAddress
) external onlyRole(DEFAULT_ADMIN_ROLE) isNonZeroAddress(supplyControlAddress) {
supplyControl = SupplyControl(supplyControlAddress);
emit SupplyControlSet(supplyControlAddress);
}
/**
* @notice Return the freeze status of an address.
* @dev Check if whether the address is currently frozen.
* @param addr The address to check if frozen.
* @return A bool representing whether the given address is frozen.
*/
function isFrozen(address addr) external view returns (bool) {
return _isAddrFrozen(addr);
}
/**
* Public Functions
*/
/**
* @notice Initialize the contract.
* @dev Wrapper around {_initialize}. This is useful to get the version before
* it is updated by {reinitializer}.
* @param initialDelay Initial delay for changing the owner
* @param initialOwner Address of the initial owner
* @param pauser Address of the pauser
* @param assetProtector Address of the asset protector
*/
function initialize(uint48 initialDelay, address initialOwner, address pauser, address assetProtector) public {
uint64 pastVersion = _getInitializedVersion();
_initialize(pastVersion, initialDelay, initialOwner, pauser, assetProtector);
}
/**
* @notice Initialize the domain separator for the contract.
* @dev This is public to allow for updates to the domain separator if the name is updated.
*/
function initializeDomainSeparator() public {
_initializeDomainSeparator();
}
/**
* @notice Returns the total supply of the token.
* @return An uint256 representing the total supply of the token.
*/
function totalSupply() public view returns (uint256) {
return totalSupply_;
}
/**
* @notice Execute a transfer
* @dev Transfer token to the specified address from msg.sender
* @param to The address to transfer to
* @param value The amount to be transferred
* @return True if successful
*/
function transfer(address to, uint256 value) public whenNotPaused returns (bool) {
_transfer(msg.sender, to, value);
return true;
}
/**
* @notice Gets the balance of the specified address
* @param addr The address to query the the balance of
* @return An uint256 representing the amount owned by the passed address
*/
function balanceOf(address addr) public view returns (uint256) {
return balances[addr];
}
/**
* @notice Transfer tokens from one address to another
* @param from address The address which you want to send tokens from
* @param to address The address which you want to transfer to
* @param value uint256 the amount of tokens to be transferred
* @return True if successful
*/
function transferFrom(address from, address to, uint256 value) public whenNotPaused returns (bool) {
if (_isAddrFrozen(msg.sender)) revert AddressFrozen();
_transferFromAllowance(from, to, value);
return true;
}
/**
* @notice Transfer tokens from one set of addresses to another in a single transaction
* @param from addres[] The addresses which you want to send tokens from
* @param to address[] The addresses which you want to transfer to
* @param value uint256[] The amounts of tokens to be transferred
* @return True if successful
*/
function transferFromBatch(
address[] calldata from,
address[] calldata to,
uint256[] calldata value
) public whenNotPaused returns (bool) {
// Validate length of each parameter with "_from" argument to make sure lengths of all input arguments are the same.
if (to.length != from.length || value.length != from.length) revert ArgumentLengthMismatch();
if (_isAddrFrozen(msg.sender)) revert AddressFrozen();
for (uint16 i = 0; i < from.length; i++) {
_transferFromAllowance(from[i], to[i], value[i]);
}
return true;
}
/**
* @notice Set allowance of spender to spend tokens on behalf of msg.sender
* @dev Approve the passed address to spend the specified amount of tokens on behalf of msg.sender.
* Beware that changing an allowance with this method brings the risk that someone may use both the old
* and the new allowance by unfortunate transaction ordering. One possible solution to mitigate this
* race condition is to first reduce the spender's allowance to 0 and set the desired value afterwards:
* https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
* @param spender The address which will spend the funds
* @param value The amount of tokens to be spent
* @return True if successful
*/
function approve(address spender, uint256 value) public whenNotPaused isNonZeroAddress(spender) returns (bool) {
if (_isAddrFrozen(spender) || _isAddrFrozen(msg.sender)) revert AddressFrozen();
_approve(msg.sender, spender, value);
return true;
}
/**
* @notice Increase the allowance of spender to spend tokens on behalf of msg.sender
* @dev Increase the amount of tokens that an owner allowed to a spender.
* To increment allowed value is better to use this function to avoid 2 calls (and wait until the first transaction
* is mined) instead of approve.
* @param spender The address which will spend the funds
* @param addedValue The amount of tokens to increase the allowance by
* @return True if successful
*/
function increaseApproval(address spender, uint256 addedValue) public whenNotPaused returns (bool) {
if (_isAddrFrozen(spender) || _isAddrFrozen(msg.sender)) revert AddressFrozen();
if (addedValue == 0) revert ZeroValue();
allowed[msg.sender][spender] += addedValue;
emit Approval(msg.sender, spender, allowed[msg.sender][spender]);
return true;
}
/**
* @notice Decrease the allowance of spender to spend tokens on behalf of msg.sender
* @dev Decrease the amount of tokens that an owner allowed to a spender.
* To decrement allowed value is better to use this function to avoid 2 calls (and wait until the first transaction
* is mined) instead of approve.
* @param spender The address which will spend the funds
* @param subtractedValue The amount of tokens to decrease the allowance by
* @return True if successful
*/
function decreaseApproval(address spender, uint256 subtractedValue) public whenNotPaused returns (bool) {
if (_isAddrFrozen(spender) || _isAddrFrozen(msg.sender)) revert AddressFrozen();
if (subtractedValue == 0) revert ZeroValue();
if (subtractedValue > allowed[msg.sender][spender]) {
allowed[msg.sender][spender] = 0;
} else {
allowed[msg.sender][spender] -= subtractedValue;
}
emit Approval(msg.sender, spender, allowed[msg.sender][spender]);
return true;
}
/**
* @dev Get the amount of token allowance that an owner allowed to a spender
* @param owner address The address which owns the funds
* @param spender address The address which will spend the funds
* @return A uint256 specifying the amount of tokens still available for the spender
*/
function allowance(address owner, address spender) public view returns (uint256) {
return allowed[owner][spender];
}
/**
* @notice Pause the contract
* @dev called by the owner to pause, triggers stopped state
*/
function pause() public onlyRole(PAUSE_ROLE) {
if (paused) revert AlreadyPaused();
paused = true;
emit Pause();
}
/**
* @notice Unpause the contract
* @dev called by the owner to unpause, returns to normal state
*/
function unpause() public onlyRole(PAUSE_ROLE) {
if (!paused) revert AlreadyUnPaused();
paused = false;
emit Unpause();
}
// ASSET PROTECTION FUNCTIONALITY
/**
* @notice Wipe the token balance of a frozen address
* @dev Wipes the balance of a frozen address, and burns the tokens
* @param addr The new frozen address to wipe
*/
function wipeFrozenAddress(address addr) public onlyRole(ASSET_PROTECTION_ROLE) {
if (!_isAddrFrozen(addr)) revert AddressNotFrozen();
uint256 balance = balances[addr];
balances[addr] = 0;
totalSupply_ -= balance;
emit FrozenAddressWiped(addr);
emit SupplyDecreased(addr, balance);
emit Transfer(addr, address(0), balance);
}
/**
* @dev Freezes an address balance from being transferred.
* @param addr The address to freeze.
*/
function freeze(address addr) public onlyRole(ASSET_PROTECTION_ROLE) {
_freeze(addr);
}
/**
* @dev Freezes all addresses balance from being transferred.
* @param addresses The addresses to freeze.
*/
function freezeBatch(address[] calldata addresses) public onlyRole(ASSET_PROTECTION_ROLE) {
for (uint256 i = 0; i < addresses.length; ) {
_freeze(addresses[i]);
unchecked {
++i;
}
}
}
/**
* @dev Unfreezes an address balance allowing transfer.
* @param addr The new address to unfreeze.
*/
function unfreeze(address addr) public onlyRole(ASSET_PROTECTION_ROLE) {
_unfreeze(addr);
}
/**
* @dev Unfreezes all addresses balance from being transferred.
* @param addresses The addresses to unfreeze.
*/
function unfreezeBatch(address[] calldata addresses) public onlyRole(ASSET_PROTECTION_ROLE) {
for (uint256 i = 0; i < addresses.length; ) {
_unfreeze(addresses[i]);
unchecked {
++i;
}
}
}
/**
* @notice Increases the total supply by minting the specified number of tokens to the supply controller account.
* Function is marked virtual to aid in testing, but is never overridden on the actual token.
* @param value The number of tokens to add
* @param mintToAddress Address to mint tokens to.
* @return success A boolean that indicates if the operation was successful
*/
function increaseSupplyToAddress(uint256 value, address mintToAddress) public virtual returns (bool success) {
require(!_isAddrFrozen(mintToAddress), "mintToAddress frozen");
supplyControl.canMintToAddress(mintToAddress, value, msg.sender);
totalSupply_ += value;
balances[mintToAddress] += value;
emit SupplyIncreased(mintToAddress, value);
emit Transfer(address(0), mintToAddress, value);
return true;
}
/**
* @dev Wrapper around 'increaseSupplyToAddress' to extend the API
* @param value The number of tokens to add.
* @return success A boolean that indicates if the operation was successful
*/
function increaseSupply(uint256 value) public returns (bool success) {
return increaseSupplyToAddress(value, msg.sender);
}
/**
* @dev Wrapper around `increaseSupplyToAddress` to extend the API
* @param account Address to mint tokens to
* @param amount The number of tokens to add
*/
function mint(address account, uint256 amount) public {
increaseSupplyToAddress(amount, account);
}
/**
* @notice Decreases the total supply by burning the specified number of tokens. Can only be called by a
* supply controller. Function is marked virtual to aid in testing, but is never overridden on the actual token.
* @param value The number of tokens to remove
* @param burnFromAddress Address to burn tokens from.
* @return success A boolean that indicates if the operation was successful
*/
function decreaseSupplyFromAddress(uint256 value, address burnFromAddress) public virtual returns (bool success) {
require(!_isAddrFrozen(burnFromAddress), "burnFromAddress frozen");
supplyControl.canBurnFromAddress(burnFromAddress, msg.sender);
if (value > balances[burnFromAddress]) revert InsufficientFunds();
balances[burnFromAddress] -= value;
totalSupply_ -= value;
emit SupplyDecreased(burnFromAddress, value);
emit Transfer(burnFromAddress, address(0), value);
return true;
}
/**
* @dev Wrapper around 'decreaseSupplyFromAddress' to extend the API
* @param value The number of tokens to remove.
* @return success A boolean that indicates if the operation was successful
*/
function decreaseSupply(uint256 value) public returns (bool success) {
return decreaseSupplyFromAddress(value, msg.sender);
}
/**
* @dev Wrapper around `decreaseSupply` to extend the API
* @param amount The number of tokens to remove
*/
function burn(uint256 amount) public {
decreaseSupply(amount);
}
/**
* Internal Functions
*/
/**
* @dev See {PaxosBaseAbstract-_isPaused}
*/
function _isPaused() internal view override returns (bool) {
return paused;
}
/**
* @dev See {PaxosBaseAbstract-_isAddrFrozen}
*/
function _isAddrFrozen(address addr) internal view override returns (bool) {
return frozen[addr];
}
/**
* @dev Internal function to transfer balances from => to.
* Internal to the contract - see transferFrom and transferFromBatch.
* @param from address The address which you want to send tokens from
* @param to address The address which you want to transfer to
* @param value uint256 the amount of tokens to be transferred
*/
function _transferFromAllowance(address from, address to, uint256 value) internal {
if (value > allowed[from][msg.sender]) revert InsufficientAllowance();
_transfer(from, to, value);
allowed[from][msg.sender] -= value;
}
/**
* @dev See {PaxosBaseAbstract-_approve}
*/
function _approve(address owner, address spender, uint256 value) internal override {
allowed[owner][spender] = value;
emit Approval(owner, spender, value);
}
/**
* @dev See {PaxosBaseAbstract-_transfer}
*/
function _transfer(address from, address to, uint256 value) internal override isNonZeroAddress(to) {
if (_isAddrFrozen(to) || _isAddrFrozen(from)) revert AddressFrozen();
if (value > balances[from]) revert InsufficientFunds();
balances[from] -= value;
balances[to] += value;
emit Transfer(from, to, value);
}
/**
* Private Functions
*/
/**
* @dev Called on deployment, can only be called once. If the contract is ever upgraded,
* the version in reinitializer will be incremented and additional initialization logic
* can be added for the new version.
* @param pastVersion Previous contract version
* @param initialDelay Initial delay for changing the owner
* @param initialOwner Address of the initial owner
* @param pauser Address of the pauser
* @param assetProtector Address of the asset protector
*/
function _initialize(
uint64 pastVersion,
uint48 initialDelay,
address initialOwner,
address pauser,
address assetProtector
) private reinitializer(2) {
_initializeV1(pastVersion);
_initializeV2(initialDelay, initialOwner, pauser, assetProtector);
}
/**
* @dev Called on deployment to initialize V1 state. If contract already initialized,
* it returns immediately.
* @param pastVersion Previous contract version
*/
function _initializeV1(uint64 pastVersion) private {
if (pastVersion < 1 && !initializedV1) {
//Need this second condition since V1 could have used old upgrade pattern
totalSupply_ = 0;
initializedV1 = true;
}
}
/**
* @dev Called on deployment to initialize V2 state
* @param initialDelay Initial delay for changing the owner
* @param initialOwner Address of the initial owner
* @param pauser Address of the pauser
* @param assetProtector Address of the assetProtector
*/
function _initializeV2(
uint48 initialDelay,
address initialOwner,
address pauser,
address assetProtector
) private isNonZeroAddress(pauser) isNonZeroAddress(assetProtector) {
__AccessControlDefaultAdminRules_init(initialDelay, initialOwner);
_grantRole(PAUSE_ROLE, pauser);
_grantRole(ASSET_PROTECTION_ROLE, assetProtector);
_initializeDomainSeparator();
}
/**
* @dev Private function to initialize the domain separator for the contract.
*/
function _initializeDomainSeparator() private {
DOMAIN_SEPARATOR = EIP712._makeDomainSeparator(name(), "1");
}
/**
* @dev Private function to Freezes an address balance from being transferred.
* @param addr The addresses to freeze.
*/
function _freeze(address addr) private {
frozen[addr] = true;
emit FreezeAddress(addr);
}
/**
* @dev Private function to Unfreezes an address balance from being transferred.
* @param addr The addresses to unfreeze.
*/
function _unfreeze(address addr) private {
delete frozen[addr];
emit UnfreezeAddress(addr);
}
}
"
},
"contracts/openzeppelin/contracts-upgradeable/access/AccessControlDefaultAdminRulesUpgradeable.sol": {
"content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (access/AccessControlDefaultAdminRules.sol)
pragma solidity ^0.8.0;
import "./AccessControlUpgradeable.sol";
import "./IAccessControlDefaultAdminRulesUpgradeable.sol";
import "../utils/math/SafeCastUpgradeable.sol";
import "../interfaces/IERC5313Upgradeable.sol";
import "../proxy/utils/Initializable.sol";
/**
* @dev Extension of {AccessControl} that allows specifying special rules to manage
* the `DEFAULT_ADMIN_ROLE` holder, which is a sensitive role with special permissions
* over other roles that may potentially have privileged rights in the system.
*
* If a specific role doesn't have an admin role assigned, the holder of the
* `DEFAULT_ADMIN_ROLE` will have the ability to grant it and revoke it.
*
* This contract implements the following risk mitigations on top of {AccessControl}:
*
* * Only one account holds the `DEFAULT_ADMIN_ROLE` since deployment until it's potentially renounced.
* * Enforces a 2-step process to transfer the `DEFAULT_ADMIN_ROLE` to another account.
* * Enforces a configurable delay between the two steps, with the ability to cancel before the transfer is accepted.
* * The delay can be changed by scheduling, see {changeDefaultAdminDelay}.
* * It is not possible to use another role to manage the `DEFAULT_ADMIN_ROLE`.
*
* Example usage:
*
* ```solidity
* contract MyToken is AccessControlDefaultAdminRules {
* constructor() AccessControlDefaultAdminRules(
* 3 days,
* msg.sender // Explicit initial `DEFAULT_ADMIN_ROLE` holder
* ) {}
* }
* ```
*
* _Available since v4.9._
*/
abstract contract AccessControlDefaultAdminRulesUpgradeable is Initializable, IAccessControlDefaultAdminRulesUpgradeable, IERC5313Upgradeable, AccessControlUpgradeable {
// pending admin pair read/written together frequently
address private _pendingDefaultAdmin;
uint48 private _pendingDefaultAdminSchedule; // 0 == unset
uint48 private _currentDelay;
address private _currentDefaultAdmin;
// pending delay pair read/written together frequently
uint48 private _pendingDelay;
uint48 private _pendingDelaySchedule; // 0 == unset
/**
* @dev Sets the initial values for {defaultAdminDelay} and {defaultAdmin} address.
*/
function __AccessControlDefaultAdminRules_init(uint48 initialDelay, address initialDefaultAdmin) internal onlyInitializing {
__AccessControlDefaultAdminRules_init_unchained(initialDelay, initialDefaultAdmin);
}
function __AccessControlDefaultAdminRules_init_unchained(uint48 initialDelay, address initialDefaultAdmin) internal onlyInitializing {
require(initialDefaultAdmin != address(0), "AccessControl: 0 default admin");
_currentDelay = initialDelay;
_grantRole(DEFAULT_ADMIN_ROLE, initialDefaultAdmin);
}
/**
* @dev See {IERC165-supportsInterface}.
*/
function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {
return interfaceId == type(IAccessControlDefaultAdminRulesUpgradeable).interfaceId || super.supportsInterface(interfaceId);
}
/**
* @dev See {IERC5313-owner}.
*/
function owner() public view virtual returns (address) {
return defaultAdmin();
}
///
/// Override AccessControl role management
///
/**
* @dev See {AccessControl-grantRole}. Reverts for `DEFAULT_ADMIN_ROLE`.
*/
function grantRole(bytes32 role, address account) public virtual override(AccessControlUpgradeable, IAccessControlUpgradeable) {
require(role != DEFAULT_ADMIN_ROLE, "AccessControl: can't directly grant default admin role");
super.grantRole(role, account);
}
/**
* @dev See {AccessControl-revokeRole}. Reverts for `DEFAULT_ADMIN_ROLE`.
*/
function revokeRole(bytes32 role, address account) public virtual override(AccessControlUpgradeable, IAccessControlUpgradeable) {
require(role != DEFAULT_ADMIN_ROLE, "AccessControl: can't directly revoke default admin role");
super.revokeRole(role, account);
}
/**
* @dev See {AccessControl-renounceRole}.
*
* For the `DEFAULT_ADMIN_ROLE`, it only allows renouncing in two steps by first calling
* {beginDefaultAdminTransfer} to the `address(0)`, so it's required that the {pendingDefaultAdmin} schedule
* has also passed when calling this function.
*
* After its execution, it will not be possible to call `onlyRole(DEFAULT_ADMIN_ROLE)` functions.
*
* NOTE: Renouncing `DEFAULT_ADMIN_ROLE` will leave the contract without a {defaultAdmin},
* thereby disabling any functionality that is only available for it, and the possibility of reassigning a
* non-administrated role.
*/
function renounceRole(bytes32 role, address account) public virtual override(AccessControlUpgradeable, IAccessControlUpgradeable) {
if (role == DEFAULT_ADMIN_ROLE && account == defaultAdmin()) {
(address newDefaultAdmin, uint48 schedule) = pendingDefaultAdmin();
require(
newDefaultAdmin == address(0) && _isScheduleSet(schedule) && _hasSchedulePassed(schedule),
"AccessControl: only can renounce in two delayed steps"
);
delete _pendingDefaultAdminSchedule;
}
super.renounceRole(role, account);
}
/**
* @dev See {AccessControl-_grantRole}.
*
* For `DEFAULT_ADMIN_ROLE`, it only allows granting if there isn't already a {defaultAdmin} or if the
* role has been previously renounced.
*
* NOTE: Exposing this function through another mechanism may make the `DEFAULT_ADMIN_ROLE`
* assignable again. Make sure to guarantee this is the expected behavior in your implementation.
*/
function _grantRole(bytes32 role, address account) internal virtual override {
if (role == DEFAULT_ADMIN_ROLE) {
require(defaultAdmin() == address(0), "AccessControl: default admin already granted");
_currentDefaultAdmin = account;
}
super._grantRole(role, account);
}
/**
* @dev See {AccessControl-_revokeRole}.
*/
function _revokeRole(bytes32 role, address account) internal virtual override {
if (role == DEFAULT_ADMIN_ROLE && account == defaultAdmin()) {
delete _currentDefaultAdmin;
}
super._revokeRole(role, account);
}
/**
* @dev See {AccessControl-_setRoleAdmin}. Reverts for `DEFAULT_ADMIN_ROLE`.
*/
function _setRoleAdmin(bytes32 role, bytes32 adminRole) internal virtual override {
require(role != DEFAULT_ADMIN_ROLE, "AccessControl: can't violate default admin rules");
super._setRoleAdmin(role, adminRole);
}
///
/// AccessControlDefaultAdminRules accessors
///
/**
* @inheritdoc IAccessControlDefaultAdminRulesUpgradeable
*/
function defaultAdmin() public view virtual returns (address) {
return _currentDefaultAdmin;
}
/**
* @inheritdoc IAccessControlDefaultAdminRulesUpgradeable
*/
function pendingDefaultAdmin() public view virtual returns (address newAdmin, uint48 schedule) {
return (_pendingDefaultAdmin, _pendingDefaultAdminSchedule);
}
/**
* @inheritdoc IAccessControlDefaultAdminRulesUpgradeable
*/
function defaultAdminDelay() public view virtual returns (uint48) {
uint48 schedule = _pendingDelaySchedule;
return (_isScheduleSet(schedule) && _hasSchedulePassed(schedule)) ? _pendingDelay : _currentDelay;
}
/**
* @inheritdoc IAccessControlDefaultAdminRulesUpgradeable
*/
function pendingDefaultAdminDelay() public view virtual returns (uint48 newDelay, uint48 schedule) {
schedule = _pendingDelaySchedule;
return (_isScheduleSet(schedule) && !_hasSchedulePassed(schedule)) ? (_pendingDelay, schedule) : (0, 0);
}
/**
* @inheritdoc IAccessControlDefaultAdminRulesUpgradeable
*/
function defaultAdminDelayIncreaseWait() public view virtual returns (uint48) {
return 5 days;
}
///
/// AccessControlDefaultAdminRules public and internal setters for defaultAdmin/pendingDefaultAdmin
///
/**
* @inheritdoc IAccessControlDefaultAdminRulesUpgradeable
*/
function beginDefaultAdminTransfer(address newAdmin) public virtual onlyRole(DEFAULT_ADMIN_ROLE) {
_beginDefaultAdminTransfer(newAdmin);
}
/**
* @dev See {beginDefaultAdminTransfer}.
*
* Internal function without access restriction.
*/
function _beginDefaultAdminTransfer(address newAdmin) internal virtual {
uint48 newSchedule = SafeCastUpgradeable.toUint48(block.timestamp) + defaultAdminDelay();
_setPendingDefaultAdmin(newAdmin, newSchedule);
emit DefaultAdminTransferScheduled(newAdmin, newSchedule);
}
/**
* @inheritdoc IAccessControlDefaultAdminRulesUpgradeable
*/
function cancelDefaultAdminTransfer() public virtual onlyRole(DEFAULT_ADMIN_ROLE) {
_cancelDefaultAdminTransfer();
}
/**
* @dev See {cancelDefaultAdminTransfer}.
*
* Internal function without access restriction.
*/
function _cancelDefaultAdminTransfer() internal virtual {
_setPendingDefaultAdmin(address(0), 0);
}
/**
* @inheritdoc IAccessControlDefaultAdminRulesUpgradeable
*/
function acceptDefaultAdminTransfer() public virtual {
(address newDefaultAdmin, ) = pendingDefaultAdmin();
require(_msgSender() == newDefaultAdmin, "AccessControl: pending admin must accept");
_acceptDefaultAdminTransfer();
}
/**
* @dev See {acceptDefaultAdminTransfer}.
*
* Internal function without access restriction.
*/
function _acceptDefaultAdminTransfer() internal virtual {
(address newAdmin, uint48 schedule) = pendingDefaultAdmin();
require(_isScheduleSet(schedule) && _hasSchedulePassed(schedule), "AccessControl: transfer delay not passed");
_revokeRole(DEFAULT_ADMIN_ROLE, defaultAdmin());
_grantRole(DEFAULT_ADMIN_ROLE, newAdmin);
delete _pendingDefaultAdmin;
delete _pendingDefaultAdminSchedule;
}
///
/// AccessControlDefaultAdminRules public and internal setters for defaultAdminDelay/pendingDefaultAdminDelay
///
/**
* @inheritdoc IAccessControlDefaultAdminRulesUpgradeable
*/
function changeDefaultAdminDelay(uint48 newDelay) public virtual onlyRole(DEFAULT_ADMIN_ROLE) {
_changeDefaultAdminDelay(newDelay);
}
/**
* @dev See {changeDefaultAdminDelay}.
*
* Internal function without access restriction.
*/
function _changeDefaultAdminDelay(uint48 newDelay) internal virtual {
uint48 newSchedule = SafeCastUpgradeable.toUint48(block.timestamp) + _delayChangeWait(newDelay);
_setPendingDelay(newDelay, newSchedule);
emit DefaultAdminDelayChangeScheduled(newDelay, newSchedule);
}
/**
* @inheritdoc IAccessControlDefaultAdminRulesUpgradeable
*/
function rollbackDefaultAdminDelay() public virtual onlyRole(DEFAULT_ADMIN_ROLE) {
_rollbackDefaultAdminDelay();
}
/**
* @dev See {rollbackDefaultAdminDelay}.
*
* Internal function without access restriction.
*/
function _rollbackDefaultAdminDelay() internal virtual {
_setPendingDelay(0, 0);
}
/**
* @dev Returns the amount of seconds to wait after the `newDelay` will
* become the new {defaultAdminDelay}.
*
* The value returned guarantees that if the delay is reduced, it will go into effect
* after a wait that honors the previously set delay.
*
* See {defaultAdminDelayIncreaseWait}.
*/
function _delayChangeWait(uint48 newDelay) internal view virtual returns (uint48) {
uint48 currentDelay = defaultAdminDelay();
// When increasing the delay, we schedule the delay change to occur after a period of "new delay" has passed, up
// to a maximum given by defaultAdminDelayIncreaseWait, by default 5 days. For example, if increasing from 1 day
// to 3 days, the new delay will come into effect after 3 days. If increasing from 1 day to 10 days, the new
// delay will come into effect after 5 days. The 5 day wait period is intended to be able to fix an error like
// using milliseconds instead of seconds.
//
// When decreasing the delay, we wait the difference between "current delay" and "new delay". This guarantees
// that an admin transfer cannot be made faster than "current delay" at the time the delay change is scheduled.
// For example, if decreasing from 10 days to 3 days, the new delay will come into effect after 7 days.
return
newDelay > currentDelay
? uint48(MathUpgradeable.min(newDelay, defaultAdminDelayIncreaseWait())) // no need to safecast, both inputs are uint48
: currentDelay - newDelay;
}
///
/// Private setters
///
/**
* @dev Setter of the tuple for pending admin and its schedule.
*
* May emit a DefaultAdminTransferCanceled event.
*/
function _setPendingDefaultAdmin(address newAdmin, uint48 newSchedule) private {
(, uint48 oldSchedule) = pendingDefaultAdmin();
_pendingDefaultAdmin = newAdmin;
_pendingDefaultAdminSchedule = newSchedule;
// An `oldSchedule` from `pendingDefaultAdmin()` is only set if it hasn't been accepted.
if (_isScheduleSet(oldSchedule)) {
// Emit for implicit cancellations when another default admin was scheduled.
emit DefaultAdminTransferCanceled();
}
}
/**
* @dev Setter of the tuple for pending delay and its schedule.
*
* May emit a DefaultAdminDelayChangeCanceled event.
*/
function _setPendingDelay(uint48 newDelay, uint48 newSchedule) private {
uint48 oldSchedule = _pendingDelaySchedule;
if (_isScheduleSet(oldSchedule)) {
if (_hasSchedulePassed(oldSchedule)) {
// Materialize a virtual delay
_currentDelay = _pendingDelay;
} else {
// Emit for implicit cancellations when another delay was scheduled.
emit DefaultAdminDelayChangeCanceled();
}
}
_pendingDelay = newDelay;
_pendingDelaySchedule = newSchedule;
}
///
/// Private helpers
///
/**
* @dev Defines if an `schedule` is considered set. For consistency purposes.
*/
function _isScheduleSet(uint48 schedule) private pure returns (bool) {
return schedule != 0;
}
/**
* @dev Defines if an `schedule` is considered passed. For consistency purposes.
*/
function _hasSchedulePassed(uint48 schedule) private view returns (bool) {
return schedule < block.timestamp;
}
/**
* @dev This empty reserved space is put in place to allow future versions to add new
* variables without shifting down storage in the inheritance chain.
* See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps
*/
uint256[48] private __gap;
}
"
},
"contracts/lib/EIP712.sol": {
"content": "// SPDX-License-Identifier: MIT
pragma solidity ^0.8.17;
import { ECRecover } from "./ECRecover.sol";
/**
* @title EIP712
* @notice A library that provides EIP712 helper functions
* @custom:security-contact smart-contract-security@paxos.com
*/
library EIP712 {
// keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)")
bytes32 public constant EIP712_DOMAIN_TYPEHASH = 0x8b73c3c69bb8fe3d512ecc4cf759cc79239f7b179b0ffacaa9a75d522b39400f;
/**
* @notice Make EIP712 domain separator
* @param name Contract name
* @param version Contract version
* @return Domain separator
*/
function _makeDomainSeparator(string memory name, string memory version) internal view returns (bytes32) {
return
keccak256(
abi.encode(
EIP712_DOMAIN_TYPEHASH,
keccak256(bytes(name)),
keccak256(bytes(version)),
block.chainid,
address(this)
)
);
}
/**
* @notice Recover signer's address from a EIP712 signature
* @param domainSeparator Domain separator
* @param v v of the signature
* @param r r of the signature
* @param s s of the signature
* @param typeHashAndData Type hash concatenated with data
* @return Signer's address
*/
function _recover(
bytes32 domainSeparator,
uint8 v,
bytes32 r,
bytes32 s,
bytes memory typeHashAndData
) internal pure returns (address) {
bytes32 digest = keccak256(abi.encodePacked("\x19\x01", domainSeparator, keccak256(typeHashAndData)));
return ECRecover.recover(digest, v, r, s);
}
}
"
},
"contracts/lib/EIP3009.sol": {
"content": "// SPDX-License-Identifier: MIT
pragma solidity ^0.8.17;
import { PaxosBaseAbstract } from "./PaxosBaseAbstract.sol";
import { EIP712Domain } from "./EIP712Domain.sol";
import { EIP712 } from "./EIP712.sol";
/**
* @title EIP3009 contract
* @dev An abstract contract to provide EIP3009 functionality.
* @notice These functions do not prevent replay attacks when an initial
* transaction fails. If conditions change, such as the contract going
* from paused to unpaused, an external observer can reuse the data from the
* failed transaction to execute it later.
* @custom:security-contact smart-contract-security@paxos.com
*/
abstract contract EIP3009 is PaxosBaseAbstract, EIP712Domain {
// keccak256("TransferWithAuthorization(address from,address to,uint256 value,uint256 validAfter,uint256 validBefore,bytes32 nonce)")
bytes32 public constant TRANSFER_WITH_AUTHORIZATION_TYPEHASH =
0x7c7c6cdb67a18743f49ec6fa9b35f50d52ed05cbed4cc592e13b44501c1a2267;
// keccak256("ReceiveWithAuthorization(address from,address to,uint256 value,uint256 validAfter,uint256 validBefore,bytes32 nonce)")
bytes32 public constant RECEIVE_WITH_AUTHORIZATION_TYPEHASH =
0xd099cc98ef71107a616c4f0f941f04c322d8e254fe26b3c6668db87aae413de8;
// keccak256("CancelAuthorization(address authorizer,bytes32 nonce)")
bytes32 public constant CANCEL_AUTHORIZATION_TYPEHASH =
0x158b0a9edf7a828aad02f63cd515c68ef2f50ba807396f6d12842833a1597429;
error CallerMustBePayee();
error AuthorizationInvalid();
error AuthorizationExpired();
error BlockedAccountAuthorizer();
/**
* @dev authorizer address => nonce => state (true = used / false = unused)
*/
mapping(address => mapping(bytes32 => bool)) internal _authorizationStates;
// Storage gap: https://docs.openzeppelin.com/upgrades-plugins/1.x/writing-upgradeable#storage-gaps
uint256[10] __gap_EIP3009;
event AuthorizationUsed(address indexed authorizer, bytes32 indexed nonce);
event AuthorizationCanceled(address indexed authorizer, bytes32 indexed nonce);
event AuthorizationAlreadyUsed(address indexed authorizer, bytes32 indexed nonce);
/**
* @notice Returns the state of an authorization
* @dev Nonces are randomly generated 32-byte data unique to the authorizer's
* address
* @param authorizer Authorizer's address
* @param nonce Nonce of the authorization
* @return True if the nonce is used
*/
function authorizationState(address authorizer, bytes32 nonce) external view returns (bool) {
return _authorizationStates[authorizer][nonce];
}
/**
* @notice Execute a transfer with a signed authorization
* @param from Payer's address (Authorizer)
* @param to Payee's address
* @param value Amount to be transferred
* @param validAfter The time after which this is valid (unix time)
* @param validBefore The time before which this is valid (unix time)
* @param nonce Unique nonce
* @param v v of the signature
* @param r r of the signature
* @param s s of the signature
*/
function transferWithAuthorization(
address from,
address to,
uint256 value,
uint256 validAfter,
uint256 validBefore,
bytes32 nonce,
uint8 v,
bytes32 r,
bytes32 s
) external whenNotPaused {
_transferWithAuthorization(
TRANSFER_WITH_AUTHORIZATION_TYPEHASH,
from,
to,
value,
validAfter,
validBefore,
nonce,
v,
r,
s
);
}
function transferWithAuthorizationBatch(
address[] memory from,
address[] memory to,
uint256[] memory value,
uint256[] memory validAfter,
uint256[] memory validBefore,
bytes32[] memory nonce,
uint8[] memory v,
bytes32[] memory r,
bytes32[] memory s
) external whenNotPaused {
// Validate length of each parameter with "from" argument to make sure lengths of all input arguments are the same.
if (
!(to.length == from.length &&
value.length == from.length &&
validAfter.length == from.length &&
validBefore.length == from.length &&
nonce.length == from.length &&
v.length == from.length &&
r.length == from.length &&
s.length == from.length)
) {
revert ArgumentLengthMismatch();
}
for (uint16 i = 0; i < from.length; i++) {
_transferWithAuthorization(
TRANSFER_WITH_AUTHORIZATION_TYPEHASH,
from[i],
to[i],
value[i],
validAfter[i],
validBefore[i],
nonce[i],
v[i],
r[i],
s[i]
);
}
}
/**
* @notice Receive a transfer with a signed authorization from the payer
* @dev This has an additional check to ensure that the payee's address matches
* the caller of this function to prevent front-running attacks. (See security
* considerations)
* @param from Payer's address (Authorizer)
* @param to Payee's address
* @param value Amount to be transferred
* @param validAfter The time after which this is valid (unix time)
* @param validBefore The time before which this is valid (unix time)
* @param nonce Unique nonce
* @param v v of the signature
* @param r r of the signature
* @param s s of the signature
*/
function receiveWithAuthorization(
address from,
address to,
uint256 value,
uint256 validAfter,
uint256 validBefore,
bytes32 nonce,
uint8 v,
bytes32 r,
bytes32 s
) external whenNotPaused {
if (to != msg.sender) revert CallerMustBePayee();
_transferWithAuthorization(
RECEIVE_WITH_AUTHORIZATION_TYPEHASH,
from,
to,
value,
validAfter,
validBefore,
nonce,
v,
r,
s
);
}
/**
* @notice Attempt to cancel an authorization
* @param authorizer Authorizer's address
* @param nonce Nonce of the authorization
* @param v v of the signature
* @param r r of the signature
* @param s s of the signature
*/
function cancelAuthorization(
address authorizer,
bytes32 nonce,
uint8 v,
bytes32 r,
bytes32 s
) external whenNotPaused {
if (_isAddrFrozen(authorizer)) revert AddressFrozen();
if (_authorizationStates[authorizer][nonce]) {
emit AuthorizationAlreadyUsed(authorizer, nonce);
return; //Return instead of throwing an error to prevent front running from blocking complex txs
}
bytes memory data = abi.encode(CANCEL_AUTHORIZATION_TYPEHASH, authorizer, nonce);
if (EIP712._recover(DOMAIN_SEPARATOR, v, r, s, data) != authorizer) revert InvalidSignature();
_authorizationStates[authorizer][nonce] = true;
emit AuthorizationCanceled(authorizer, nonce);
}
/*
* @dev Internal function to execute a single transfer with a signed authorization
* @param typeHash The typehash of transfer or receive.
* @param from Payer's address (Authorizer)
* @param to Payee's address
* @param value Amount to be transferred
* @param validAfter The time after which this is valid (unix time)
* @param validBefore The time before which this is valid (unix time)
* @param nonce Unique nonce
* @param v v of the signature
* @param r r of the signature
* @param s s of the signature
*/
function _transferWithAuthorization(
bytes32 typeHash,
address from,
address to,
uint256 value,
uint256 validAfter,
uint256 validBefore,
bytes32 nonce,
uint8 v,
bytes32 r,
bytes32 s
) internal {
if (block.timestamp <= validAfter) revert AuthorizationInvalid();
if (block.timestamp >= validBefore) revert AuthorizationExpired();
if (_authorizationStates[from][nonce]) {
emit AuthorizationAlreadyUsed(from, nonce);
return; //Return instead of throwing an error to prevent front running from blocking batches
}
bytes memory data = abi.encode(typeHash, from, to, value, validAfter, validBefore, nonce);
if (EIP712._recover(DOMAIN_SEPARATOR, v, r, s, data) != from) revert InvalidSignature();
_authorizationStates[from][nonce] = true;
emit AuthorizationUsed(from, nonce);
_transfer(from, to, value);
}
}
"
},
"contracts/lib/EIP2612.sol": {
"content": "// SPDX-License-Identifier: MIT
pragma solidity ^0.8.17;
import { PaxosBaseAbstract } from "./PaxosBaseAbstract.sol";
import { EIP712Domain } from "./EIP712Domain.sol";
import { EIP712 } from "./EIP712.sol";
/**
* @title EIP2612 contract
* @dev An abstract contract to provide EIP2612 functionality.
* @custom:security-contact smart-contract-security@paxos.com
*/
abstract contract EIP2612 is PaxosBaseAbstract, EIP712Domain {
// keccak256("Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)")
bytes32 public constant PERMIT_TYPEHASH = 0x6e71edae12b1b97f4d1f60370fef10105fa2faae0126114a169c64845d6126c9;
mapping(address => uint256) internal _nonces;
// Storage gap: https://docs.openzeppelin.com/upgrades-plugins/1.x/writing-upgradeable#storage-gaps
uint256[10] __gap_EIP2612;
error PermitExpired();
/**
* @notice Nonces for permit
* @param owner Token owner's address
* @return Next nonce
*/
function nonces(address owner) external view returns (uint256) {
return _nonces[owner];
}
/**
* @notice Update allowance with a signed permit
* @param owner Token owner's address (Authorizer)
* @param spender Spender's address
* @param value Amount of allowance
* @param deadline The time at which this expires (unix time)
* @param v v of the signature
* @param r r of the signature
* @param s s of the signature
*/
function permit(
address owner,
address spender,
uint256 value,
uint256 deadline,
uint8 v,
bytes32 r,
bytes32 s
) external whenNotPaused isNonZeroAddress(owner) isNonZeroAddress(spender) {
if (deadline < block.timestamp) revert PermitExpired();
if (_isAddrFrozen(spender) || _isAddrFrozen(owner)) revert AddressFrozen();
bytes memory data = abi.encode(PERMIT_TYPEHASH, owner, spender, value, _nonces[owner]++, deadline);
if (EIP712._recover(DOMAIN_SEPARATOR, v, r, s, data) != owner) revert InvalidSignature();
_approve(owner, spender, value);
}
}
"
},
"contracts/SupplyControl.sol": {
"content": "// SPDX-License-Identifier: MIT
pragma solidity ^0.8.17;
import { AccessControlDefaultAdminRulesUpgradeable } from "./openzeppelin/contracts-upgradeable/access/AccessControlDefaultAdminRulesUpgradeable.sol";
import { UUPSUpgradeable } from "./openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol";
import { IAccessControl } from "./openzeppelin/contracts/access/IAccessControl.sol";
import { EnumerableSet } from "./openzeppelin/contracts/utils/structs/EnumerableSet.sol";
import { DoubleEndedQueue } from "./openzeppelin/contracts/utils/structs/DoubleEndedQueue.sol";
import { PaxosBaseAbstract } from "./lib/PaxosBaseAbstract.sol";
import { RateLimit } from "./lib/RateLimit.sol";
/**
* @title SupplyControl
* @dev control the token supply. The `SUPPLY_CONTROLLER_MANAGER_ROLE` role is responsible for managing
* addresses with the `SUPPLY_CONTROLLER_ROLE`, referred to as supplyControllers. Only supplyControllers can
* mint and burn tokens. SupplyControllers can optionally have rate limits to limit how many tokens can be
* minted over a given time frame.
* @custom:security-contact smart-contract-security@paxos.com
*/
contract SupplyControl is AccessControlDefaultAdminRulesUpgradeable, UUPSUpgradeable {
using EnumerableSet for EnumerableSet.AddressSet;
// Access control roles
// keccak256("SUPPLY_CONTROLLER_MANAGER_ROLE")
// Can add, update, and remove `SupplyController`s
bytes32 public constant SUPPLY_CONTROLLER_MANAGER_ROLE =
0x5d3e9f1ecbcdad7b0da30e7d29c9eddaef83a4502dafe3d2dd85cfdb12e4af10;
// keccak256("SUPPLY_CONTROLLER_ROLE")
// Can mint/burn tokens
bytes32 public constant SUPPLY_CONTROLLER_ROLE = 0x9c00d6f280439b1dfa4da90321e0a3f3c2e87280f4d07fea9fa43ff2cf02df2b;
// keccak256("TOKEN_CONTRACT_ROLE")
// Tracks the token contract to protect functions which impact rate limits
bytes32 public constant TOKEN_CONTRACT_ROLE = 0xd32fd1ee5f4f111da6f27444787e5200ec57a8849509c00ef2998467052b32a3;
// SUPPLY CONTROL DATA
mapping(address => SupplyController) internal supplyControllerMap;
//Used to get all supply controllers
EnumerableSet.AddressSet internal supplyControllerSet;
uint256[35] private __gap_SC; // solhint-disable-line var-name-mixedcase
/**
* @dev Struct defines a supply controller. Different supply controllers can have different rules.
* @param rateLimit Contract which handles rate limit logic
* @param mintAddressWhitelist Addresses the {SupplyController} can mint to
* @param allowAnyMintAndBurnAddress If true, allows the supply controller to mint to and burn from any address
*/
struct SupplyController {
RateLimit.Storage rateLimitStorage;
EnumerableSet.AddressSet mintAddressWhitelist;
bool allowAnyMintAndBurnAddress;
}
/**
* @dev Struct defines the configuration needed when creating a new supply controller.
* @param newSupplyController Address of the new supply controller
* @param limitConfig Limit configuration
* @param mintAddressWhitelist Addresses the supply controller can mint to
* @param allowAnyMintAndBurnAddress If true, allows the supply controller to mint to and burn from any address
*/
struct SupplyControllerInitialization {
address newSupplyController;
RateLimit.LimitConfig limitConfig;
address[] mintAddressWhitelist;
bool allowAnyMintAndBurnAddress;
}
/**
* @dev Emitted when {addSupplyController} is called.
* @param newSupplyController Address of the new supply controller
* @param limitCapacity Max amount for the rate limit. Checked in `_checkCurrentPeriodAmount`
* @param refillPerSecond Amount to add to limit each second up to the `limitCapacity`
* @param mintAddressWhitelist Addresses the supply controller can mint to
* @param allowAnyMintAndBurnAddress If true, allows the supply controller to mint to and burn from any address
*/
event SupplyControllerAdded(
address indexed newSupplyController,
uint256 limitCapacity,
uint256 refillPerSecond,
address[] mintAddressWhitelist,
bool allowAnyMintAndBurnAddress
);
/**
* @dev Emitted when {removeSupplyController} is called.
* @param oldSupplyController The old supply controller address
*/
event SupplyControllerRemoved(address indexed oldSupplyController);
/**
* @dev Emitted when limit configuration is updated for `supplyController`.
* Occurs when {updateLimitConfig} is called.
* @param supplyController Supply controller address
* @param newLimitConfig New limit configuration
* @param oldLimitConfig Old limit configuration
*/
event LimitConfigUpdated(
address indexed supplyController,
RateLimit.LimitConfig newLimitConfig,
RateLimit.LimitConfig oldLimitConfig
);
/**
* @dev Emitted when `allowAnyMintAndBurnAddress` is updated for `supplyController`.
* Occurs when {updateAllowAnyMintAndBurnAddress} is called.
* @param supplyController Supply controller address
* @param newAllowAnyMintAndBurnAddress New allow config
* @param oldAllowAnyMintAndBurnAddress Old allow config
*/
event AllowAnyMintAndBurnAddressUpdated(
address indexed supplyController,
bool newAllowAnyMintAndBurnAddress,
bool oldAllowAnyMintAndBurnAddress
);
/**
* @dev Emitted when `mintAddress` is added to `mintAddressWhitelist` in `supplyController`.
* Occurs when {addMintAddressToWhitelist} is called
* @param supplyController Supply controller address
* @param mintAddress New address which can be minted to
*/
event MintAddressAddedToWhitelist(address indexed supplyController, address indexed mintAddress);
/**
* @dev Emitted when `mintAddress` is removed from `mintAddressWhitelist` in `supplyController`.
* Occurs when {removeMintAddressFromWhitelist} is called
* @param supplyController Supply controller address
* @param mintAddress Address which can no longer be minted to
*/
event MintAddressRemovedFromWhitelist(address indexed supplyController, address indexed mintAddress);
error AccountMissingSupplyControllerRole(address account);
error AccountAlreadyHasSupplyControllerRole(address account);
error CannotMintToAddress(address supplyController, address mintToAddress);
error CannotBurnFromAddress(address supplyController, address burnFromAddress);
error CannotAddDuplicateAddress(address addressToAdd);
error CannotRemoveNonExistantAddress(address addressToRemove);
error ZeroAddress();
/**
* @dev Modifier which checks that the specified `supplyController` address has the SUPPLY_CONTROLLER_ROLE
* @param supplyController Supply controller address
*/
modifier onlySupplyController(address supplyController) {
if (!hasRole(SUPPLY_CONTROLLER_ROLE, supplyController)) {
revert AccountMissingSupplyControllerRole(supplyController);
}
_;
}
/**
* @dev Modifier which checks that the specified `supplyController` address does not have the SUPPLY_CONTROLLER_ROLE
* @param supplyController Supply controller address
*/
modifier notSupplyController(address supplyController) {
if (hasRole(SUPPLY_CONTROLLER_ROLE, supplyController)) {
revert AccountAlreadyHasSupplyControllerRole(supplyController);
}
_;
}
/**
* @dev Modifier to check for zero address.
*/
modifier isNonZeroAddress(address addr) {
if (addr == address(0)) {
revert ZeroAddress();
}
_;
}
/// @custom:oz-upgrades-unsafe-allow constructor
constructor() {
_disableInitializers();
}
/**
* @dev Initializer for SupplyControl.
* Proper order of setting up the contracts:
* 1. Deploy/reinitialize PaxosToken
* 2. Deploy SupplyControl with `SupplyControllerInitialization` config
* 3. Set SupplyControl address in PaxosToken via `setSupplyControl`
* @param initialOwner Initial owner address
* @param supplyControllerManager SupplyControllerManager address
* @param tokenAddress Token contract address
* @param scInitializationConfig Configuration to initialize a list of supply controllers
*/
function initialize(
address initialOwner,
address supplyControllerManager,
address tokenAddress,
SupplyControllerInitialization[] calldata scInitializationConfig
) external initializer isNonZeroAddress(supplyControllerManager) isNonZeroAddress(tokenAddress) {
__AccessControlDefaultAdminRules_init(3 hours, initialOwner);
__UUPSUpgradeable_init();
_grantRole(SUPPLY_CONTROLLER_MANAGER_ROLE, supplyControllerManager);
_grantRole(TOKEN_CONTRACT_ROLE, tokenAddress);
for (uint256 i = 0; i < scInitializationConfig.length; ) {
_addSupplyController(scInitializationConfig[i]);
unchecked {
i++;
}
}
}
/**
* @dev Adds a new supply controller which can be used to control the supply of a token.
* Can be called externally by the `SUPPLY_CONTROLLER_MANAGER_ROLE`.
* @param newSupplyController Address of the new supply controller
* @param limitCapacity Max amount for the rate limit.
* @param refillPerSecond Amount to add to limit each second up to the `limitCapacity`
* @param mintAddressWhitelist Addresses the supply controller can mint to
* @param allowAnyMintAndBurnAddress If true, allows the supply controller to mint to and burn from any address
*/
function addSupplyController(
address newSupplyController,
uint256 limitCapacity,
uint256 refillPerSecond,
address[] memory mintAddressWhitelist,
bool allowAnyMintAndBurnAddress
) external onlyRole(SUPPLY_CONTROLLER_MANAGER_ROLE) {
RateLimit.LimitConfig memory limitConfig = RateLimit.LimitConfig(limitCapacity, refillPerSecond);
SupplyControllerInitialization memory scInitializationConfig = SupplyControllerInitialization(
newSupplyController,
limitConfig,
mintAddressWhitelist,
allowAnyMintAndBurnAddress
);
_addSupplyController(scInitializationConfig);
}
/**
* @dev Removes `oldSupplyController`
* @param oldSupplyController The old supply controller address
*/
function removeSupplyController(
address oldSupplyController
) external onlyRole(SUPPLY_CONTROLLER_MANAGER_ROLE) onlySupplyController(oldSupplyController) {
_revokeRole(SUPPLY_CONTROLLER_ROLE, oldSupplyController);
SupplyController storage supplyController = supplyControllerMap[oldSupplyController];
_removeAddressSet(supplyController.mintAddressWhitelist);
EnumerableSet.remove(supplyControllerSet, oldSupplyController);
delete supplyControllerMap[oldSupplyController];
emit SupplyControllerRemoved(oldSupplyController);
}
/**
* Update limit configuration
* @param supplyController_ Supply controller address.
* @param limitCapacity Max amount for the rate limit
* @param refillPerSecond Amount to add to limit each second up to the `limitCapacity`
*/
function updateLimitConfig(
address supplyController_,
uint256 limitCapacity,
uint256 refillPerSecond
) external onlyRole(SUPPLY_CONTROLLER_MANAGER_ROLE) onlySupplyController(supplyController_) {
RateLimit.LimitConfig memory limitConfig = RateLimit.LimitConfig(limitCapacity, refillPerSecond);
SupplyController storage supplyController = supplyControllerMap[supplyController_];
RateLimit.LimitConfig memory oldLimitConfig = supplyController.rateLimitStorage.limitConfig;
supplyController.rateLimitStorage.limitConfig = limitConfig;
emit LimitConfigUpdated(supplyController_, limitConfig, oldLimitConfig);
}
function updateAllowAnyMintAndBurnAddress(
address supplyController_,
bool allowAnyMintAndBurnAddress
) external onlyRole(SUPPLY_CONTROLLER_MANAGER_ROLE) onlySupplyController(supplyController_) {
SupplyController storage supplyController = supplyControllerMap[supplyController_];
bool oldAllowValue = supplyController.allowAnyMintAndBurnAddress;
supplyController.allowAnyMintAndBurnAddress = allowAnyMintAndBurnAddress;
emit AllowAnyMintAndBurnAddressUpdated(supplyController_, allowAnyMintAndBurnAddress, oldAllowValue);
}
/**
* @dev Adds `mintAddress` to `mintAddressWhitelist` in `supplyController`.
* @param supplyController_ Supply controller address
* @param mintAddress Address which can be minted to
*/
function addMintAddressToWhitelist(
address supplyController_,
address mintAddress
) external onlyRole(SUPPLY_CONTROLLER_MANAGER_ROLE) onlySupplyController(supplyController_) {
SupplyController storage supplyController = supplyControllerMap
Submitted on: 2025-10-24 21:18:00
Comments
Log in to comment.
No comments yet.