Skip to content

Commit

Permalink
Merge pull request #229 from TokenySolutions/BT-47-utility-checker
Browse files Browse the repository at this point in the history
BT-47: Eligibility and compliance checker
  • Loading branch information
Joachim-Lebrun authored Oct 18, 2024
2 parents 6f65f78 + 36d1555 commit 0b848b1
Show file tree
Hide file tree
Showing 13 changed files with 1,250 additions and 198 deletions.
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,13 @@ All notable changes to this project will be documented in this file.
- **Custom Errors for Require Statements**: All `require` clauses that previously returned a string reason for failure have been updated to use **custom errors**, making debugging easier and more efficient.
- This upgrade provides a cleaner, more efficient error handling process and improves overall code structure without affecting backward compatibility.

- **Utility checker**:
- Add a new utility contract to check for freeze status, eligibility and compliance of an address for a token:
- Test if the transfer of tokens will fail
- Test the eligibility of an address for a token
- Test the compliance of an address for a token
- Test the freeze status of an address for a token

### Updated

- **Token Recovery Function**:
Expand Down
9 changes: 9 additions & 0 deletions contracts/_testContracts/MockContract.sol
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ pragma solidity 0.8.27;
contract MockContract {
address _irRegistry;
uint16 _investorCountry;
address _compliance;

function identityRegistry() public view returns (address identityRegistry) {
if (_irRegistry != address(0)) {
Expand All @@ -19,4 +20,12 @@ contract MockContract {
function setInvestorCountry(uint16 country) public {
_investorCountry = country;
}

function setCompliance(address compliance) public {
_compliance = compliance;
}

function compliance() public view returns (address) {
return _compliance;
}
}
129 changes: 129 additions & 0 deletions contracts/utilities/IUtilityChecker.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
// 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.27;

import { IClaimIssuer } from "@onchain-id/solidity/contracts/interface/IClaimIssuer.sol";


interface IUtilityChecker {

struct ComplianceCheckDetails {
string moduleName;
bool pass;
}

struct EligibilityCheckDetails {
IClaimIssuer issuer;
uint256 topic;
bool pass;
}

/// @dev This function verifies if the transfer is restricted due to frozen addresses or tokens.
/// @param _token The address of the token contract.
/// @param _from The address of the sender.
/// @param _to The address of the recipient.
/// @param _amount The amount of tokens to be transferred.
/// @return _frozen bool Returns true if the transfer is affected by freeze conditions, false otherwise.
/// @return _availableBalance uint256 Available unfreezed balance.
function testFreeze(address _token, address _from, address _to, uint256 _amount)
external view returns (bool _frozen, uint256 _availableBalance);

/// @dev This function performs a comprehensive check on whether a transfer would succeed:
/// - check if token is paused,
/// - check freeze conditions,
/// - check eligibilty,
/// - check compliance.
/// @param _token The address of the token contract.
/// @param _from The address of the sender.
/// @param _to The address of the recipient.
/// @param _amount The amount of tokens to be transferred.
/// @return _freezeStatus bool
/// Returns true if the transfer would be successful according to pause/freeze conditions, false otherwise.
/// @return _eligibilityStatus bool
/// Returns true if the transfer would be successful according to eligibilty conditions, false otherwise.
/// @return _complianceStatus bool
/// Returns true if the transfer would be successful according to compliance conditions, false otherwise.
function testTransfer(address _token, address _from, address _to, uint256 _amount)
external view returns (bool _freezeStatus, bool _eligibilityStatus, bool _complianceStatus);

/// @dev Check trade validity and return the status of each module for this transfer.
/// @param _token The address of the token contract.
/// @param _from Address of the sender.
/// @param _to Address of the receiver.
/// @param _value Amount of tokens to transfer.
/// @return _details Array of struct with module name and result of the `moduleCheck` call.
function testTransferDetails(address _token, address _from, address _to, uint256 _value)
external view returns (ComplianceCheckDetails [] memory _details);


/// @dev This functions checks whether an identity contract corresponding to the provided user address has the required
/// claims or not based on the data fetched from trusted issuers registry and from the claim topics registry. It
/// returns the details of each (issuer, topic).
/// @param _token Address of the token contract.
/// @param _userAddress Address of the user to be verified.
/// @return _details Array of struct with issuer, topic, and the verified status.
function testVerifiedDetails(address _token, address _userAddress)
external view returns (EligibilityCheckDetails [] memory _details);

}
185 changes: 185 additions & 0 deletions contracts/utilities/UtilityChecker.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,185 @@
// 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.27;

import { OwnableUpgradeable } from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";
import { UUPSUpgradeable } from "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol";

import { IClaimIssuer, IIdentity } from "@onchain-id/solidity/contracts/interface/IClaimIssuer.sol";
import { IERC3643IdentityRegistry } from "../ERC-3643/IERC3643IdentityRegistry.sol";
import { IERC3643TrustedIssuersRegistry } from "../ERC-3643/IERC3643TrustedIssuersRegistry.sol";
import { IModularCompliance } from "../compliance/modular/IModularCompliance.sol";
import { IModule } from "../compliance/modular/modules/IModule.sol";
import { IToken } from "../token/IToken.sol";
import { IUtilityChecker } from "./IUtilityChecker.sol";


contract UtilityChecker is IUtilityChecker, OwnableUpgradeable, UUPSUpgradeable {

function initialize() external initializer {
__Ownable_init();
}

/// @inheritdoc IUtilityChecker
/// @dev This function is not gas optimized and should be called only OFF chain.
function testTransfer(address _token, address _from, address _to, uint256 _amount)
external view override returns (bool _freezeStatus, bool _eligibilityStatus, bool _complianceStatus) {
IToken token = IToken(_token);

_freezeStatus = !token.paused();

(bool frozen, ) = testFreeze(_token, _from, _to, _amount);
_freezeStatus = _freezeStatus && !frozen;

IERC3643IdentityRegistry ir = token.identityRegistry();
_eligibilityStatus = ir.isVerified(_to);

ComplianceCheckDetails [] memory details = testTransferDetails(_token, _from, _to, _amount);
for (uint256 i; i < details.length; i++) {
if (!details[i].pass) {
_complianceStatus = false;
break;
}
}
_complianceStatus = true;
}

/// @inheritdoc IUtilityChecker
function testVerifiedDetails(address _token, address _userAddress)
public view override returns (EligibilityCheckDetails [] memory _details) {

IERC3643IdentityRegistry identityRegistry = IToken(_token).identityRegistry();
IERC3643TrustedIssuersRegistry tokenIssuersRegistry = identityRegistry.issuersRegistry();
IIdentity identity = identityRegistry.identity(_userAddress);

uint256 foundClaimTopic;
uint256 scheme;
address issuer;
bytes memory sig;
bytes memory data;
uint256 topic;
uint256[] memory requiredClaimTopics = identityRegistry.topicsRegistry().getClaimTopics();
uint256 topicsCount = requiredClaimTopics.length;
_details = new EligibilityCheckDetails[](topicsCount);
for (uint256 claimTopic; claimTopic < topicsCount; claimTopic++) {
topic = requiredClaimTopics[claimTopic];
IClaimIssuer[] memory trustedIssuers =
tokenIssuersRegistry.getTrustedIssuersForClaimTopic(topic);

for (uint256 i; i < trustedIssuers.length; i++) {
bytes32 claimId = keccak256(abi.encode(trustedIssuers[i], topic));
(foundClaimTopic, scheme, issuer, sig, data, ) = identity.getClaim(claimId);
if (foundClaimTopic == topic) {
bool pass;
try IClaimIssuer(issuer).isClaimValid(identity, topic, sig, data) returns(bool validity) {
pass = validity;
}
catch {
pass = false;
}

_details[claimTopic] = EligibilityCheckDetails({
issuer: trustedIssuers[i],
topic: topic,
pass: pass
});
}
}
}
}

/// @inheritdoc IUtilityChecker
function testFreeze(address _token, address _from, address _to, uint256 _amount)
public view override returns (bool _frozen, uint256 _availableBalance) {
IToken token = IToken(_token);

if (token.isFrozen(_from) || token.isFrozen(_to)) {
_availableBalance = 0;
_frozen = true;
} else {
_availableBalance = token.balanceOf(_from) - token.getFrozenTokens(_from);
_frozen = _amount > _availableBalance;
}
}

/// @inheritdoc IUtilityChecker
function testTransferDetails(address _token, address _from, address _to, uint256 _value)
public view override returns (ComplianceCheckDetails [] memory _details) {
IModularCompliance compliance = IModularCompliance(address(IToken(_token).compliance()));
address[] memory modules = compliance.getModules();
uint256 length = modules.length;
_details = new ComplianceCheckDetails[](length);
for (uint256 i; i < length; i++) {
IModule module = IModule(modules[i]);
_details[i] = ComplianceCheckDetails({
moduleName: module.name(),
pass: module.moduleCheck(_from, _to, _value, address(compliance))
});
}
}

// solhint-disable-next-line no-empty-blocks
function _authorizeUpgrade(address /*newImplementation*/) internal view override onlyOwner { }

}
Loading

0 comments on commit 0b848b1

Please sign in to comment.