mirror of
https://github.com/luxfi/erc-3643.git
synced 2026-10-30 16:58:28 +00:00
* ✨ integrate foundry alongside hardhat for dual testing framework - Add foundry.toml configuration - Reorganize test structure: test/forge/ and test/hardhat/ - Add Foundry scripts to package.json (build:forge, test:forge, coverage:forge) - Update hardhat.config.ts with evmVersion for compatibility - Add GitHub workflows and coverage scripts from template - Add Husky hooks (pre-commit, post-checkout, post-merge) - Add .vscode/settings.json for better IDE support - Disable coverage checks until tests are migrated (commented with TODO) This migration is based on TokenySolutions/contracts-template and enables gradual migration of tests from Hardhat (TypeScript) to Foundry (Solidity). Related to: BT-1418 * ✅ trex factory tests * ⚰️ remove trex suit * feat(forge): reorganize Foundry test structure and sync GitHub workflows - Move TREXFactorySetup to helpers folder - Rename TREXSuite.t.sol to TREXFactory.t.sol - Update fixture to inherit from Test for proper Foundry pattern - Transfer ownership to deployer address to match Hardhat behavior - Sync GitHub workflows with package.json scripts - Update test.yml to use npm run build and npm run test - Update push_checking.yml to use unified npm run lint - Update forge_coverage.sh to use npm run coverage * fix(ci): install Foundry and skip husky in push_checking workflow - Add Foundry installation step before npm ci - Set HUSKY=0 to skip husky setup in CI environment - Fixes 'forge: not found' error during npm prepare script * 🔧 keep hardhat settings * ✅ create addressess directly * ✅ removed test_StandardAddressesSet * ✅ fix lint * 💡 removed title natspecs * ✅ migrated trex factort tests * ✅ added test module multicall * ✅ migrate agent role test * ✅ migrate claim topics registry * ✅ migrated trusted issuer registry * ✅ migrate identity registry storage * ✅ migrated identity registry tests * ✅ migrated implementation authority test * ✅ migrated token agen restrictions * ✅ migrated token information test * ✅ migrated token recover * ✅ migrated token permit * ✅ migrated token transfer * ✅ migrated utilities upgrade * ✅ migrated freeze check * ✅ migrated compliance check in utilities test * ✅ migrated eligibility check * ✅ migrated transfer check in utilities * ✅ migrated compliance test * ✅ migrated gateway tests * 🔥 removed already defined events * 🗑️ imported Initializable error from openzeppelin * ➕ added already defined events and errors * ✅ added base test token * ✅ used idFactory * ♻️ refactored compliance test * ⚰️ remoced useless vm.prank * 🔧 deleted hardhat configurations * 💚 fixed ci * refactor: remove 100% coverage requirement from forge_coverage.sh - Remove strict 100% coverage checks that were causing workflow failures - Script now only generates and displays coverage report without failing - Aligns with previous behavior where coverage was informational only * ✅ resolve getVerifiedDetails * ✅ applied _getEligibility and remove via-ir from foundry.toml * ✅ cover tokena and token permit * ✅ cover compliance * ✅ cover identity registry * ✅ increased trex gateway coverage * ✅ increase coverage for trex factory * ✅ increased AbstractModuleUpgradeable.sol coverage * ⚰️ removed abstract module * 🔧 updted generate coverage * ✅ coverage 100% identity registry * ✅ coverage 100% trex gate way * ✅ coverage trex factory * ✅ coverage 100% AbstractModuleUpgradeable by test module * ✅ coverage IAFactory.sol * ✅ coverage 100% for implementation authority * ✅ coverage 1005 abstract proxy * ✅ coverage 100% claim topic registry * ✅ coverage 100% identity registry storage * ✅ coverage 100% modular compliance proxy * ✅ coverage trusted issuer registry proxy * ✅ coverage 100% identity registry * ✅ coverage 100% token proxy * 🚨 resolve linter in token information * ✅ coverage 100% roles * 🚨 resolve linter * 🔧 added no coverage for test, _testContracts and utils * ✅ coverage 100% for token.sol * ✅ coverage 100% for modular compliance * ✅ coverage 100% trusted issuer registry * 🔧 restore package.json * ✅ coverage 100% for utlity checker * 🚨 fix linter
755 lines
29 KiB
Solidity
Executable File
755 lines
29 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) 2025, 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.30;
|
|
|
|
import "../errors/CommonErrors.sol";
|
|
import "../errors/InvalidArgumentErrors.sol";
|
|
import "../roles/AgentRoleUpgradeable.sol";
|
|
import "../roles/IERC173.sol";
|
|
import "./IToken.sol";
|
|
import "./TokenPermit.sol";
|
|
import "./TokenStorage.sol";
|
|
import "@onchain-id/solidity/contracts/interface/IIdentity.sol";
|
|
import "@openzeppelin/contracts/utils/introspection/IERC165.sol";
|
|
|
|
/// errors
|
|
|
|
/// @dev Thrown when address is not an agent.
|
|
/// @param _agent address of agent.
|
|
error AddressNotAgent(address _agent);
|
|
|
|
/// @dev Thrown when agent is not authorized.
|
|
/// @param _agent address of agent.
|
|
/// @param _reason authorisation label.
|
|
error AgentNotAuthorized(address _agent, string _reason);
|
|
|
|
/// @dev Thrown when already initialized.
|
|
error AlreadyInitialized();
|
|
|
|
/// @dev Thrown when amount is above maximum amount.
|
|
/// @param _amount amount value.
|
|
/// @param _maxAmount maximum amount value.
|
|
error AmountAboveFrozenTokens(uint256 _amount, uint256 _maxAmount);
|
|
|
|
/// @dev Thrown when wallet is frozen.
|
|
error FrozenWallet();
|
|
|
|
/// @dev Thrown when compliance is not followed.
|
|
error ComplianceNotFollowed();
|
|
|
|
/// @dev Thrown when thers is no token to recover.
|
|
error NoTokenToRecover();
|
|
|
|
/// @dev Thrown when recovery is not possible.
|
|
error RecoveryNotPossible();
|
|
|
|
/// @dev Thrown when transfer is not possible.
|
|
error TransferNotPossible();
|
|
|
|
/// @dev Thrown when identity is not verified.
|
|
error UnverifiedIdentity();
|
|
|
|
/// @dev Thrown when default allowance is already enabled for _user.
|
|
error DefaultAllowanceAlreadyEnabled(address _user);
|
|
|
|
/// @dev Thrown when default allowance is already disabled for _user.
|
|
error DefaultAllowanceAlreadyDisabled(address _user);
|
|
|
|
/// @dev Thrown when default allowance is already set for _target.
|
|
error DefaultAllowanceAlreadySet(address _target);
|
|
|
|
contract Token is IToken, AgentRoleUpgradeable, TokenStorage, IERC165, TokenPermit {
|
|
|
|
bytes32 private constant _TYPE_HASH =
|
|
keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)");
|
|
|
|
bytes32 private constant _PERMIT_TYPEHASH =
|
|
keccak256("Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)");
|
|
|
|
/// modifiers
|
|
|
|
/// @dev Modifier to make a function callable only when the contract is not paused.
|
|
modifier whenNotPaused() {
|
|
require(!_tokenPaused, EnforcedPause());
|
|
_;
|
|
}
|
|
|
|
/// @dev Modifier to make a function callable only when the contract is paused.
|
|
modifier whenPaused() {
|
|
require(_tokenPaused, ExpectedPause());
|
|
_;
|
|
}
|
|
|
|
constructor() {
|
|
_disableInitializers();
|
|
}
|
|
|
|
/**
|
|
* @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), AlreadyInitialized());
|
|
require(_identityRegistry != address(0) && _compliance != address(0), ZeroAddress());
|
|
require(
|
|
keccak256(abi.encode(_name)) != keccak256(abi.encode(""))
|
|
&& keccak256(abi.encode(_symbol)) != keccak256(abi.encode("")),
|
|
EmptyString()
|
|
);
|
|
require(0 <= _decimals && _decimals <= 18, DecimalsOutOfRange(_decimals));
|
|
__Ownable_init();
|
|
_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("")), EmptyString());
|
|
_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("")), EmptyString());
|
|
_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 {
|
|
require(!getAgentRestrictions(msg.sender).disablePause, AgentNotAuthorized(msg.sender, "pause disabled"));
|
|
_tokenPaused = true;
|
|
emit Paused(msg.sender);
|
|
}
|
|
|
|
/**
|
|
* @dev See {IToken-unpause}.
|
|
*/
|
|
function unpause() external override onlyAgent whenPaused {
|
|
require(!getAgentRestrictions(msg.sender).disablePause, AgentNotAuthorized(msg.sender, "pause disabled"));
|
|
_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]);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @dev See {IToken-setAgentRestrictions}.
|
|
*/
|
|
function setAgentRestrictions(address agent, TokenRoles memory restrictions) external override onlyOwner {
|
|
if (!isAgent(agent)) {
|
|
revert AddressNotAgent(agent);
|
|
}
|
|
_agentsRestrictions[agent] = restrictions;
|
|
emit AgentRestrictionsSet(
|
|
agent,
|
|
restrictions.disableMint,
|
|
restrictions.disableBurn,
|
|
restrictions.disableAddressFreeze,
|
|
restrictions.disableForceTransfer,
|
|
restrictions.disablePartialFreeze,
|
|
restrictions.disablePause,
|
|
restrictions.disableRecovery
|
|
);
|
|
}
|
|
|
|
/**
|
|
* @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], FrozenWallet());
|
|
|
|
uint256 balance = balanceOf(_from) - (_frozenTokens[_from]);
|
|
require(_amount <= balance, ERC20InsufficientBalance(_from, balance, _amount));
|
|
if (_tokenIdentityRegistry.isVerified(_to) && _tokenCompliance.canTransfer(_from, _to, _amount)) {
|
|
if (!_defaultAllowances[msg.sender] || _defaultAllowanceOptOuts[_from]) {
|
|
_approve(_from, msg.sender, _allowances[_from][msg.sender] - (_amount));
|
|
}
|
|
_transfer(_from, _to, _amount);
|
|
_tokenCompliance.transferred(_from, _to, _amount);
|
|
return true;
|
|
}
|
|
revert TransferNotPossible();
|
|
}
|
|
|
|
/**
|
|
* @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(!getAgentRestrictions(msg.sender).disableRecovery, AgentNotAuthorized(msg.sender, "recovery disabled"));
|
|
require(balanceOf(_lostWallet) != 0, NoTokenToRecover());
|
|
require(
|
|
_tokenIdentityRegistry.contains(_lostWallet) || _tokenIdentityRegistry.contains(_newWallet),
|
|
RecoveryNotPossible()
|
|
);
|
|
uint256 investorTokens = balanceOf(_lostWallet);
|
|
uint256 frozenTokens = _frozenTokens[_lostWallet];
|
|
bool addressFreeze = _frozen[_lostWallet];
|
|
_transfer(_lostWallet, _newWallet, investorTokens);
|
|
if (frozenTokens > 0) {
|
|
_frozenTokens[_lostWallet] = 0;
|
|
emit TokensUnfrozen(_lostWallet, frozenTokens);
|
|
_frozenTokens[_newWallet] += frozenTokens;
|
|
emit TokensFrozen(_newWallet, frozenTokens);
|
|
}
|
|
if (addressFreeze) {
|
|
_frozen[_lostWallet] = false;
|
|
emit AddressFrozen(_lostWallet, false, address(this));
|
|
if (!_frozen[_newWallet]) {
|
|
_frozen[_newWallet] = true;
|
|
emit AddressFrozen(_newWallet, true, address(this));
|
|
}
|
|
}
|
|
if (_tokenIdentityRegistry.contains(_lostWallet)) {
|
|
if (!_tokenIdentityRegistry.contains(_newWallet)) {
|
|
_tokenIdentityRegistry.registerIdentity(
|
|
_newWallet, IIdentity(_investorOnchainID), _tokenIdentityRegistry.investorCountry(_lostWallet)
|
|
);
|
|
}
|
|
_tokenIdentityRegistry.deleteIdentity(_lostWallet);
|
|
}
|
|
emit RecoverySuccess(_lostWallet, _newWallet, _investorOnchainID);
|
|
return true;
|
|
}
|
|
|
|
/// @dev See {IToken-setAllowanceForAll}.
|
|
function setAllowanceForAll(bool _allow, address[] calldata _targets) external override onlyOwner {
|
|
uint256 targetsCount = _targets.length;
|
|
require(targetsCount <= 100, ArraySizeLimited(100));
|
|
for (uint256 i = 0; i < targetsCount; i++) {
|
|
require(_defaultAllowances[_targets[i]] != _allow, DefaultAllowanceAlreadySet(_targets[i]));
|
|
_defaultAllowances[_targets[i]] = _allow;
|
|
emit DefaultAllowance(_targets[i], _allow);
|
|
}
|
|
}
|
|
|
|
/// @dev See {IToken-disableDefaultAllowance}.
|
|
function disableDefaultAllowance() external override {
|
|
require(!_defaultAllowanceOptOuts[msg.sender], DefaultAllowanceAlreadyDisabled(msg.sender));
|
|
_defaultAllowanceOptOuts[msg.sender] = true;
|
|
emit DefaultAllowanceDisabled(msg.sender);
|
|
}
|
|
|
|
/// @dev See {IToken-enableDefaultAllowance}.
|
|
function enableDefaultAllowance() external override {
|
|
require(_defaultAllowanceOptOuts[msg.sender], DefaultAllowanceAlreadyEnabled(msg.sender));
|
|
_defaultAllowanceOptOuts[msg.sender] = false;
|
|
emit DefaultAllowanceEnabled(msg.sender);
|
|
}
|
|
|
|
/**
|
|
* @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) {
|
|
if (_defaultAllowances[_spender] && !_defaultAllowanceOptOuts[_owner]) {
|
|
return type(uint256).max;
|
|
}
|
|
|
|
return _allowances[_owner][_spender];
|
|
}
|
|
|
|
/**
|
|
* @dev See {IToken-identityRegistry}.
|
|
*/
|
|
function identityRegistry() external view override returns (IERC3643IdentityRegistry) {
|
|
return _tokenIdentityRegistry;
|
|
}
|
|
|
|
/**
|
|
* @dev See {IToken-compliance}.
|
|
*/
|
|
function compliance() external view override returns (IERC3643Compliance) {
|
|
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-onchainID}.
|
|
*/
|
|
function onchainID() external view override returns (address) {
|
|
return _tokenOnchainID;
|
|
}
|
|
|
|
/**
|
|
* @dev See {IToken-symbol}.
|
|
*/
|
|
function symbol() external view override returns (string memory) {
|
|
return _tokenSymbol;
|
|
}
|
|
|
|
/**
|
|
* @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], FrozenWallet());
|
|
uint256 balance = balanceOf(msg.sender) - _frozenTokens[msg.sender];
|
|
require(_amount <= balance, ERC20InsufficientBalance(msg.sender, balance, _amount));
|
|
if (_tokenIdentityRegistry.isVerified(_to) && _tokenCompliance.canTransfer(msg.sender, _to, _amount)) {
|
|
_transfer(msg.sender, _to, _amount);
|
|
_tokenCompliance.transferred(msg.sender, _to, _amount);
|
|
return true;
|
|
}
|
|
|
|
revert TransferNotPossible();
|
|
}
|
|
|
|
/**
|
|
* @dev See {IToken-forcedTransfer}.
|
|
*/
|
|
function forcedTransfer(address _from, address _to, uint256 _amount) public override onlyAgent returns (bool) {
|
|
require(
|
|
!getAgentRestrictions(msg.sender).disableForceTransfer,
|
|
AgentNotAuthorized(msg.sender, "forceTransfer disabled")
|
|
);
|
|
require(balanceOf(_from) >= _amount, ERC20InsufficientBalance(_from, balanceOf(_from), _amount));
|
|
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 TransferNotPossible();
|
|
}
|
|
|
|
/**
|
|
* @dev See {IToken-mint}.
|
|
*/
|
|
function mint(address _to, uint256 _amount) public override onlyAgent {
|
|
require(!getAgentRestrictions(msg.sender).disableMint, AgentNotAuthorized(msg.sender, "mint disabled"));
|
|
require(_tokenIdentityRegistry.isVerified(_to), UnverifiedIdentity());
|
|
require(_tokenCompliance.canTransfer(address(0), _to, _amount), ComplianceNotFollowed());
|
|
_mint(_to, _amount);
|
|
_tokenCompliance.created(_to, _amount);
|
|
}
|
|
|
|
/**
|
|
* @dev See {IToken-burn}.
|
|
*/
|
|
function burn(address _userAddress, uint256 _amount) public override onlyAgent {
|
|
require(!getAgentRestrictions(msg.sender).disableBurn, AgentNotAuthorized(msg.sender, "burn disabled"));
|
|
require(
|
|
balanceOf(_userAddress) >= _amount, ERC20InsufficientBalance(_userAddress, balanceOf(_userAddress), _amount)
|
|
);
|
|
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 {
|
|
require(
|
|
!getAgentRestrictions(msg.sender).disableAddressFreeze,
|
|
AgentNotAuthorized(msg.sender, "address freeze disabled")
|
|
);
|
|
_frozen[_userAddress] = _freeze;
|
|
|
|
emit AddressFrozen(_userAddress, _freeze, msg.sender);
|
|
}
|
|
|
|
/**
|
|
* @dev See {IToken-freezePartialTokens}.
|
|
*/
|
|
function freezePartialTokens(address _userAddress, uint256 _amount) public override onlyAgent {
|
|
require(
|
|
!getAgentRestrictions(msg.sender).disablePartialFreeze,
|
|
AgentNotAuthorized(msg.sender, "partial freeze disabled")
|
|
);
|
|
uint256 balance = balanceOf(_userAddress);
|
|
require(
|
|
balance >= _frozenTokens[_userAddress] + _amount, ERC20InsufficientBalance(_userAddress, balance, _amount)
|
|
);
|
|
_frozenTokens[_userAddress] = _frozenTokens[_userAddress] + (_amount);
|
|
emit TokensFrozen(_userAddress, _amount);
|
|
}
|
|
|
|
/**
|
|
* @dev See {IToken-unfreezePartialTokens}.
|
|
*/
|
|
function unfreezePartialTokens(address _userAddress, uint256 _amount) public override onlyAgent {
|
|
require(
|
|
!getAgentRestrictions(msg.sender).disablePartialFreeze,
|
|
AgentNotAuthorized(msg.sender, "partial freeze disabled")
|
|
);
|
|
require(_frozenTokens[_userAddress] >= _amount, AmountAboveFrozenTokens(_amount, _frozenTokens[_userAddress]));
|
|
_frozenTokens[_userAddress] = _frozenTokens[_userAddress] - (_amount);
|
|
emit TokensUnfrozen(_userAddress, _amount);
|
|
}
|
|
|
|
/**
|
|
* @dev See {IToken-setIdentityRegistry}.
|
|
*/
|
|
function setIdentityRegistry(address _identityRegistry) public override onlyOwner {
|
|
_tokenIdentityRegistry = IERC3643IdentityRegistry(_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 = IERC3643Compliance(_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 {IToken-getAgentRestrictions}.
|
|
*/
|
|
function getAgentRestrictions(address agent) public view override returns (TokenRoles memory) {
|
|
return _agentsRestrictions[agent];
|
|
}
|
|
|
|
/**
|
|
* @dev See {IToken-name}.
|
|
*/
|
|
function name() public view override(IERC20Metadata, TokenPermit) returns (string memory) {
|
|
return _tokenName;
|
|
}
|
|
|
|
/**
|
|
* @dev See {IERC165-supportsInterface}.
|
|
*/
|
|
function supportsInterface(bytes4 interfaceId) public pure virtual override returns (bool) {
|
|
return interfaceId == type(IERC20).interfaceId || interfaceId == type(IToken).interfaceId
|
|
|| interfaceId == type(IERC173).interfaceId || interfaceId == type(IERC165).interfaceId
|
|
|| interfaceId == type(IERC3643).interfaceId || interfaceId == type(IERC20Permit).interfaceId;
|
|
}
|
|
|
|
/**
|
|
* @dev See {IToken-version}.
|
|
*/
|
|
function version() public pure override(IERC3643, TokenPermit) returns (string memory) {
|
|
return _TOKEN_VERSION;
|
|
}
|
|
|
|
/**
|
|
* @dev See {ERC20-_transfer}.
|
|
*/
|
|
function _transfer(address _from, address _to, uint256 _amount) internal virtual {
|
|
require(_from != address(0), ERC20InvalidSpender(_from));
|
|
require(_to != address(0), ERC20InvalidReceiver(_to));
|
|
|
|
_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), ERC20InvalidReceiver(_userAddress));
|
|
|
|
_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), ERC20InvalidSpender(_userAddress));
|
|
|
|
_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 override {
|
|
require(_owner != address(0), ERC20InvalidSender(_owner));
|
|
require(_spender != address(0), ERC20InvalidSpender(_spender));
|
|
|
|
_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 { }
|
|
|
|
}
|