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;
Submitted on: 2025-09-19 17:49:07
Comments
Log in to comment.
No comments yet.