AssetTokenCCIPInitializable

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/assetToken/AssetTokenInitializable.sol": {
      "content": "// SPDX-License-Identifier: MIT
pragma solidity ^0.8.28;

import { Initializable } from "solady/src/utils/Initializable.sol";
import { ERC20 } from "solady/src/tokens/ERC20.sol";
import { ReentrancyGuard } from "solady/src/utils/ReentrancyGuard.sol";

import { AssetTokenData } from "./base/AssetTokenData.sol";
/// @author Swarm Markets
/// @title AssetToken
/// @notice Main Asset Token Contract
contract AssetTokenInitializable is Initializable, ERC20, ReentrancyGuard {
    error ZeroAddressPassed();
    error AccessNotFound();
    error WrongKYA(string kya);

    enum RequestErrorType {
        NotExists,
        Completed,
        Cancelled,
        UnstakeRequested
    }
    error RequestError(uint256 requestID, RequestErrorType errorType);

    enum AmountErrorType {
        ZeroAmount,
        NotEnoughFunds,
        MaxStatePercentReached,
        AmountExceedsStaked,
        MinRedemptionAmountNotReached
    }
    error AmountError(uint256 value1, uint256 value2, AmountErrorType errorType);

    enum ContractErrorType {
        NotAuthorizedOnActive,
        NotAllowedOnSafeguard,
        FreezingError,
        UnfreezingError,
        SafeguardChangeError,
        ContractIsActiveNotOnSafeguard
    }
    error ContractError(address contractAddress, ContractErrorType errorType);

    /// @notice Emitted when the address of the asset token data is set
    event AssetTokenDataChanged(AssetTokenData indexed oldAddress, address indexed newAddress, address indexed caller);

    /// @notice Emitted when kya string is set
    event KyaChanged(string kya, address indexed caller);

    /// @notice Emitted when minimumRedemptionAmount is set
    event MinimumRedemptionAmountChanged(uint256 newAmount, address indexed caller);

    /// @notice Emitted when a mint request is requested
    event MintRequested(
        uint256 indexed mintRequestID,
        address indexed destination,
        uint256 amount,
        address indexed caller
    );

    /// @notice Emitted when a mint request gets approved
    event MintApproved(
        uint256 indexed mintRequestID,
        address indexed destination,
        uint256 amountMinted,
        address indexed caller
    );

    /// @notice Emitted when a redemption request is requested
    event RedemptionRequested(
        uint256 indexed redemptionRequestID,
        uint256 assetTokenAmount,
        uint256 underlyingAssetAmount,
        bool fromStake,
        address indexed caller
    );

    /// @notice Emitted when a redemption request is cancelled
    event RedemptionCanceled(
        uint256 indexed redemptionRequestID,
        address indexed requestReceiver,
        string motive,
        address indexed caller
    );

    /// @notice Emitted when a redemption request is approved
    event RedemptionApproved(
        uint256 indexed redemptionRequestID,
        uint256 assetTokenAmount,
        uint256 underlyingAssetAmount,
        address indexed requestReceiver,
        address indexed caller
    );

    /// @notice Emitted when the token gets bruned
    event TokenBurned(uint256 amount, address indexed caller);

    /// @notice Emitted when the contract change to safeguard
    event SafeguardUnstaked(uint256 amount, address indexed caller);

    /// @notice Structure to hold the Mint Requests
    struct MintRequest {
        address destination;
        uint256 amount;
        string referenceTo;
        bool completed;
    }

    /// @notice Structure to hold the Redemption Requests
    struct RedemptionRequest {
        address sender;
        string receipt;
        uint256 assetTokenAmount;
        uint256 underlyingAssetAmount;
        bool completed;
        bool fromStake;
        string approveTxID;
        address canceledBy;
    }

    /// @dev This is a WAD on DSMATH representing 1
    /// @dev This is a proportion of 1 representing 100%, equal to a WAD
    uint256 public constant DECIMALS_HUNDRED_PERCENT = 10 ** 18;

    /// @dev Used to check access to functions as a kindof modifiers
    uint256 private constant ACTIVE_CONTRACT = 1 << 0;
    uint256 private constant UNFROZEN_CONTRACT = 1 << 1;
    uint256 private constant ONLY_ISSUER = 1 << 2;
    uint256 private constant ONLY_ISSUER_OR_GUARDIAN = 1 << 3;
    uint256 private constant ONLY_ISSUER_OR_AGENT = 1 << 4;

    string private constant AUTOMATIC_REDEMPTION_APPROVAL = "AutomaticRedemptionApproval";

    string private NAME;
    string private SYMBOL;

    /// @notice AssetTokenData Address
    AssetTokenData public assetTokenDataAddress;

    /// @notice Mint Requests mapping and last ID
    mapping(uint256 => MintRequest) public mintRequests;
    uint256 public mintRequestID;

    /// @notice Redemption Requests mapping and last ID
    mapping(uint256 => RedemptionRequest) public redemptionRequests;
    uint256 public redemptionRequestID;

    /// @notice stakedRedemptionRequests is map from requester to request ID
    /// @notice exists to detect that sender already has request from stake function
    mapping(address => uint256) public stakedRedemptionRequests;

    /// @notice mapping to hold each user safeguardStake amoun
    mapping(address => uint256) public safeguardStakes;

    /// @notice sum of the total stakes amounts
    uint256 public totalStakes;

    /// @notice the percetage (on 18 digits)
    /// @notice if this gets overgrown the contract change state
    uint256 public statePercent;

    /// @notice know your asset string
    string public kya;

    /// @notice minimum Redemption Amount (in Asset token value)
    uint256 public minimumRedemptionAmount;

    modifier requireNonEmptyAddress(address _address) {
        require(_address != address(0), ZeroAddressPassed());
        _;
    }

    /// @notice Constructor: sets the state variables and provide proper checks to deploy
    /// @param _assetTokenData the asset token data contract address
    /// @param _statePercent the state percent to check the safeguard convertion
    /// @param _kya verification link
    /// @param _minimumRedemptionAmount less than this value is not allowed
    /// @param _name of the token
    /// @param _symbol of the token
    function __initialize_AssetTokenInitializable_(
        address _assetTokenData,
        uint256 _statePercent,
        string memory _kya,
        uint256 _minimumRedemptionAmount,
        string memory _name,
        string memory _symbol
    ) internal requireNonEmptyAddress(_assetTokenData) {
        require(_statePercent > 0, AmountError(_statePercent, 0, AmountErrorType.ZeroAmount));
        require(
            _statePercent <= DECIMALS_HUNDRED_PERCENT,
            AmountError(_statePercent, DECIMALS_HUNDRED_PERCENT, AmountErrorType.MaxStatePercentReached)
        );
        require(bytes(_kya).length > 3, WrongKYA(_kya));

        NAME = _name;
        SYMBOL = _symbol;
        // IT IS THE WAD EQUIVALENT USED IN DSMATH
        assetTokenDataAddress = AssetTokenData(_assetTokenData);
        statePercent = _statePercent;
        kya = _kya;
        minimumRedemptionAmount = _minimumRedemptionAmount;
    }

    /// @notice Approves the Mint Request
    /// @param _mintRequestID the ID to be referenced in the mapping
    /// @param _referenceTo reference comment for the issuer
    function approveMint(uint256 _mintRequestID, string memory _referenceTo) public nonReentrant {
        _checkAccessToFunction(ACTIVE_CONTRACT | ONLY_ISSUER);

        MintRequest storage s_req = mintRequests[_mintRequestID];
        MintRequest memory m_req = s_req;
        require(m_req.destination != address(0), RequestError(_mintRequestID, RequestErrorType.NotExists));
        require(!m_req.completed, RequestError(_mintRequestID, RequestErrorType.Completed));

        s_req.completed = true;
        s_req.referenceTo = _referenceTo;

        uint256 currentRate = assetTokenDataAddress.update(address(this));
        uint256 amountToMint = (m_req.amount * DECIMALS_HUNDRED_PERCENT) / currentRate;

        _mint(m_req.destination, amountToMint);
        emit MintApproved(_mintRequestID, m_req.destination, amountToMint, msg.sender);
    }

    /// @notice Approves the Redemption Requests
    /// @param _redemptionRequestID redemption request ID to be referenced in the mapping
    /// @param _approveTxID the transaction ID
    function approveRedemption(uint256 _redemptionRequestID, string memory _approveTxID) public {
        _checkAccessToFunction(ONLY_ISSUER_OR_GUARDIAN);
        RedemptionRequest storage s_req = redemptionRequests[_redemptionRequestID];
        RedemptionRequest storage m_req = s_req;

        require(m_req.canceledBy == address(0), RequestError(_redemptionRequestID, RequestErrorType.Cancelled));
        require(m_req.sender != address(0), RequestError(_redemptionRequestID, RequestErrorType.NotExists));
        require(!m_req.completed, RequestError(_redemptionRequestID, RequestErrorType.Completed));
        if (m_req.fromStake)
            require(
                assetTokenDataAddress.isOnSafeguard(address(this)),
                ContractError(address(this), ContractErrorType.ContractIsActiveNotOnSafeguard)
            );

        s_req.completed = true;
        s_req.approveTxID = _approveTxID;
        _burn(address(this), m_req.assetTokenAmount);

        emit RedemptionApproved(
            _redemptionRequestID,
            m_req.assetTokenAmount,
            m_req.underlyingAssetAmount,
            m_req.sender,
            msg.sender
        );
    }

    /// @notice Requests an amount of assetToken Redemption
    /// @param _assetTokenAmount the amount of Asset Token to be redeemed
    /// @param _destination the off chain hash of the redemption transaction
    /// @return reqId uint256 redemptionRequest ID to be referenced in the mapping
    function requestRedemption(
        uint256 _assetTokenAmount,
        string calldata _destination
    ) external nonReentrant returns (uint256 reqId) {
        require(_assetTokenAmount > 0, AmountError(_assetTokenAmount, 0, AmountErrorType.ZeroAmount));
        uint256 balance = balanceOf(msg.sender);
        require(balance >= _assetTokenAmount, AmountError(_assetTokenAmount, balance, AmountErrorType.NotEnoughFunds));

        AssetTokenData assetTknDtaContract = assetTokenDataAddress;
        address issuer = assetTknDtaContract.getIssuer(address(this));
        address guardian = assetTknDtaContract.getGuardian(address(this));
        bool isOnSafeguard = assetTknDtaContract.isOnSafeguard(address(this));

        if ((!isOnSafeguard && msg.sender != issuer) || (isOnSafeguard && msg.sender != guardian)) {
            uint256 _minimumRedemptionAmount = minimumRedemptionAmount;
            require(
                _assetTokenAmount >= _minimumRedemptionAmount,
                AmountError(_assetTokenAmount, _minimumRedemptionAmount, AmountErrorType.MinRedemptionAmountNotReached)
            );
        }

        uint256 rate = assetTknDtaContract.update(address(this));
        uint256 underlyingAssetAmount = (_assetTokenAmount * rate) / DECIMALS_HUNDRED_PERCENT;

        reqId = redemptionRequestID + 1;
        redemptionRequestID = reqId;

        redemptionRequests[reqId] = RedemptionRequest(
            msg.sender,
            _destination,
            _assetTokenAmount,
            underlyingAssetAmount,
            false,
            false,
            "",
            address(0)
        );

        /// @dev make the transfer to the contract for the amount requested (18 digits)
        _transfer(msg.sender, address(this), _assetTokenAmount);

        /// @dev approve instantly when called by issuer or guardian
        if ((!isOnSafeguard && msg.sender == issuer) || (isOnSafeguard && msg.sender == guardian)) {
            approveRedemption(reqId, AUTOMATIC_REDEMPTION_APPROVAL);
        }

        emit RedemptionRequested(reqId, _assetTokenAmount, underlyingAssetAmount, false, msg.sender);
    }

    /// @notice Performs the Safeguard Stake
    /// @param _amount the assetToken amount to be staked
    /// @param _receipt the off chain hash of the redemption transaction
    function safeguardStake(uint256 _amount, string calldata _receipt) external nonReentrant {
        _checkAccessToFunction(ACTIVE_CONTRACT);
        uint256 balance = balanceOf(msg.sender);
        require(balance >= _amount, AmountError(_amount, balance, AmountErrorType.NotEnoughFunds));

        uint256 _totalStakes = totalStakes + _amount;

        if ((_totalStakes * DECIMALS_HUNDRED_PERCENT) / totalSupply() >= statePercent) {
            require(
                assetTokenDataAddress.setContractToSafeguard(address(this)),
                ContractError(address(this), ContractErrorType.SafeguardChangeError)
            );
            /// @dev now the contract is on safeguard
        }

        uint256 _requestID = stakedRedemptionRequests[msg.sender];
        if (_requestID == 0) {
            /// @dev zero means that it's new request
            uint256 reqId = redemptionRequestID + 1;
            _requestID = reqId;

            redemptionRequestID = reqId;
            redemptionRequests[reqId] = RedemptionRequest(
                msg.sender,
                _receipt,
                _amount,
                0,
                false,
                true,
                "",
                address(0)
            );
            stakedRedemptionRequests[msg.sender] = reqId;
        } else {
            /// @dev non zero means the request already exist and need only add amount
            redemptionRequests[_requestID].assetTokenAmount += _amount;
        }

        safeguardStakes[msg.sender] += _amount;
        totalStakes = _totalStakes;

        _transfer(msg.sender, address(this), _amount);

        emit RedemptionRequested(
            _requestID,
            redemptionRequests[_requestID].assetTokenAmount,
            redemptionRequests[_requestID].underlyingAssetAmount,
            true,
            msg.sender
        );
    }

    /// @notice Sets Asset Token Data Address
    /// @param _newAddress value to be set
    function setAssetTokenData(address _newAddress) external requireNonEmptyAddress(_newAddress) {
        _checkAccessToFunction(UNFROZEN_CONTRACT | ONLY_ISSUER_OR_GUARDIAN);
        AssetTokenData oldAddress = assetTokenDataAddress;
        assetTokenDataAddress = AssetTokenData(_newAddress);
        emit AssetTokenDataChanged(oldAddress, _newAddress, msg.sender);
    }

    /// @notice Requests a mint to the caller
    /// @param _amount the amount to mint in asset token format
    /// @return uint256 request ID to be referenced in the mapping
    function requestMint(uint256 _amount) external returns (uint256) {
        return _requestMint(_amount, msg.sender);
    }

    /// @notice Requests a mint to the _destination address
    /// @param _amount the amount to mint in asset token format
    /// @param _destination the receiver of the tokens
    /// @return uint256 request ID to be referenced in the mapping
    function requestMint(uint256 _amount, address _destination) external returns (uint256) {
        return _requestMint(_amount, _destination);
    }

    /// @notice Sets the verification link
    /// @param _kya value to be set
    function setKya(string calldata _kya) external {
        _checkAccessToFunction(UNFROZEN_CONTRACT | ONLY_ISSUER_OR_GUARDIAN);
        require(bytes(_kya).length > 3, WrongKYA(_kya));
        kya = _kya;
        emit KyaChanged(_kya, msg.sender);
    }

    /// @notice Sets the _minimumRedemptionAmount
    /// @param _minimumRedemptionAmount value to be set
    function setMinimumRedemptionAmount(uint256 _minimumRedemptionAmount) external {
        _checkAccessToFunction(UNFROZEN_CONTRACT | ONLY_ISSUER_OR_GUARDIAN);
        minimumRedemptionAmount = _minimumRedemptionAmount;
        emit MinimumRedemptionAmountChanged(_minimumRedemptionAmount, msg.sender);
    }

    /// @notice Freeze the contract
    function freezeContract() external {
        _checkAccessToFunction(ONLY_ISSUER_OR_GUARDIAN);
        require(
            assetTokenDataAddress.freezeContract(address(this)),
            ContractError(address(this), ContractErrorType.FreezingError)
        );
    }

    /// @notice unfreeze the contract
    function unfreezeContract() external {
        _checkAccessToFunction(ONLY_ISSUER_OR_GUARDIAN);
        require(
            assetTokenDataAddress.unfreezeContract(address(this)),
            ContractError(address(this), ContractErrorType.UnfreezingError)
        );
    }

    /// @notice Burns a certain amount of tokens
    /// @param _amount qty of assetTokens to be burned
    function burn(uint256 _amount) external {
        _burn(msg.sender, _amount);
        emit TokenBurned(_amount, msg.sender);
    }

    /// @notice Approves the Redemption Requests
    /// @param _redemptionRequestID redemption request ID to be referenced in the mapping
    /// @param _motive motive of the cancelation
    function cancelRedemptionRequest(uint256 _redemptionRequestID, string calldata _motive) external {
        RedemptionRequest storage s_req = redemptionRequests[_redemptionRequestID];
        RedemptionRequest memory m_req = s_req;
        require(m_req.sender != address(0), RequestError(_redemptionRequestID, RequestErrorType.NotExists));
        require(m_req.canceledBy == address(0), RequestError(_redemptionRequestID, RequestErrorType.Cancelled));
        require(!m_req.completed, RequestError(_redemptionRequestID, RequestErrorType.Completed));
        require(!m_req.fromStake, RequestError(_redemptionRequestID, RequestErrorType.UnstakeRequested));

        if (msg.sender != m_req.sender) {
            // not owner of the redemption so guardian or issuer should be the caller
            assetTokenDataAddress.onlyIssuerOrGuardian(address(this), msg.sender);
        }

        s_req.assetTokenAmount = 0;
        s_req.underlyingAssetAmount = 0;
        s_req.canceledBy = msg.sender;

        _transfer(address(this), m_req.sender, m_req.assetTokenAmount);

        emit RedemptionCanceled(_redemptionRequestID, m_req.sender, _motive, msg.sender);
    }

    /// @notice Calls to UnStake all the funds
    function safeguardUnstake() external {
        _safeguardUnstake(safeguardStakes[msg.sender]);
    }

    /// @notice Calls to UnStake with a certain amount
    /// @param _amount to be unStaked in asset token
    function safeguardUnstake(uint256 _amount) external {
        _safeguardUnstake(_amount);
    }

    /// @dev Returns the name of the token.
    function name() public view override returns (string memory) {
        return NAME;
    }

    /// @dev Returns the symbol of the token.
    function symbol() public view override returns (string memory) {
        return SYMBOL;
    }

    /// @notice Performs the Mint Request to the destination address
    /// @param _amount entered in the external functions
    /// @param _destination the receiver of the tokens
    /// @return reqId uint256 request ID to be referenced in the mapping
    function _requestMint(uint256 _amount, address _destination) private returns (uint256 reqId) {
        _checkAccessToFunction(ACTIVE_CONTRACT | UNFROZEN_CONTRACT | ONLY_ISSUER_OR_AGENT);
        require(_amount > 0, AmountError(_amount, 0, AmountErrorType.ZeroAmount));

        reqId = mintRequestID + 1;
        mintRequestID = reqId;

        mintRequests[reqId] = MintRequest(_destination, _amount, "", false);

        if (msg.sender == assetTokenDataAddress.getIssuer(address(this))) {
            approveMint(reqId, "IssuerMint");
        }

        emit MintRequested(reqId, _destination, _amount, msg.sender);
    }

    /// @notice Hook to be executed before every transfer and mint
    /// @notice This overrides the ERC20 defined function
    /// @param _from the sender
    /// @param _to the receipent
    /// @param _amount the amount (it is not used  but needed to be defined to override)
    function _beforeTokenTransfer(address _from, address _to, uint256 _amount) internal override {
        //  on safeguard the only available transfers are from allowed addresses and guardian
        //  or from an authorized user to this contract
        //  address(this) is added as the _from for approving redemption (burn)
        //  address(this) is added as the _to for requesting redemption (transfer to this contract)
        //  address(0) is added to the condition to allow burn on safeguard
        _checkAccessToFunction(UNFROZEN_CONTRACT);
        AssetTokenData _assetTokenDataAddress = assetTokenDataAddress;

        bool mbah = _assetTokenDataAddress.mustBeAuthorizedHolders(address(this), _from, _to, _amount);
        if (_assetTokenDataAddress.isOnSafeguard(address(this))) {
            /// @dev  State is SAFEGUARD
            if (
                // receiver is NOT this contract AND sender is NOT this contract AND sender is NOT guardian
                _to != address(this) &&
                _from != address(this) &&
                _from != _assetTokenDataAddress.getGuardian(address(this))
            ) {
                require(
                    _assetTokenDataAddress.isAllowedTransferOnSafeguard(address(this), _from),
                    ContractError(address(this), ContractErrorType.NotAllowedOnSafeguard)
                );
            } else {
                require(mbah, ContractError(address(this), ContractErrorType.NotAuthorizedOnActive));
            }
        } else {
            /// @dev State is ACTIVE
            // this is mint or transfer
            // mint signature: ==> _beforeTokenTransfer(address(0), account, amount);
            // burn signature: ==> _beforeTokenTransfer(account, address(0), amount);
            require(mbah, ContractError(address(this), ContractErrorType.NotAuthorizedOnActive));
        }

        super._beforeTokenTransfer(_from, _to, _amount);
    }

    /// @notice Performs the UnStake with a certain amount
    /// @param _amount to be unStaked in asset token
    function _safeguardUnstake(uint256 _amount) private {
        _checkAccessToFunction(ACTIVE_CONTRACT | UNFROZEN_CONTRACT);
        require(_amount > 0, AmountError(_amount, 0, AmountErrorType.ZeroAmount));
        uint256 _safeguardStakes = safeguardStakes[msg.sender];
        require(
            _safeguardStakes >= _amount,
            AmountError(_amount, _safeguardStakes, AmountErrorType.AmountExceedsStaked)
        );

        safeguardStakes[msg.sender] = _safeguardStakes - _amount;
        totalStakes -= _amount;
        redemptionRequests[stakedRedemptionRequests[msg.sender]].assetTokenAmount -= _amount;

        _transfer(address(this), msg.sender, _amount);

        emit SafeguardUnstaked(_amount, msg.sender);
    }

    /// @notice kindof modifier to frist-check data on functions
    /// @param modifiers an array containing the modifiers to check (the enums)
    function _checkAccessToFunction(uint256 modifiers) private view {
        bool found;
        AssetTokenData assetTknDtaContract = assetTokenDataAddress;
        if (modifiers & ACTIVE_CONTRACT != 0) {
            assetTknDtaContract.onlyActiveContract(address(this));
            found = true;
        }
        if (modifiers & UNFROZEN_CONTRACT != 0) {
            assetTknDtaContract.onlyUnfrozenContract(address(this));
            found = true;
        }
        if (modifiers & ONLY_ISSUER != 0) {
            assetTknDtaContract.onlyIssuer(address(this), msg.sender);
            found = true;
        }
        if (modifiers & ONLY_ISSUER_OR_GUARDIAN != 0) {
            assetTknDtaContract.onlyIssuerOrGuardian(address(this), msg.sender);
            found = true;
        }
        if (modifiers & ONLY_ISSUER_OR_AGENT != 0) {
            assetTknDtaContract.onlyIssuerOrAgent(address(this), msg.sender);
            found = true;
        }
        require(found, AccessNotFound());
    }
}
"
    },
    "contracts/assetToken/base/AccessManager.sol": {
      "content": "// SPDX-License-Identifier: MIT
pragma solidity ^0.8.28;

import { Ownable } from "solady/src/auth/Ownable.sol";
import { EnumerableRoles } from "solady/src/auth/EnumerableRoles.sol";
import { IAuthorizationContract } from "../../interfaces/IAuthorizationContract.sol";

/// @author Swarm Markets
/// @title Access Manager for AssetToken Contract
/// @notice Contract to manage the Asset Token contracts
abstract contract AccessManager is EnumerableRoles, Ownable {
    error ZeroAddressPassed();
    error NewAgentEqOldAgent(address agent);
    error ListNotOwned(address oldAgent, address sender);

    enum RoleErrorType {
        OnlyIssuerOrAdmin,
        OnlyGuardianOrAdmin,
        OnlyIssuer,
        OnlyAgent,
        OnlyIssuerOrAgent,
        OnlyIssuerOnActive,
        OnlyAgentOrIssuerOnActive,
        OnlyGuardianOnSafeguard
    }
    error RolesError(address caller, RoleErrorType role);

    enum BlacklistErrorType {
        Blacklisted,
        NotBlacklisted
    }
    error BlacklistError(address account, BlacklistErrorType errorType);

    enum AgentErrorType {
        NotExists,
        Exists,
        HasContractsAssigned
    }
    error AgentError(address token, address agent, AgentErrorType errorType);

    enum TokenErrorType {
        NotStored,
        Registered,
        NotFrozen,
        Frozen,
        NotActiveOnSafeguard
    }
    error TokenError(address token, TokenErrorType errorType);

    enum ContractErrorType {
        NotAContract,
        NotFound,
        NotManagedByCaller,
        BelongsToAgent
    }
    error ContractError(address token, address contractAddress, ContractErrorType errorType);

    enum ValidationErrorType {
        ZeroAddressesPassed,
        AuthListEmpty,
        IndexNotExists,
        RemovingFromAuthFailed,
        AuthListOwnershipTransferFailed,
        AgentWithoutContracts
    }
    error ValidationError(ValidationErrorType errorType);

    /// @notice Emitted when changed max quantity
    event ChangedMaxQtyOfAuthorizationLists(address indexed changedBy, uint newQty);

    /// @notice Emitted when Issuer is transferred
    event IssuerTransferred(address indexed _tokenAddress, address indexed _caller, address indexed _newIssuer);
    /// @notice Emitted when Guardian is transferred
    event GuardianTransferred(address indexed _tokenAddress, address indexed _caller, address indexed _newGuardian);

    /// @notice Emitted when Agent is added to the contract
    event AgentAdded(address indexed _tokenAddress, address indexed _caller, address indexed _newAgent);
    /// @notice Emitted when Agent is removed from the contract
    event AgentRemoved(address indexed _tokenAddress, address indexed _caller, address indexed _agent);

    /// @notice Emitted when an Agent list is transferred to another Agent
    event AgentAuthorizationListTransferred(
        address indexed _tokenAddress,
        address _caller,
        address indexed _newAgent,
        address indexed _oldAgent
    );

    /// @notice Emitted when an account is added to the Asset Token Blacklist
    event AddedToBlacklist(address indexed _tokenAddress, address indexed _account, address indexed _from);
    /// @notice Emitted when an account is removed from the Asset Token Blacklist
    event RemovedFromBlacklist(address indexed _tokenAddress, address indexed _account, address indexed _from);

    /// @notice Emitted when a contract is added to the Asset Token Authorization list
    event AddedToAuthorizationContracts(
        address indexed _tokenAddress,
        address indexed _contractAddress,
        address indexed _from
    );
    /// @notice Emitted when a contract is removed from the Asset Token Authorization list
    event RemovedFromAuthorizationContracts(
        address indexed _tokenAddress,
        address indexed _contractAddress,
        address indexed _from
    );

    /// @notice Emitted when an account is granted with the right to transfer on safeguard state
    event AddedTransferOnSafeguardAccount(address indexed _tokenAddress, address indexed _account);
    /// @notice Emitted when an account is revoked the right to transfer on safeguard state
    event RemovedTransferOnSafeguardAccount(address indexed _tokenAddress, address indexed _account);

    /// @notice Emitted when a new Asset Token is deployed and registered
    event TokenRegistered(address indexed _tokenAddress, address _caller);
    /// @notice Emitted when an  Asset Token is deleted
    event TokenDeleted(address indexed _tokenAddress, address _caller);

    /// @notice Emitted when the contract changes to safeguard mode
    event ChangedToSafeGuard(address indexed _tokenAddress);

    /// @notice Emitted when the contract gets frozen
    event FrozenContract(address indexed _tokenAddress);
    /// @notice Emitted when the contract gets unfrozen
    event UnfrozenContract(address indexed _tokenAddress);

    /// @notice Admin role
    uint256 public constant DEFAULT_ADMIN_ROLE = uint256(keccak256("DEFAULT_ADMIN_ROLE"));
    /// @notice Role to be able to deploy an Asset Token
    uint256 public constant ASSET_DEPLOYER_ROLE = uint256(keccak256("ASSET_DEPLOYER_ROLE"));

    /// @dev This is a WAD on DSMATH representing 1
    /// @dev This is a proportion of 1 representing 100%, equal to a WAD
    uint256 public constant DECIMALS = 10 ** 18;

    /// @notice Structure to hold the Token Data
    /// @notice guardian and issuer of the contract
    /// @notice isFrozen: boolean to store if the contract is frozen
    /// @notice isOnSafeguard: state of the contract: false is ACTIVE // true is SAFEGUARD
    /// @notice positiveInterest: if the interest will be a positvie or negative one
    /// @notice interestRate: the interest rate set in AssetTokenData.setInterestRate() (percent per seconds)
    /// @notice rate: the interest determined by the formula. Default is 10**18
    /// @notice lastUpdate: last block where the update function was called
    /// @notice blacklist: account => bool (if bool = true, account is blacklisted)
    /// @notice agents: agents => bool(true or false) (enabled/disabled agent)
    /// @notice safeguardTransferAllow: allow certain addresses to transfer even on safeguard
    /// @notice authorizationsPerAgent: list of contracts of each agent to authorize a user
    /// @notice array of addresses. Each one is a contract with the isTxAuthorized function
    struct TokenData {
        bool isFrozen;
        bool isOnSafeguard;
        bool positiveInterest;
        uint256 interestRate;
        uint256 rate;
        uint256 lastUpdate;
        address issuer;
        address guardian;
        address[] authorizationContracts;
        mapping(address => bool) blacklist;
        mapping(address => bool) agents;
        mapping(address => bool) safeguardTransferAllow;
        mapping(address => address) authorizationsPerAgent;
    }
    /// @notice mapping of TokenData, entered by token Address
    mapping(address => TokenData) public tokensData;

    /// @dev this is just to have an estimation of qty and prevent innecesary looping
    uint256 public maxQtyOfAuthorizationLists;

    modifier requireNonEmptyAddress(address _address) {
        require(_address != address(0), ZeroAddressPassed());
        _;
    }

    /// @notice Check if the token is valid
    /// @param tokenAddress address of the current token being managed
    modifier onlyStoredToken(address tokenAddress) {
        require(tokensData[tokenAddress].issuer != address(0), TokenError(tokenAddress, TokenErrorType.NotStored));
        _;
    }

    /// @notice Check if sender is an AGENT
    /// @param tokenAddress address of the current token being managed
    /// @param functionCaller the caller of the function where this is used
    modifier onlyAgent(address tokenAddress, address functionCaller) {
        require(tokensData[tokenAddress].agents[functionCaller], RolesError(functionCaller, RoleErrorType.OnlyAgent));
        _;
    }

    /// @notice Allow TRANSFER on Safeguard
    /// @param _tokenAddress address of the current token being managed
    /// @param _account the account to grant the right to transfer on safeguard state
    function allowTransferOnSafeguard(address _tokenAddress, address _account) external onlyStoredToken(_tokenAddress) {
        onlyIssuerOrGuardian(_tokenAddress, msg.sender);
        tokensData[_tokenAddress].safeguardTransferAllow[_account] = true;
        emit AddedTransferOnSafeguardAccount(_tokenAddress, _account);
    }

    /// @notice Removed TRANSFER on Safeguard
    /// @param _tokenAddress address of the current token being managed
    /// @param _account the account to be revoked from the right to transfer on safeguard state
    function preventTransferOnSafeguard(
        address _tokenAddress,
        address _account
    ) external onlyStoredToken(_tokenAddress) {
        onlyIssuerOrGuardian(_tokenAddress, msg.sender);
        tokensData[_tokenAddress].safeguardTransferAllow[_account] = false;
        emit RemovedTransferOnSafeguardAccount(_tokenAddress, _account);
    }

    function changeMaxQtyOfAuthorizationLists(uint newMaxQty) public onlyRole(DEFAULT_ADMIN_ROLE) {
        maxQtyOfAuthorizationLists = newMaxQty;
        emit ChangedMaxQtyOfAuthorizationLists(msg.sender, newMaxQty);
    }

    /**
     * @notice Checks if the user is authorized by the agent
     * @dev This function verifies if the `_from` and `_to` addresses are authorized to perform a given `_amount`
     * transaction on the asset token contract `_tokenAddress`.
     * @param _tokenAddress The address of the current token being managed
     * @param _from The address to be checked if it's authorized
     * @param _to The address to be checked if it's authorized
     * @param _amount The amount of the operation to be made
     * @return bool Returns true if `_from` and `_to` are authorized to perform the transaction
     */
    function mustBeAuthorizedHolders(
        address _tokenAddress,
        address _from,
        address _to,
        uint256 _amount
    ) external onlyStoredToken(_tokenAddress) returns (bool) {
        require(msg.sender == _tokenAddress, ContractError(_tokenAddress, msg.sender, ContractErrorType.NotAContract));
        // This line below should never happen. A registered asset token shouldn't call
        // to this function with both addresses (from - to) in ZERO
        require(_from != address(0) || _to != address(0), ValidationError(ValidationErrorType.ZeroAddressesPassed));

        address[2] memory addresses = [_from, _to];
        uint256 response = 0;
        uint256 arrayLength = addresses.length;
        TokenData storage token = tokensData[_tokenAddress];
        for (uint256 i = 0; i < arrayLength; ++i) {
            if (addresses[i] != address(0)) {
                require(!token.blacklist[addresses[i]], BlacklistError(addresses[i], BlacklistErrorType.Blacklisted));

                /// @dev the caller (the asset token contract) is an authorized holder
                if (addresses[i] == _tokenAddress && addresses[i] == msg.sender) {
                    response++;
                    // this is a resource to avoid validating this contract in other system
                    addresses[i] = address(0);
                }
                if (!token.isOnSafeguard) {
                    /// @dev on active state, issuer and agents are authorized holder
                    if (addresses[i] == token.issuer || token.agents[addresses[i]]) {
                        response++;
                        // this is a resource to avoid validating agent/issuer in other system
                        addresses[i] = address(0);
                    }
                } else {
                    /// @dev on safeguard state, guardian is authorized holder
                    if (addresses[i] == token.guardian) {
                        response++;
                        // this is a resource to avoid validating guardian in other system
                        addresses[i] = address(0);
                    }
                }

                /// each of these if statements are mutually exclusive, so response cannot be more than 2
            }
        }

        /// if response is more than 0 none of the address are:
        /// the asset token contract itself, agents, issuer or guardian
        /// if response is 1 there is one address which is one of the above
        /// if response is 2 both addresses are one of the above, no need to iterate in external list
        if (response < 2) {
            uint256 length = token.authorizationContracts.length;
            require(length > 0, ValidationError(ValidationErrorType.AuthListEmpty));
            for (uint256 i = 0; i < length; ++i) {
                if (
                    IAuthorizationContract(token.authorizationContracts[i]).isTxAuthorized(
                        _tokenAddress,
                        addresses[0],
                        addresses[1],
                        _amount
                    )
                ) {
                    return true;
                }
            }
        } else {
            return true;
        }
        return false;
    }

    /// @notice Changes the ISSUER
    /// @param _tokenAddress address of the current token being managed
    /// @param _newIssuer to be assigned in the contract
    function transferIssuer(address _tokenAddress, address _newIssuer) external onlyStoredToken(_tokenAddress) {
        TokenData storage tokenData = tokensData[_tokenAddress];
        require(
            msg.sender == tokenData.issuer || hasRole(msg.sender, DEFAULT_ADMIN_ROLE),
            RolesError(msg.sender, RoleErrorType.OnlyIssuerOrAdmin)
        );
        tokenData.issuer = _newIssuer;
        emit IssuerTransferred(_tokenAddress, msg.sender, _newIssuer);
    }

    /// @notice Changes the GUARDIAN
    /// @param _tokenAddress address of the current token being managed
    /// @param _newGuardian to be assigned in the contract
    function transferGuardian(address _tokenAddress, address _newGuardian) external onlyStoredToken(_tokenAddress) {
        TokenData storage tokenData = tokensData[_tokenAddress];
        require(
            msg.sender == tokenData.guardian || hasRole(msg.sender, DEFAULT_ADMIN_ROLE),
            RolesError(msg.sender, RoleErrorType.OnlyGuardianOrAdmin)
        );
        tokenData.guardian = _newGuardian;
        emit GuardianTransferred(_tokenAddress, msg.sender, _newGuardian);
    }

    /// @notice Adds an AGENT
    /// @param _tokenAddress address of the current token being managed
    /// @param _newAgent to be added
    function addAgent(address _tokenAddress, address _newAgent) external onlyStoredToken(_tokenAddress) {
        onlyIssuerOrGuardian(_tokenAddress, msg.sender);
        TokenData storage tokenData = tokensData[_tokenAddress];
        require(!tokenData.agents[_newAgent], AgentError(_tokenAddress, _newAgent, AgentErrorType.Exists));
        tokenData.agents[_newAgent] = true;
        emit AgentAdded(_tokenAddress, msg.sender, _newAgent);
    }

    /// @notice Deletes an AGENT
    /// @param _tokenAddress address of the current token being managed
    /// @param _agent to be removed
    function removeAgent(address _tokenAddress, address _agent) external onlyStoredToken(_tokenAddress) {
        onlyIssuerOrGuardian(_tokenAddress, msg.sender);
        TokenData storage tokenData = tokensData[_tokenAddress];
        require(tokenData.agents[_agent], AgentError(_tokenAddress, _agent, AgentErrorType.NotExists));

        require(
            !_agentHasContractsAssigned(_tokenAddress, _agent),
            AgentError(_tokenAddress, _agent, AgentErrorType.HasContractsAssigned)
        );

        delete tokenData.agents[_agent];
        emit AgentRemoved(_tokenAddress, msg.sender, _agent);
    }

    /// @notice Transfers the authorization contracts to a new Agent
    /// @param _tokenAddress address of the current token being managed
    /// @param _newAgent to link the authorization list
    /// @param _oldAgent to unlink the authrization list
    function transferAgentList(
        address _tokenAddress,
        address _newAgent,
        address _oldAgent
    ) external onlyStoredToken(_tokenAddress) {
        TokenData storage tokenData = tokensData[_tokenAddress];

        if (!tokenData.isOnSafeguard) {
            require(
                msg.sender == tokenData.issuer || tokenData.agents[msg.sender],
                RolesError(msg.sender, RoleErrorType.OnlyAgentOrIssuerOnActive)
            );
        } else {
            require(msg.sender == tokenData.guardian, RolesError(msg.sender, RoleErrorType.OnlyGuardianOnSafeguard));
        }
        require(tokenData.authorizationContracts.length > 0, ValidationError(ValidationErrorType.AuthListEmpty));
        require(_newAgent != _oldAgent, NewAgentEqOldAgent(_newAgent));
        require(tokenData.agents[_oldAgent], AgentError(_tokenAddress, _oldAgent, AgentErrorType.NotExists));

        if (msg.sender != tokenData.issuer && msg.sender != tokenData.guardian) {
            require(_oldAgent == msg.sender, ListNotOwned(_oldAgent, msg.sender));
        }
        require(tokenData.agents[_newAgent], AgentError(_tokenAddress, _newAgent, AgentErrorType.NotExists));

        (bool executionOk, bool changed) = _changeAuthorizationOwnership(_tokenAddress, _newAgent, _oldAgent);
        // this 2 lines below should never happen. The change list owner should always be successfull
        // because of the requires validating the information before calling _changeAuthorizationOwnership
        require(executionOk, ValidationError(ValidationErrorType.AuthListOwnershipTransferFailed));
        require(changed, ValidationError(ValidationErrorType.AgentWithoutContracts));
        emit AgentAuthorizationListTransferred(_tokenAddress, msg.sender, _newAgent, _oldAgent);
    }

    /// @notice Adds an address to the authorization list
    /// @param _tokenAddress address of the current token being managed
    /// @param _contractAddress the address to be added
    function addToAuthorizationList(
        address _tokenAddress,
        address _contractAddress
    ) external onlyStoredToken(_tokenAddress) onlyAgent(_tokenAddress, msg.sender) {
        require(
            _isContract(_contractAddress),
            ContractError(_tokenAddress, _contractAddress, ContractErrorType.NotAContract)
        );
        TokenData storage tokenData = tokensData[_tokenAddress];
        require(
            tokenData.authorizationsPerAgent[_contractAddress] == address(0),
            ContractError(_tokenAddress, _contractAddress, ContractErrorType.BelongsToAgent)
        );
        tokenData.authorizationContracts.push(_contractAddress);
        tokenData.authorizationsPerAgent[_contractAddress] = msg.sender;
        emit AddedToAuthorizationContracts(_tokenAddress, _contractAddress, msg.sender);
    }

    /// @notice Removes an address from the authorization list
    /// @param _tokenAddress address of the current token being managed
    /// @param _contractAddress the address to be removed
    function removeFromAuthorizationList(
        address _tokenAddress,
        address _contractAddress
    ) external onlyStoredToken(_tokenAddress) onlyAgent(_tokenAddress, msg.sender) {
        require(
            _isContract(_contractAddress),
            ContractError(_tokenAddress, _contractAddress, ContractErrorType.NotAContract)
        );
        TokenData storage tokenData = tokensData[_tokenAddress];
        require(
            tokenData.authorizationsPerAgent[_contractAddress] != address(0),
            ContractError(_tokenAddress, _contractAddress, ContractErrorType.NotFound)
        );
        require(
            tokenData.authorizationsPerAgent[_contractAddress] == msg.sender,
            ContractError(_tokenAddress, _contractAddress, ContractErrorType.NotManagedByCaller)
        );

        // this line below should never happen. The removal should always be successfull
        // because of the require validating the caller before _removeFromAuthorizationArray
        require(
            _removeFromAuthorizationArray(_tokenAddress, _contractAddress),
            ValidationError(ValidationErrorType.RemovingFromAuthFailed)
        );

        delete tokenData.authorizationsPerAgent[_contractAddress];
        emit RemovedFromAuthorizationContracts(_tokenAddress, _contractAddress, msg.sender);
    }

    /// @notice Adds an address to the blacklist
    /// @param _tokenAddress address of the current token being managed
    /// @param _account the address to be blacklisted
    function addMemberToBlacklist(address _tokenAddress, address _account) external onlyStoredToken(_tokenAddress) {
        onlyIssuerOrGuardian(_tokenAddress, msg.sender);
        TokenData storage tokenData = tokensData[_tokenAddress];
        require(!tokenData.blacklist[_account], BlacklistError(_account, BlacklistErrorType.Blacklisted));
        tokenData.blacklist[_account] = true;
        emit AddedToBlacklist(_tokenAddress, _account, msg.sender);
    }

    /// @notice Removes an address from the blacklist
    /// @param _tokenAddress address of the current token being managed
    /// @param _account the address to be removed from the blacklisted
    function removeMemberFromBlacklist(
        address _tokenAddress,
        address _account
    ) external onlyStoredToken(_tokenAddress) {
        onlyIssuerOrGuardian(_tokenAddress, msg.sender);
        TokenData storage tokenData = tokensData[_tokenAddress];
        require(tokenData.blacklist[_account], BlacklistError(_account, BlacklistErrorType.NotBlacklisted));
        delete tokenData.blacklist[_account];
        emit RemovedFromBlacklist(_tokenAddress, _account, msg.sender);
    }

    /// @notice Register the asset tokens and its rates in this contract
    /// @param _tokenAddress address of the current token being managed
    /// @param _issuer address of the contract issuer
    /// @param _guardian address of the contract guardian
    /// @return bool true if operation was successful
    function registerAssetToken(
        address _tokenAddress,
        address _issuer,
        address _guardian
    )
        external
        onlyRole(ASSET_DEPLOYER_ROLE)
        requireNonEmptyAddress(_tokenAddress)
        requireNonEmptyAddress(_issuer)
        requireNonEmptyAddress(_guardian)
        returns (bool)
    {
        TokenData storage tokenData = tokensData[_tokenAddress];
        require(tokenData.issuer == address(0), TokenError(_tokenAddress, TokenErrorType.Registered));
        require(
            _isContract(_tokenAddress),
            ContractError(_tokenAddress, address(this), ContractErrorType.NotAContract)
        );

        tokenData.issuer = _issuer;
        tokenData.guardian = _guardian;
        tokenData.rate = DECIMALS;
        tokenData.lastUpdate = block.timestamp;

        emit TokenRegistered(_tokenAddress, msg.sender);
        return true;
    }

    /// @notice Deletes the asset token from this contract
    /// @notice It has no real use (I think should be removed)
    /// @param _tokenAddress address of the current token being managed
    function deleteAssetToken(address _tokenAddress) external onlyStoredToken(_tokenAddress) {
        onlyUnfrozenContract(_tokenAddress);
        onlyIssuerOrGuardian(_tokenAddress, msg.sender);
        delete tokensData[_tokenAddress];
        emit TokenDeleted(_tokenAddress, msg.sender);
    }

    /// @notice Set the contract into Safeguard)
    /// @param _tokenAddress address of the current token being managed
    /// @return bool true if operation was successful
    function setContractToSafeguard(address _tokenAddress) external onlyStoredToken(_tokenAddress) returns (bool) {
        onlyUnfrozenContract(_tokenAddress);
        onlyActiveContract(_tokenAddress);
        require(msg.sender == _tokenAddress, ContractError(_tokenAddress, msg.sender, ContractErrorType.NotAContract));
        tokensData[_tokenAddress].isOnSafeguard = true;
        emit ChangedToSafeGuard(_tokenAddress);
        return true;
    }

    /// @notice Freeze the contract
    /// @param _tokenAddress address of the current token being managed
    /// @return bool true if operation was successful
    function freezeContract(address _tokenAddress) external onlyStoredToken(_tokenAddress) returns (bool) {
        require(msg.sender == _tokenAddress, ContractError(_tokenAddress, msg.sender, ContractErrorType.NotAContract));
        TokenData storage tokenData = tokensData[_tokenAddress];
        require(!tokenData.isFrozen, TokenError(_tokenAddress, TokenErrorType.Frozen));

        tokenData.isFrozen = true;
        emit FrozenContract(_tokenAddress);
        return true;
    }

    /// @notice Unfreeze the contract
    /// @param _tokenAddress address of the current token being managed
    /// @return bool true if operation was successful
    function unfreezeContract(address _tokenAddress) external onlyStoredToken(_tokenAddress) returns (bool) {
        require(msg.sender == _tokenAddress, ContractError(_tokenAddress, msg.sender, ContractErrorType.NotAContract));
        TokenData storage tokenData = tokensData[_tokenAddress];
        require(tokenData.isFrozen, TokenError(_tokenAddress, TokenErrorType.NotFrozen));

        tokenData.isFrozen = false;
        emit UnfrozenContract(_tokenAddress);
        return true;
    }

    /// @notice Check if the token contract is Active
    /// @param _tokenAddress address of the current token being managed
    function onlyActiveContract(address _tokenAddress) public view {
        require(
            !tokensData[_tokenAddress].isOnSafeguard,
            TokenError(_tokenAddress, TokenErrorType.NotActiveOnSafeguard)
        );
    }

    /// @notice Check if the token contract is Not frozen
    /// @param tokenAddress address of the current token being managed
    function onlyUnfrozenContract(address tokenAddress) public view {
        require(!tokensData[tokenAddress].isFrozen, TokenError(tokenAddress, TokenErrorType.Frozen));
    }

    /// @notice Check if sender is the ISSUER
    /// @param _tokenAddress address of the current token being managed
    /// @param _functionCaller the caller of the function where this is used
    function onlyIssuer(address _tokenAddress, address _functionCaller) external view {
        require(
            _functionCaller == tokensData[_tokenAddress].issuer,
            RolesError(_functionCaller, RoleErrorType.OnlyIssuer)
        );
    }

    /// @notice Check if sender is AGENT_or ISSUER
    /// @param _tokenAddress address of the current token being managed
    /// @param _functionCaller the caller of the function where this is used
    function onlyIssuerOrAgent(address _tokenAddress, address _functionCaller) external view {
        TokenData storage data = tokensData[_tokenAddress];
        require(
            _functionCaller == data.issuer || data.agents[_functionCaller],
            RolesError(_functionCaller, RoleErrorType.OnlyIssuerOrAgent)
        );
    }

    /// @notice Check if sender is GUARDIAN or ISSUER
    /// @param _tokenAddress address of the current token being managed
    /// @param _functionCaller the caller of the function where this is used
    function onlyIssuerOrGuardian(address _tokenAddress, address _functionCaller) public view {
        TokenData storage data = tokensData[_tokenAddress];
        if (data.isOnSafeguard) {
            require(
                _functionCaller == data.guardian,
                RolesError(_functionCaller, RoleErrorType.OnlyGuardianOnSafeguard)
            );
        } else {
            require(_functionCaller == data.issuer, RolesError(_functionCaller, RoleErrorType.OnlyIssuerOnActive));
        }
    }

    /// @notice Return if the account can transfer on safeguard
    /// @param _tokenAddress address of the current token being managed
    /// @param _account the account to get info from
    /// @return isAllowed bool true or false
    function isAllowedTransferOnSafeguard(
        address _tokenAddress,
        address _account
    ) external view onlyStoredToken(_tokenAddress) returns (bool isAllowed) {
        isAllowed = tokensData[_tokenAddress].safeguardTransferAllow[_account];
    }

    /// @notice Get if the contract is on SafeGuard or not
    /// @param _tokenAddress address of the current token being managed
    /// @return bool true if the contract is on SafeGuard
    function isOnSafeguard(address _tokenAddress) external view onlyStoredToken(_tokenAddress) returns (bool) {
        return tokensData[_tokenAddress].isOnSafeguard;
    }

    /// @notice Get if the contract is frozen or not
    /// @param _tokenAddress address of the current token being managed
    /// @return isFrozen bool true if the contract is frozen
    function isContractFrozen(
        address _tokenAddress
    ) external view onlyStoredToken(_tokenAddress) returns (bool isFrozen) {
        isFrozen = tokensData[_tokenAddress].isFrozen;
    }

    /// @notice Get the issuer of the asset token
    /// @param _tokenAddress address of the current token being managed
    /// @return address the issuer address
    function getIssuer(address _tokenAddress) external view onlyStoredToken(_tokenAddress) returns (address) {
        return tokensData[_tokenAddress].issuer;
    }

    /// @notice Get the guardian of the asset token
    /// @param _tokenAddress address of the current token being managed
    /// @return address the guardian address
    function getGuardian(address _tokenAddress) external view onlyStoredToken(_tokenAddress) returns (address) {
        return tokensData[_tokenAddress].guardian;
    }

    /// @notice Get if the account is blacklisted for the asset token
    /// @param _tokenAddress address of the current token being managed
    /// @return blacklisted bool true if the account is blacklisted
    function isBlacklisted(
        address _tokenAddress,
        address _account
    ) external view onlyStoredToken(_tokenAddress) returns (bool blacklisted) {
        blacklisted = tokensData[_tokenAddress].blacklist[_account];
    }

    /// @notice Get if the account is an agent of the asset token
    /// @param _tokenAddress address of the current token being managed
    /// @return _agent bool true if account is an agent
    function isAgent(
        address _tokenAddress,
        address _agentAddress
    ) external view onlyStoredToken(_tokenAddress) returns (bool _agent) {
        _agent = tokensData[_tokenAddress].agents[_agentAddress];
    }

    /// @notice Get the agent address who was responsable of the validation contract (_contractAddress)
    /// @param _tokenAddress address of the current token being managed
    /// @return addedBy address of the agent
    function authorizationContractAddedBy(
        address _tokenAddress,
        address _contractAddress
    ) external view onlyStoredToken(_tokenAddress) returns (address addedBy) {
        addedBy = tokensData[_tokenAddress].authorizationsPerAgent[_contractAddress];
    }

    /// @notice Get the position (index) in the authorizationContracts array of the authorization contract
    /// @param _tokenAddress address of the current token being managed
    /// @return uint256 the index of the array
    function getIndexByAuthorizationAddress(
        address _tokenAddress,
        address _authorizationContractAddress
    ) external view onlyStoredToken(_tokenAddress) returns (uint256) {
        TokenData storage token = tokensData[_tokenAddress];
        uint256 length = token.authorizationContracts.length;
        for (uint256 i = 0; i < length; ++i) {
            if (token.authorizationContracts[i] == _authorizationContractAddress) {
                return i;
            }
        }
        /// @dev returning this when address is not found
        return maxQtyOfAuthorizationLists + 1;
    }

    /// @notice Get the authorization contract address given an index in authorizationContracts array
    /// @param _tokenAddress address of the current token being managed
    /// @return addressByIndex address the address of the authorization contract
    function getAuthorizationAddressByIndex(
        address _tokenAddress,
        uint256 _index
    ) external view returns (address addressByIndex) {
        TokenData storage token = tokensData[_tokenAddress];
        require(_index < token.authorizationContracts.length, ValidationError(ValidationErrorType.IndexNotExists));
        addressByIndex = token.authorizationContracts[_index];
    }

    /* *
     * [IMPORTANT]
     * ====
     * It is unsafe to assume that an address for which this function returns
     * false is an externally-owned account (EOA) and not a contract.
     *
     * Among others, `isContract` will return false for the following
     * types of addresses:
     *
     *  - an externally-owned account
     *  - a contract in construction
     *  - an address where a contract will be created
     *  - an address where a contract lived, but was destroyed
     * ====
     */

    /// @notice Returns true if `account` is a contract
    /// @param _contractAddress the address to be ckecked
    /// @return bool if `account` is a contract
    function _isContract(address _contractAddress) internal view returns (bool) {
        // This method relies on extcodesize, which returns 0 for contracts in
        // construction, since the code is only stored at the end of the
        // constructor execution.

        uint256 size;
        // solhint-disable-next-line no-inline-assembly
        assembly {
            size := extcodesize(_contractAddress)
        }
        return size > 0;
    }

    /// @notice checks if the agent has a contract from the array list assigned
    /// @param _tokenAddress address of the current token being managed
    /// @param _agent agent to check
    /// @return bool if the agent has any contract assigned
    function _agentHasContractsAssigned(address _tokenAddress, address _agent) internal view returns (bool) {
        TokenData storage token = tokensData[_tokenAddress];
        uint256 length = token.authorizationContracts.length;
        for (uint256 i = 0; i < length; ++i) {
            if (token.authorizationsPerAgent[token.authorizationContracts[i]] == _agent) {
                return true;
            }
        }
        return false;
    }

    /// @notice changes the owner of the contracts auth array
    /// @param _tokenAddress address of the current token being managed
    /// @param _newAgent target agent to link the contracts to
    /// @param _oldAgent source agent to unlink the contracts from
    /// @return bool true if there was no error
    /// @return bool true if authorization ownership has occurred
    function _changeAuthorizationOwnership(
        address _tokenAddress,
        address _newAgent,
        address _oldAgent
    ) internal returns (bool, bool) {
        bool changed = false;
        TokenData storage token = tokensData[_tokenAddress];
        uint256 length = token.authorizationContracts.length;
        for (uint256 i = 0; i < length; ++i) {
            if (token.authorizationsPerAgent[token.authorizationContracts[i]] == _oldAgent) {
                token.authorizationsPerAgent[token.authorizationContracts[i]] = _newAgent;
                changed = true;
            }
        }
        return (true, changed);
    }

    /// @notice removes contract from auth array
    /// @param _tokenAddress address of the current token being managed
    /// @param _contractAddress to be removed
    /// @return bool if address was removed
    function _removeFromAuthorizationArray(address _tokenAddress, address _contractAddress) internal returns (bool) {
        TokenData storage token = tokensData[_tokenAddress];
        uint256 length = token.authorizationContracts.length;
        for (uint256 i = 0; i < length; ++i) {
            if (token.authorizationContracts[i] == _contractAddress) {
                token.authorizationContracts[i] = token.authorizationContracts[length - 1];
                token.authorizationContracts.pop();
                return true;
            }
        }
        // This line below should never happen. Before calling this function,
        // it is known that the address exists in the array
        return false;
    }
}
"
    },
    "contracts/assetToken/base/AssetTokenData.sol": {
      "content": "// SPDX-License-Identifier: MIT
pragma solidity ^0.8.28;

import { FixedPointMathLib } from "solady/src/utils/FixedPointMathLib.sol";
import { AccessManager } from "./AccessManager.sol";

/// @author Swarm Markets
/// @title Asset Token Data for Asset Token Contract
/// @notice Manages interest rate calculations for Asset Tokens
contract AssetTokenData is AccessManager {
    using FixedPointMathLib for uint256;

    error MaxQtyOfAuthorizationListsError(uint256 maxQtyOfAuthorizationLists);
    error InterestRateError(uint256 interestRate, uint256 HUNDRED_PERCENT_ANNUAL);

    /// @notice Emitted when the interest rate is set
    event InterestRateStored(
        address indexed token,
        address indexed caller,
        uint256 interestRate,
        bool positiveInterest
    );

    /// @notice Emitted when the rate gets updated
    event RateUpdated(address indexed token, address indexed caller, uint256 newRate, bool positiveInterest);

    uint256 public constant HUNDRED_PERCENT_ANNUAL = 21979553151;

    /// @notice Constructor
    /// @param _maxQtyOfAuthorizationLists max qty for addresses to be added in the authorization list
    constructor(uint256 _maxQtyOfAuthorizationLists) {
        require(
            _maxQtyOfAuthorizationLists > 0 && _maxQtyOfAuthorizationLists < 100,
            MaxQtyOfAuthorizationListsError(_maxQtyOfAuthorizationLists)
        );

        maxQtyOfAuthorizationLists = _maxQtyOfAuthorizationLists;
        _initializeOwner(msg.sender);
        _setRole(msg.sender, DEFAULT_ADMIN_ROLE, true);
    }

    /// @notice Gets the interest rate and positive/negative interest value
    /// @param token address of the current token being managed
    /// @return rate uint256 the interest rate
    /// @return isPositive bool true if it is positive interest, false if it is not
    function getInterestRate(
        address token
    ) external view onlyStoredToken(token) returns (uint256 rate, bool isPositive) {
        TokenData storage data = tokensData[token];
        return (data.interestRate, data.positiveInterest);
    }

    /// @notice Gets the current rate
    /// @param token address of the current token being managed
    /// @return rate uint256 the rate
    function getCurrentRate(address token) external view onlyStoredToken(token) returns (uint256) {
        return tokensData[token].rate;
    }

    /// @notice Gets the timestamp of the last update
    /// @param token address of the current token being managed
    /// @return lastUpd uint256 the last update in block.timestamp format
    function getLastUpdate(address token) external view onlyStoredToken(token) returns (uint256) {
        return tokensData[token].lastUpdate;
    }

    /// @notice Sets the new intereset rate
    /// @param token address of the current token being managed
    /// @param interestRate the value to be set (the value is in percent per seconds)
    /// @param positiveInterest if it's a negative or positive interest
    function setInterestRate(
        address token,
        uint256 interestRate,
        bool positiveInterest
    ) external onlyStoredToken(token) {
        onlyIssuerOrGuardian(token, msg.sender);
        require(interestRate <= HUNDRED_PERCENT_ANNUAL, InterestRateError(interestRate, HUNDRED_PERCENT_ANNUAL));

        update(token);

        TokenData storage data = tokensData[token];
        data.interestRate = interestRate;
        data.positiveInterest = positiveInterest;

     

Tags:
ERC20, Multisig, Mintable, Burnable, Upgradeable, Multi-Signature, Factory|addr:0xf49474ed86ba26eefd3108717cec33c05e67c1bb|verified:true|block:23398092|tx:0xba0037037ee25e272ed3e69cfbb8544b3b98a6fd87f032c1f6862bc3d58993ed|first_check:1758296946

Submitted on: 2025-09-19 17:49:07

Comments

Log in to comment.

No comments yet.