Files
zeekay 7b9ab37182 fix: __Ownable_init(msg.sender) for OZ v5 + constructor Ownable(msg.sender)
OpenZeppelin v5 dropped parameterless __Ownable_init() and Ownable() in
favor of explicit owner argument. Required for solc 0.8.31 + OZ v5 build
(see liquidityio/contracts foundry.toml).

10 files: ModularCompliance, AbstractModuleUpgradeable, TREXFactory,
TREXImplementationAuthority, ClaimTopicsRegistry, IdentityRegistry,
IdentityRegistryStorage, TrustedIssuersRegistry, AgentRole, Token.
2026-05-04 11:11:35 -07:00

596 lines
22 KiB
Solidity
Executable File

// SPDX-License-Identifier: GPL-3.0
//
// :+#####%%%%%%%%%%%%%%+
// .-*@@@%+.:+%@@@@@%%#***%@@%=
// :=*%@@@#=. :#@@% *@@@%=
// .-+*%@%*-.:+%@@@@@@+. -*+: .=#. :%@@@%-
// :=*@@@@%%@@@@@@@@@%@@@- .=#@@@%@%= =@@@@#.
// -=+#%@@%#*=:. :%@@@@%. -*@@#*@@@@@@@#=:- *@@@@+
// =@@%=:. :=: *@@@@@%#- =%*%@@@@#+-. =+ :%@@@%-
// -@@%. .+@@@ =+=-. @@#- +@@@%- =@@@@%:
// :@@@. .+@@#%: : .=*=-::.-%@@@+*@@= +@@@@#.
// %@@: +@%%* =%@@@@@@@@@@@#. .*@%- +@@@@*.
// #@@= .+@@@@%:=*@@@@@- :%@%: .*@@@@+
// *@@* +@@@#-@@%-:%@@* +@@#. :%@@@@-
// -@@% .:-=++*##%%%@@@@@@@@@@@@*. :@+.@@@%: .#@@+ =@@@@#:
// .@@@*-+*#%%%@@@@@@@@@@@@@@@@%%#**@@%@@@. *@=*@@# :#@%= .#@@@@#-
// -%@@@@@@@@@@@@@@@*+==-:-@@@= *@# .#@*-=*@@@@%= -%@@@* =@@@@@%-
// -+%@@@#. %@%%= -@@:+@: -@@* *@@*-:: -%@@%=. .*@@@@@#
// *@@@* +@* *@@##@@- #@*@@+ -@@= . :+@@@#: .-+@@@%+-
// +@@@%*@@:..=@@@@* .@@@* .#@#. .=+- .=%@@@*. :+#@@@@*=:
// =@@@@%@@@@@@@@@@@@@@@@@@@@@@%- :+#*. :*@@@%=. .=#@@@@%+:
// .%@@= ..... .=#@@+. .#@@@*: -*%@@@@%+.
// +@@#+===---:::... .=%@@*- +@@@+. -*@@@@@%+.
// -@@@@@@@@@@@@@@@@@@@@@@%@@@@= -@@@+ -#@@@@@#=.
// ..:::---===+++***###%%%@@@#- .#@@+ -*@@@@@#=.
// @@@@@@+. +@@*. .+@@@@@%=.
// -@@@@@= =@@%: -#@@@@%+.
// +@@@@@. =@@@= .+@@@@@*:
// #@@@@#:%@@#. :*@@@@#-
// @@@@@%@@@= :#@@@@+.
// :@@@@@@@#.:#@@@%-
// +@@@@@@-.*@@@*:
// #@@@@#.=@@@+.
// @@@@+-%@%=
// :@@@#%@%=
// +@@@@%-
// :#%%=
//
/**
* NOTICE
*
* The T-REX software is licensed under a proprietary license or the GPL v.3.
* If you choose to receive it under the GPL v.3 license, the following applies:
* T-REX is a suite of smart contracts implementing the ERC-3643 standard and
* developed by Tokeny to manage and transfer financial assets on EVM blockchains
*
* Copyright (C) 2023, Tokeny sàrl.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
pragma solidity ^0.8.17;
import "./IToken.sol";
import "@onchain-id/solidity/contracts/interface/IIdentity.sol";
import "./TokenStorage.sol";
import "../roles/AgentRoleUpgradeable.sol";
contract Token is IToken, AgentRoleUpgradeable, TokenStorage {
/// modifiers
/// @dev Modifier to make a function callable only when the contract is not paused.
modifier whenNotPaused() {
require(!_tokenPaused, "Pausable: paused");
_;
}
/// @dev Modifier to make a function callable only when the contract is paused.
modifier whenPaused() {
require(_tokenPaused, "Pausable: not paused");
_;
}
/**
* @dev the constructor initiates the token contract
* msg.sender is set automatically as the owner of the smart contract
* @param _identityRegistry the address of the Identity registry linked to the token
* @param _compliance the address of the compliance contract linked to the token
* @param _name the name of the token
* @param _symbol the symbol of the token
* @param _decimals the decimals of the token
* @param _onchainID the address of the onchainID of the token
* emits an `UpdatedTokenInformation` event
* emits an `IdentityRegistryAdded` event
* emits a `ComplianceAdded` event
*/
function init(
address _identityRegistry,
address _compliance,
string memory _name,
string memory _symbol,
uint8 _decimals,
// _onchainID can be zero address if not set, can be set later by owner
address _onchainID
) external initializer {
// that require is protecting legacy versions of TokenProxy contracts
// as there was a bug with the initializer modifier on these proxies
// that check is preventing attackers to call the init functions on those
// legacy contracts.
require(owner() == address(0), "already initialized");
require(
_identityRegistry != address(0)
&& _compliance != address(0)
, "invalid argument - zero address");
require(
keccak256(abi.encode(_name)) != keccak256(abi.encode(""))
&& keccak256(abi.encode(_symbol)) != keccak256(abi.encode(""))
, "invalid argument - empty string");
require(0 <= _decimals && _decimals <= 18, "decimals between 0 and 18");
__Ownable_init(msg.sender);
_tokenName = _name;
_tokenSymbol = _symbol;
_tokenDecimals = _decimals;
_tokenOnchainID = _onchainID;
_tokenPaused = true;
setIdentityRegistry(_identityRegistry);
setCompliance(_compliance);
emit UpdatedTokenInformation(_tokenName, _tokenSymbol, _tokenDecimals, _TOKEN_VERSION, _tokenOnchainID);
}
/**
* @dev See {IERC20-approve}.
*/
function approve(address _spender, uint256 _amount) external virtual override returns (bool) {
_approve(msg.sender, _spender, _amount);
return true;
}
/**
* @dev See {ERC20-increaseAllowance}.
*/
function increaseAllowance(address _spender, uint256 _addedValue) external virtual returns (bool) {
_approve(msg.sender, _spender, _allowances[msg.sender][_spender] + (_addedValue));
return true;
}
/**
* @dev See {ERC20-decreaseAllowance}.
*/
function decreaseAllowance(address _spender, uint256 _subtractedValue) external virtual returns (bool) {
_approve(msg.sender, _spender, _allowances[msg.sender][_spender] - _subtractedValue);
return true;
}
/**
* @dev See {IToken-setName}.
*/
function setName(string calldata _name) external override onlyOwner {
require(keccak256(abi.encode(_name)) != keccak256(abi.encode("")), "invalid argument - empty string");
_tokenName = _name;
emit UpdatedTokenInformation(_tokenName, _tokenSymbol, _tokenDecimals, _TOKEN_VERSION, _tokenOnchainID);
}
/**
* @dev See {IToken-setSymbol}.
*/
function setSymbol(string calldata _symbol) external override onlyOwner {
require(keccak256(abi.encode(_symbol)) != keccak256(abi.encode("")), "invalid argument - empty string");
_tokenSymbol = _symbol;
emit UpdatedTokenInformation(_tokenName, _tokenSymbol, _tokenDecimals, _TOKEN_VERSION, _tokenOnchainID);
}
/**
* @dev See {IToken-setOnchainID}.
* if _onchainID is set at zero address it means no ONCHAINID is bound to this token
*/
function setOnchainID(address _onchainID) external override onlyOwner {
_tokenOnchainID = _onchainID;
emit UpdatedTokenInformation(_tokenName, _tokenSymbol, _tokenDecimals, _TOKEN_VERSION, _tokenOnchainID);
}
/**
* @dev See {IToken-pause}.
*/
function pause() external override onlyAgent whenNotPaused {
_tokenPaused = true;
emit Paused(msg.sender);
}
/**
* @dev See {IToken-unpause}.
*/
function unpause() external override onlyAgent whenPaused {
_tokenPaused = false;
emit Unpaused(msg.sender);
}
/**
* @dev See {IToken-batchTransfer}.
*/
function batchTransfer(address[] calldata _toList, uint256[] calldata _amounts) external override {
for (uint256 i = 0; i < _toList.length; i++) {
transfer(_toList[i], _amounts[i]);
}
}
/**
* @notice ERC-20 overridden function that include logic to check for trade validity.
* Require that the from and to addresses are not frozen.
* Require that the value should not exceed available balance .
* Require that the to address is a verified address
* @param _from The address of the sender
* @param _to The address of the receiver
* @param _amount The number of tokens to transfer
* @return `true` if successful and revert if unsuccessful
*/
function transferFrom(
address _from,
address _to,
uint256 _amount
) external override whenNotPaused returns (bool) {
require(!_frozen[_to] && !_frozen[_from], "wallet is frozen");
require(_amount <= balanceOf(_from) - (_frozenTokens[_from]), "Insufficient Balance");
if (_tokenIdentityRegistry.isVerified(_to) && _tokenCompliance.canTransfer(_from, _to, _amount)) {
_approve(_from, msg.sender, _allowances[_from][msg.sender] - (_amount));
_transfer(_from, _to, _amount);
_tokenCompliance.transferred(_from, _to, _amount);
return true;
}
revert("Transfer not possible");
}
/**
* @dev See {IToken-batchForcedTransfer}.
*/
function batchForcedTransfer(
address[] calldata _fromList,
address[] calldata _toList,
uint256[] calldata _amounts
) external override {
for (uint256 i = 0; i < _fromList.length; i++) {
forcedTransfer(_fromList[i], _toList[i], _amounts[i]);
}
}
/**
* @dev See {IToken-batchMint}.
*/
function batchMint(address[] calldata _toList, uint256[] calldata _amounts) external override {
for (uint256 i = 0; i < _toList.length; i++) {
mint(_toList[i], _amounts[i]);
}
}
/**
* @dev See {IToken-batchBurn}.
*/
function batchBurn(address[] calldata _userAddresses, uint256[] calldata _amounts) external override {
for (uint256 i = 0; i < _userAddresses.length; i++) {
burn(_userAddresses[i], _amounts[i]);
}
}
/**
* @dev See {IToken-batchSetAddressFrozen}.
*/
function batchSetAddressFrozen(address[] calldata _userAddresses, bool[] calldata _freeze) external override {
for (uint256 i = 0; i < _userAddresses.length; i++) {
setAddressFrozen(_userAddresses[i], _freeze[i]);
}
}
/**
* @dev See {IToken-batchFreezePartialTokens}.
*/
function batchFreezePartialTokens(address[] calldata _userAddresses, uint256[] calldata _amounts) external override {
for (uint256 i = 0; i < _userAddresses.length; i++) {
freezePartialTokens(_userAddresses[i], _amounts[i]);
}
}
/**
* @dev See {IToken-batchUnfreezePartialTokens}.
*/
function batchUnfreezePartialTokens(address[] calldata _userAddresses, uint256[] calldata _amounts) external override {
for (uint256 i = 0; i < _userAddresses.length; i++) {
unfreezePartialTokens(_userAddresses[i], _amounts[i]);
}
}
/**
* @dev See {IToken-recoveryAddress}.
*/
function recoveryAddress(
address _lostWallet,
address _newWallet,
address _investorOnchainID
) external override onlyAgent returns (bool) {
require(balanceOf(_lostWallet) != 0, "no tokens to recover");
IIdentity _onchainID = IIdentity(_investorOnchainID);
bytes32 _key = keccak256(abi.encode(_newWallet));
if (_onchainID.keyHasPurpose(_key, 1)) {
uint256 investorTokens = balanceOf(_lostWallet);
uint256 frozenTokens = _frozenTokens[_lostWallet];
_tokenIdentityRegistry.registerIdentity(_newWallet, _onchainID, _tokenIdentityRegistry.investorCountry
(_lostWallet));
forcedTransfer(_lostWallet, _newWallet, investorTokens);
if (frozenTokens > 0) {
freezePartialTokens(_newWallet, frozenTokens);
}
if (_frozen[_lostWallet] == true) {
setAddressFrozen(_newWallet, true);
}
_tokenIdentityRegistry.deleteIdentity(_lostWallet);
emit RecoverySuccess(_lostWallet, _newWallet, _investorOnchainID);
return true;
}
revert("Recovery not possible");
}
/**
* @dev See {IERC20-totalSupply}.
*/
function totalSupply() external view override returns (uint256) {
return _totalSupply;
}
/**
* @dev See {IERC20-allowance}.
*/
function allowance(address _owner, address _spender) external view virtual override returns (uint256) {
return _allowances[_owner][_spender];
}
/**
* @dev See {IToken-identityRegistry}.
*/
function identityRegistry() external view override returns (IIdentityRegistry) {
return _tokenIdentityRegistry;
}
/**
* @dev See {IToken-compliance}.
*/
function compliance() external view override returns (IModularCompliance) {
return _tokenCompliance;
}
/**
* @dev See {IToken-paused}.
*/
function paused() external view override returns (bool) {
return _tokenPaused;
}
/**
* @dev See {IToken-isFrozen}.
*/
function isFrozen(address _userAddress) external view override returns (bool) {
return _frozen[_userAddress];
}
/**
* @dev See {IToken-getFrozenTokens}.
*/
function getFrozenTokens(address _userAddress) external view override returns (uint256) {
return _frozenTokens[_userAddress];
}
/**
* @dev See {IToken-decimals}.
*/
function decimals() external view override returns (uint8) {
return _tokenDecimals;
}
/**
* @dev See {IToken-name}.
*/
function name() external view override returns (string memory) {
return _tokenName;
}
/**
* @dev See {IToken-onchainID}.
*/
function onchainID() external view override returns (address) {
return _tokenOnchainID;
}
/**
* @dev See {IToken-symbol}.
*/
function symbol() external view override returns (string memory) {
return _tokenSymbol;
}
/**
* @dev See {IToken-version}.
*/
function version() external pure override returns (string memory) {
return _TOKEN_VERSION;
}
/**
* @notice ERC-20 overridden function that include logic to check for trade validity.
* Require that the msg.sender and to addresses are not frozen.
* Require that the value should not exceed available balance .
* Require that the to address is a verified address
* @param _to The address of the receiver
* @param _amount The number of tokens to transfer
* @return `true` if successful and revert if unsuccessful
*/
function transfer(address _to, uint256 _amount) public override whenNotPaused returns (bool) {
require(!_frozen[_to] && !_frozen[msg.sender], "wallet is frozen");
require(_amount <= balanceOf(msg.sender) - (_frozenTokens[msg.sender]), "Insufficient Balance");
if (_tokenIdentityRegistry.isVerified(_to) && _tokenCompliance.canTransfer(msg.sender, _to, _amount)) {
_transfer(msg.sender, _to, _amount);
_tokenCompliance.transferred(msg.sender, _to, _amount);
return true;
}
revert("Transfer not possible");
}
/**
* @dev See {IToken-forcedTransfer}.
*/
function forcedTransfer(
address _from,
address _to,
uint256 _amount
) public override onlyAgent returns (bool) {
require(balanceOf(_from) >= _amount, "sender balance too low");
uint256 freeBalance = balanceOf(_from) - (_frozenTokens[_from]);
if (_amount > freeBalance) {
uint256 tokensToUnfreeze = _amount - (freeBalance);
_frozenTokens[_from] = _frozenTokens[_from] - (tokensToUnfreeze);
emit TokensUnfrozen(_from, tokensToUnfreeze);
}
if (_tokenIdentityRegistry.isVerified(_to)) {
_transfer(_from, _to, _amount);
_tokenCompliance.transferred(_from, _to, _amount);
return true;
}
revert("Transfer not possible");
}
/**
* @dev See {IToken-mint}.
*/
function mint(address _to, uint256 _amount) public override onlyAgent {
require(_tokenIdentityRegistry.isVerified(_to), "Identity is not verified.");
require(_tokenCompliance.canTransfer(address(0), _to, _amount), "Compliance not followed");
_mint(_to, _amount);
_tokenCompliance.created(_to, _amount);
}
/**
* @dev See {IToken-burn}.
*/
function burn(address _userAddress, uint256 _amount) public override onlyAgent {
require(balanceOf(_userAddress) >= _amount, "cannot burn more than balance");
uint256 freeBalance = balanceOf(_userAddress) - _frozenTokens[_userAddress];
if (_amount > freeBalance) {
uint256 tokensToUnfreeze = _amount - (freeBalance);
_frozenTokens[_userAddress] = _frozenTokens[_userAddress] - (tokensToUnfreeze);
emit TokensUnfrozen(_userAddress, tokensToUnfreeze);
}
_burn(_userAddress, _amount);
_tokenCompliance.destroyed(_userAddress, _amount);
}
/**
* @dev See {IToken-setAddressFrozen}.
*/
function setAddressFrozen(address _userAddress, bool _freeze) public override onlyAgent {
_frozen[_userAddress] = _freeze;
emit AddressFrozen(_userAddress, _freeze, msg.sender);
}
/**
* @dev See {IToken-freezePartialTokens}.
*/
function freezePartialTokens(address _userAddress, uint256 _amount) public override onlyAgent {
uint256 balance = balanceOf(_userAddress);
require(balance >= _frozenTokens[_userAddress] + _amount, "Amount exceeds available balance");
_frozenTokens[_userAddress] = _frozenTokens[_userAddress] + (_amount);
emit TokensFrozen(_userAddress, _amount);
}
/**
* @dev See {IToken-unfreezePartialTokens}.
*/
function unfreezePartialTokens(address _userAddress, uint256 _amount) public override onlyAgent {
require(_frozenTokens[_userAddress] >= _amount, "Amount should be less than or equal to frozen tokens");
_frozenTokens[_userAddress] = _frozenTokens[_userAddress] - (_amount);
emit TokensUnfrozen(_userAddress, _amount);
}
/**
* @dev See {IToken-setIdentityRegistry}.
*/
function setIdentityRegistry(address _identityRegistry) public override onlyOwner {
_tokenIdentityRegistry = IIdentityRegistry(_identityRegistry);
emit IdentityRegistryAdded(_identityRegistry);
}
/**
* @dev See {IToken-setCompliance}.
*/
function setCompliance(address _compliance) public override onlyOwner {
if (address(_tokenCompliance) != address(0)) {
_tokenCompliance.unbindToken(address(this));
}
_tokenCompliance = IModularCompliance(_compliance);
_tokenCompliance.bindToken(address(this));
emit ComplianceAdded(_compliance);
}
/**
* @dev See {IERC20-balanceOf}.
*/
function balanceOf(address _userAddress) public view override returns (uint256) {
return _balances[_userAddress];
}
/**
* @dev See {ERC20-_transfer}.
*/
function _transfer(
address _from,
address _to,
uint256 _amount
) internal virtual {
require(_from != address(0), "ERC20: transfer from the zero address");
require(_to != address(0), "ERC20: transfer to the zero address");
_beforeTokenTransfer(_from, _to, _amount);
_balances[_from] = _balances[_from] - _amount;
_balances[_to] = _balances[_to] + _amount;
emit Transfer(_from, _to, _amount);
}
/**
* @dev See {ERC20-_mint}.
*/
function _mint(address _userAddress, uint256 _amount) internal virtual {
require(_userAddress != address(0), "ERC20: mint to the zero address");
_beforeTokenTransfer(address(0), _userAddress, _amount);
_totalSupply = _totalSupply + _amount;
_balances[_userAddress] = _balances[_userAddress] + _amount;
emit Transfer(address(0), _userAddress, _amount);
}
/**
* @dev See {ERC20-_burn}.
*/
function _burn(address _userAddress, uint256 _amount) internal virtual {
require(_userAddress != address(0), "ERC20: burn from the zero address");
_beforeTokenTransfer(_userAddress, address(0), _amount);
_balances[_userAddress] = _balances[_userAddress] - _amount;
_totalSupply = _totalSupply - _amount;
emit Transfer(_userAddress, address(0), _amount);
}
/**
* @dev See {ERC20-_approve}.
*/
function _approve(
address _owner,
address _spender,
uint256 _amount
) internal virtual {
require(_owner != address(0), "ERC20: approve from the zero address");
require(_spender != address(0), "ERC20: approve to the zero address");
_allowances[_owner][_spender] = _amount;
emit Approval(_owner, _spender, _amount);
}
/**
* @dev See {ERC20-_beforeTokenTransfer}.
*/
// solhint-disable-next-line no-empty-blocks
function _beforeTokenTransfer(address _from, address _to, uint256 _amount) internal virtual {}
}