// 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 . */ pragma solidity 0.8.17; import "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; import "../../token/IToken.sol"; import "./IModularCompliance.sol"; import "./MCStorage.sol"; import "./modules/IModule.sol"; contract ModularCompliance is IModularCompliance, OwnableUpgradeable, MCStorage { /// modifiers /** * @dev Throws if called by any address that is not a token bound to the compliance. */ modifier onlyToken() { require(msg.sender == _tokenBound, "error : this address is not a token bound to the compliance contract"); _; } function init() external initializer { __Ownable_init(); } /** * @dev See {IModularCompliance-bindToken}. */ function bindToken(address _token) external override { require(owner() == msg.sender || (_tokenBound == address(0) && msg.sender == _token), "only owner or token can call"); require(_token != address(0), "invalid argument - zero address"); _tokenBound = _token; emit TokenBound(_token); } /** * @dev See {IModularCompliance-unbindToken}. */ function unbindToken(address _token) external override { require(owner() == msg.sender || msg.sender == _token , "only owner or token can call"); require(_token == _tokenBound, "This token is not bound"); require(_token != address(0), "invalid argument - zero address"); delete _tokenBound; emit TokenUnbound(_token); } /** * @dev See {IModularCompliance-addModule}. */ function addModule(address _module) external override onlyOwner { require(_module != address(0), "invalid argument - zero address"); require(!_moduleBound[_module], "module already bound"); require(_modules.length <= 24, "cannot add more than 25 modules"); IModule module = IModule(_module); if (!module.isPlugAndPlay()) { require(module.canComplianceBind(address(this)), "compliance is not suitable for binding to the module"); } module.bindCompliance(address(this)); _modules.push(_module); _moduleBound[_module] = true; emit ModuleAdded(_module); } /** * @dev See {IModularCompliance-removeModule}. */ function removeModule(address _module) external override onlyOwner { require(_module != address(0), "invalid argument - zero address"); require(_moduleBound[_module], "module not bound"); uint256 length = _modules.length; for (uint256 i = 0; i < length; i++) { if (_modules[i] == _module) { IModule(_module).unbindCompliance(address(this)); _modules[i] = _modules[length - 1]; _modules.pop(); _moduleBound[_module] = false; emit ModuleRemoved(_module); break; } } } /** * @dev See {IModularCompliance-transferred}. */ function transferred(address _from, address _to, uint256 _value) external onlyToken override { require( _from != address(0) && _to != address(0) , "invalid argument - zero address"); require(_value > 0, "invalid argument - no value transfer"); uint256 length = _modules.length; for (uint256 i = 0; i < length; i++) { IModule(_modules[i]).moduleTransferAction(_from, _to, _value); } } /** * @dev See {IModularCompliance-created}. */ function created(address _to, uint256 _value) external onlyToken override { require(_to != address(0), "invalid argument - zero address"); require(_value > 0, "invalid argument - no value mint"); uint256 length = _modules.length; for (uint256 i = 0; i < length; i++) { IModule(_modules[i]).moduleMintAction(_to, _value); } } /** * @dev See {IModularCompliance-destroyed}. */ function destroyed(address _from, uint256 _value) external onlyToken override { require(_from != address(0), "invalid argument - zero address"); require(_value > 0, "invalid argument - no value burn"); uint256 length = _modules.length; for (uint256 i = 0; i < length; i++) { IModule(_modules[i]).moduleBurnAction(_from, _value); } } /** * @dev see {IModularCompliance-callModuleFunction}. */ function callModuleFunction(bytes calldata callData, address _module) external override onlyOwner { require(_moduleBound[_module], "call only on bound module"); // NOTE: Use assembly to call the interaction instead of a low level // call for two reasons: // - We don't want to copy the return data, since we discard it for // interactions. // - Solidity will under certain conditions generate code to copy input // calldata twice to memory (the second being a "memcopy loop"). // solhint-disable-next-line no-inline-assembly assembly { let freeMemoryPointer := mload(0x40) calldatacopy(freeMemoryPointer, callData.offset, callData.length) if iszero( call( gas(), _module, 0, freeMemoryPointer, callData.length, 0, 0 )) { returndatacopy(0, 0, returndatasize()) revert(0, returndatasize()) } } emit ModuleInteraction(_module, _selector(callData)); } /** * @dev See {IModularCompliance-isModuleBound}. */ function isModuleBound(address _module) external view override returns (bool) { return _moduleBound[_module]; } /** * @dev See {IModularCompliance-getModules}. */ function getModules() external view override returns (address[] memory) { return _modules; } /** * @dev See {IModularCompliance-getTokenBound}. */ function getTokenBound() external view override returns (address) { return _tokenBound; } /** * @dev See {IModularCompliance-canTransfer}. */ function canTransfer(address _from, address _to, uint256 _value) external view override returns (bool) { uint256 length = _modules.length; for (uint256 i = 0; i < length; i++) { if (!IModule(_modules[i]).moduleCheck(_from, _to, _value, address(this))) { return false; } } return true; } /// @dev Extracts the Solidity ABI selector for the specified interaction. /// @param callData Interaction data. /// @return result The 4 byte function selector of the call encoded in /// this interaction. function _selector(bytes calldata callData) internal pure returns (bytes4 result) { if (callData.length >= 4) { // NOTE: Read the first word of the interaction's calldata. The // value does not need to be shifted since `bytesN` values are left // aligned, and the value does not need to be masked since masking // occurs when the value is accessed and not stored: // // // solhint-disable-next-line no-inline-assembly assembly { result := calldataload(callData.offset) } } } }