-
Notifications
You must be signed in to change notification settings - Fork 39
Add extension ERC7984Hooked that calls hooks before and after transfer
#332
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
119 commits
Select commit
Hold shift + click to select a range
61729c2
Init `ERC7984Rwa` extension.
james-toussaint 639e5a2
Fix typos
james-toussaint 85546dd
Add agent role
james-toussaint 0029ad7
Update spelling
james-toussaint b2174ea
Add pausable & roles tests
james-toussaint ce8286c
Add mint/burn/force/transfer tests
james-toussaint 6cb98a5
Remove tmp freezable
james-toussaint 46d6800
Merge remote-tracking branch 'origin/master' into feature/confidentia…
james-toussaint d2562aa
Name ERC7984Rwa
james-toussaint f58c1f3
Name confidential
james-toussaint 76b21ae
Move RWA test
james-toussaint 127aff5
Test with & without proof
james-toussaint 84af687
Rwa mock uses freezable
james-toussaint b0d5ffa
Check transferred amounts in tests
james-toussaint 6fb7f97
Bypass hardhat fhevm behaviour
james-toussaint 0cb0208
Add support interface test
james-toussaint 90ebfa0
Add should not force transfer if anyone
james-toussaint c5d07fe
Move some modifiers to mock
james-toussaint e484066
Update doc
james-toussaint 4ec0bd1
Merge remote-tracking branch 'origin/master' into feature/confidentia…
james-toussaint 64c6c9b
Swap items in doc
james-toussaint 1ad9ccd
Add suggestions
james-toussaint 628d143
Remove lint annotation
james-toussaint 0b1d87c
Update test name
james-toussaint b6b6827
Add restriction to ERC7984Rwa
james-toussaint 3185336
Move gates
james-toussaint b4f8c03
Remove ExpectedPause error
james-toussaint 28973a2
Rename block functions
james-toussaint 0facae5
Rename fixture
james-toussaint 2d0ef0e
Force transfer with all update effects
james-toussaint 5451777
Update set frozen doc
james-toussaint 3065002
Refactor event checks in freezable tests
james-toussaint 4a2e41e
Init compliance modules for confidential RWAs
james-toussaint 3946b30
Add abstract compliance modules
james-toussaint 4debb47
Compliance implements interface
james-toussaint 86d5250
Add post transfer hook
james-toussaint 30ca7df
Typo
james-toussaint 57ab500
Init investor cap module
james-toussaint ed06057
Move rwa compliance contracts
james-toussaint 4b4d558
Support confidential rwa module
james-toussaint 4c45948
Rename rwa mock functions
james-toussaint 9974fae
Immutable token in balance cap module
james-toussaint 3332581
Switch to always-on/transfer-only compliance modules
james-toussaint c81b703
Typo
james-toussaint 7d438c3
Use enum for compliance module type
james-toussaint fea22af
Enable token handles access to modules
james-toussaint b74c5ba
Increase coverage on modular compliance flow
james-toussaint b964c8b
Should not post update investors if not compliant
james-toussaint 3be8b00
Rename to `ModularCompliance` & `ComplianceModule`
james-toussaint 1bc4c3c
Add balance cap module tests
james-toussaint 1336085
Add max investor tests
james-toussaint 35b0771
Merge remote-tracking branch 'origin' into feature/confidential-rwa
james-toussaint 4f04222
Use agent for operations
james-toussaint 9e56fa5
Merge remote-tracking branch 'origin/feature/confidential-rwa' into f…
james-toussaint 020fe73
Merge remote-tracking branch 'origin' into feature/confidential-rwa-c…
james-toussaint 53ec926
Restore restricted and freezable
james-toussaint d3240e1
Update styling
james-toussaint 89f6c72
Merge branch 'master' into feature/confidential-rwa-compliance
arr00 830aece
run checks even on 0 transfer
arr00 e132aac
fix compilation
arr00 7c078bd
add comments and rename modules
arr00 15174b7
create identity module
arr00 f659ac3
revert unnecessary change and ensure token has access to transfer amount
arr00 f9a28d5
revamp `ERC7984RwaInvestorCapModule`
arr00 4420c8b
compiles
arr00 546bea2
fix `_postTransfer` hook
arr00 980e6ec
nit
arr00 7292495
add `onInstall` and `onUninstall` hooks to compliance modules
arr00 d6fb493
create mock compliance module
arr00 a0b1ffb
remove compliance module
arr00 65b8830
remove compliance module mock
arr00 1a89821
rename file
arr00 3df5aae
update tests
arr00 53f0231
tests passing
arr00 81aa09a
fix lint
arr00 1700b8e
rename default compliance module to standard
arr00 d246367
nit
arr00 d36c031
add coverage
arr00 46c18a0
optimize
arr00 978567b
remove compliance module from token dir
arr00 1a5ccfa
fix test
arr00 44f0e17
Merge branch 'master' into feat/update-compliance-modules
arr00 e8e0368
fix tests
arr00 974c5a6
override supports interface on `ERC7984RwaModularCompliance`
arr00 3b19d07
fix slither
arr00 6bf6fc6
add only admin modifier to abstract module
arr00 f19df16
move modular compliance file
arr00 f146dd6
allow compliance modules to read transfer amount
arr00 1566cab
up
arr00 39e7bd0
Remove `IERC7984RwaComplianceModule` interface. Instead use `IComplia…
arr00 a1b1dd0
review feedback
arr00 ccdca7e
Merge branch 'master' into feat/update-compliance-modules
arr00 62f22cc
remove `isAdminOrAgent` function
arr00 bb28fe8
rename compliance functions
arr00 666f621
max number of compliance modules
arr00 38bce20
switch to use 165 for module verification
arr00 fbee7ae
remove todo block
arr00 fb75f9b
remove force transfer modules
arr00 60bf89e
update docs and add modules getter
arr00 162f511
rename ERC7984RwaModularCompliance to ERC7984Hooked
arr00 c03df4a
Move from modular compliance to hooked
arr00 3d3f9aa
hooked is extension of ERC7984
arr00 10351a0
Apply suggestion from @arr00
arr00 8613b2b
Apply suggestion from @arr00
arr00 29fd622
standardize pre/post
arr00 cc56a4b
up
arr00 dcfcbf4
fix tests
arr00 a70d5fd
move hook module file
arr00 f836b6d
pr feedback
arr00 4c54896
Apply suggestions from code review
arr00 a1bc7cb
add in modifier
arr00 7ca76c0
add to docs
arr00 8d7ebce
Update contracts/mocks/token/ERC7984HookedMock.sol
arr00 f30be07
Update contracts/token/ERC7984/extensions/ERC7984Hooked.sol
arr00 24882ca
add test for base module
arr00 752e3d3
move mocks
arr00 6771ff5
fix mock imports
arr00 9d95fa4
return slice of modules instead of full list
arr00 19244c3
prefix install and uninstall events
arr00 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| --- | ||
| 'openzeppelin-confidential-contracts': minor | ||
| --- | ||
|
|
||
| `ERC7984Hooked`: Call external hooks before and after transfer of confidential tokens. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,28 @@ | ||
| // SPDX-License-Identifier: MIT | ||
|
|
||
| pragma solidity ^0.8.24; | ||
|
|
||
| import {euint64, ebool} from "@fhevm/solidity/lib/FHE.sol"; | ||
| import {IERC165} from "@openzeppelin/contracts/interfaces/IERC165.sol"; | ||
|
|
||
| /// @dev Interface for an ERC-7984 hook module. | ||
| interface IERC7984HookModule is IERC165 { | ||
| /** | ||
| * @dev Hook that runs before a transfer. Should be non-mutating. Transient access is already granted | ||
| * to the module for `encryptedAmount`. | ||
| */ | ||
| function preTransfer(address from, address to, euint64 encryptedAmount) external returns (ebool); | ||
|
|
||
| /// @dev Performs operation after transfer. | ||
| function postTransfer(address from, address to, euint64 encryptedAmount) external; | ||
|
|
||
| /// @dev Performs operations after installation. | ||
| function onInstall(bytes calldata initData) external; | ||
|
|
||
| /** | ||
| * @dev Performs operations after uninstallation. | ||
| * | ||
| * NOTE: The module uninstallation will succeed even if the function reverts. | ||
| */ | ||
| function onUninstall(bytes calldata deinitData) external; | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
27 changes: 27 additions & 0 deletions
27
contracts/mocks/token/ERC7984/extensions/ERC7984HookedMock.sol
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,27 @@ | ||
| // SPDX-License-Identifier: MIT | ||
|
|
||
| pragma solidity ^0.8.24; | ||
|
|
||
| import {euint64} from "@fhevm/solidity/lib/FHE.sol"; | ||
| import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; | ||
| import {ERC7984Hooked} from "../../../../token/ERC7984/extensions/ERC7984Hooked.sol"; | ||
| import {ERC7984Mock} from "../../ERC7984Mock.sol"; | ||
|
|
||
| contract ERC7984HookedMock is ERC7984Hooked, ERC7984Mock, Ownable { | ||
| constructor( | ||
| string memory name, | ||
| string memory symbol, | ||
| string memory tokenUri, | ||
| address admin | ||
| ) ERC7984Mock(name, symbol, tokenUri) Ownable(admin) {} | ||
|
|
||
| function _update( | ||
| address from, | ||
| address to, | ||
| euint64 amount | ||
| ) internal virtual override(ERC7984Mock, ERC7984Hooked) returns (euint64) { | ||
| return super._update(from, to, amount); | ||
| } | ||
|
|
||
| function _authorizeModuleChange() internal virtual override onlyOwner {} | ||
| } |
58 changes: 58 additions & 0 deletions
58
contracts/mocks/token/ERC7984/utils/ERC7984HookModuleMock.sol
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,58 @@ | ||
| // SPDX-License-Identifier: MIT | ||
|
|
||
| pragma solidity ^0.8.24; | ||
|
|
||
| import {ZamaEthereumConfig} from "@fhevm/solidity/config/ZamaConfig.sol"; | ||
| import {FHE, ebool, euint64} from "@fhevm/solidity/lib/FHE.sol"; | ||
| import {IERC7984} from "../../../../interfaces/IERC7984.sol"; | ||
| import {ERC7984HookModule} from "../../../../token/ERC7984/utils/ERC7984HookModule.sol"; | ||
|
|
||
| contract ERC7984HookModuleMock is ERC7984HookModule, ZamaEthereumConfig { | ||
| bool public isCompliant = true; | ||
| bool public revertOnUninstall = false; | ||
|
|
||
| event PostTransfer(); | ||
| event PreTransfer(); | ||
|
|
||
| event OnInstall(bytes initData); | ||
| event OnUninstall(bytes deinitData); | ||
|
|
||
| function onInstall(bytes calldata initData) public override { | ||
| emit OnInstall(initData); | ||
| super.onInstall(initData); | ||
| } | ||
|
|
||
| function onUninstall(bytes calldata deinitData) public override { | ||
| if (revertOnUninstall) { | ||
| revert("Revert on uninstall"); | ||
| } | ||
|
|
||
| emit OnUninstall(deinitData); | ||
| super.onUninstall(deinitData); | ||
| } | ||
|
|
||
| function setIsCompliant(bool isCompliant_) public { | ||
| isCompliant = isCompliant_; | ||
| } | ||
|
|
||
| function setRevertOnUninstall(bool revertOnUninstall_) public { | ||
| revertOnUninstall = revertOnUninstall_; | ||
| } | ||
|
|
||
| function _preTransfer(address token, address from, address, euint64) internal override returns (ebool) { | ||
| euint64 fromBalance = IERC7984(token).confidentialBalanceOf(from); | ||
|
|
||
| if (FHE.isInitialized(fromBalance)) { | ||
| _getTokenHandleAllowance(token, fromBalance); | ||
| assert(FHE.isAllowed(fromBalance, address(this))); | ||
| } | ||
|
|
||
| emit PreTransfer(); | ||
| return FHE.asEbool(isCompliant); | ||
| } | ||
|
|
||
| function _postTransfer(address token, address from, address to, euint64 amount) internal override { | ||
| emit PostTransfer(); | ||
| super._postTransfer(token, from, to, amount); | ||
| } | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,157 @@ | ||
| // SPDX-License-Identifier: MIT | ||
|
|
||
| pragma solidity ^0.8.27; | ||
|
|
||
| import {FHE, ebool, euint64} from "@fhevm/solidity/lib/FHE.sol"; | ||
| import {ERC165Checker} from "@openzeppelin/contracts/utils/introspection/ERC165Checker.sol"; | ||
| import {LowLevelCall} from "@openzeppelin/contracts/utils/LowLevelCall.sol"; | ||
| import {EnumerableSet} from "@openzeppelin/contracts/utils/structs/EnumerableSet.sol"; | ||
| import {IERC7984HookModule} from "./../../../interfaces/IERC7984HookModule.sol"; | ||
| import {HandleAccessManager} from "./../../../utils/HandleAccessManager.sol"; | ||
| import {ERC7984} from "./../ERC7984.sol"; | ||
|
|
||
| /** | ||
| * @dev Extension of {ERC7984} that supports hook modules. Inspired by ERC-7579 modules. | ||
| * | ||
| * Modules are called before and after transfers. Before the transfer, modules | ||
| * conduct checks to see if they approve the given transfer and return an encrypted boolean. If any module | ||
| * returns false, the transferred amount becomes 0. After the transfer, modules are notified of the final transfer | ||
| * amount and may do accounting as necessary. Modules may revert on either call, which will propagate | ||
| * and revert the entire transaction. | ||
| * | ||
| * NOTE: Hook modules are trusted contracts--they have access to any private state the token has access to. | ||
| */ | ||
| abstract contract ERC7984Hooked is ERC7984, HandleAccessManager { | ||
| using EnumerableSet for *; | ||
|
|
||
| EnumerableSet.AddressSet private _modules; | ||
|
|
||
| /// @dev Emitted when a module is installed. | ||
|
arr00 marked this conversation as resolved.
|
||
| event ERC7984HookedModuleInstalled(address module); | ||
| /// @dev Emitted when a module is uninstalled. | ||
| event ERC7984HookedModuleUninstalled(address module); | ||
|
|
||
| /// @dev The address is not a valid module. | ||
| error ERC7984HookedInvalidModule(address module); | ||
| /// @dev The module is already installed. | ||
| error ERC7984HookedDuplicateModule(address module); | ||
| /// @dev The module is not installed. | ||
| error ERC7984HookedNonexistentModule(address module); | ||
| /// @dev The maximum number of modules has been exceeded. | ||
| error ERC7984HookedExceededMaxModules(); | ||
|
|
||
| modifier onlyAuthorizedModuleChange() { | ||
| _authorizeModuleChange(); | ||
| _; | ||
| } | ||
|
|
||
| /// @dev Checks if a module is installed. | ||
| function isModuleInstalled(address module) public view virtual returns (bool) { | ||
| return _modules.contains(module); | ||
| } | ||
|
|
||
| /** | ||
| * @dev Installs a hook module. | ||
| * | ||
| * Consider gas footprint of the module before adding it since all modules will perform | ||
| * both steps (pre-hook, post-hook) on all transfers. | ||
| */ | ||
| function installModule(address module, bytes memory initData) public virtual onlyAuthorizedModuleChange { | ||
| _installModule(module, initData); | ||
| } | ||
|
|
||
| /// @dev Uninstalls a hook module. | ||
| function uninstallModule(address module, bytes memory deinitData) public virtual onlyAuthorizedModuleChange { | ||
| _uninstallModule(module, deinitData); | ||
| } | ||
|
|
||
| /** | ||
| * @dev Returns a slice of the list of modules installed on the token with inclusive start and exclusive end. | ||
| * | ||
| * TIP: Use an end value of type(uint256).max to get the entire list of modules. | ||
| */ | ||
| function modules(uint256 start, uint256 end) public view virtual returns (address[] memory) { | ||
| return _modules.values(start, end); | ||
| } | ||
|
|
||
| /// @dev Returns the maximum number of modules that can be installed. | ||
| function maxModules() public view virtual returns (uint256) { | ||
|
james-toussaint marked this conversation as resolved.
|
||
| return 15; | ||
| } | ||
|
|
||
| /// @dev Authorization logic for installing and uninstalling modules. Must be implemented by the concrete contract. | ||
| function _authorizeModuleChange() internal virtual; | ||
|
arr00 marked this conversation as resolved.
|
||
|
|
||
| /// @dev Internal function which installs a hook module. | ||
| function _installModule(address module, bytes memory initData) internal virtual { | ||
| require(_modules.length() < maxModules(), ERC7984HookedExceededMaxModules()); | ||
| require( | ||
| ERC165Checker.supportsInterface(module, type(IERC7984HookModule).interfaceId), | ||
| ERC7984HookedInvalidModule(module) | ||
| ); | ||
| require(_modules.add(module), ERC7984HookedDuplicateModule(module)); | ||
|
|
||
| IERC7984HookModule(module).onInstall(initData); | ||
|
|
||
| emit ERC7984HookedModuleInstalled(module); | ||
| } | ||
|
|
||
| /// @dev Internal function which uninstalls a module. | ||
| function _uninstallModule(address module, bytes memory deinitData) internal virtual { | ||
| require(_modules.remove(module), ERC7984HookedNonexistentModule(module)); | ||
|
|
||
| LowLevelCall.callNoReturn(module, abi.encodeCall(IERC7984HookModule.onUninstall, (deinitData))); | ||
|
|
||
| emit ERC7984HookedModuleUninstalled(module); | ||
| } | ||
|
|
||
| /** | ||
| * @dev See {ERC7984-_update}. | ||
| * | ||
| * Modified to run pre and post transfer hooks. Zero tokens are transferred if a module does not approve | ||
| * the transfer. | ||
| */ | ||
| function _update( | ||
| address from, | ||
| address to, | ||
| euint64 encryptedAmount | ||
| ) internal virtual override returns (euint64 transferred) { | ||
| euint64 amountToTransfer = FHE.select( | ||
| _runPreTransferHooks(from, to, encryptedAmount), | ||
| encryptedAmount, | ||
| FHE.asEuint64(0) | ||
| ); | ||
| transferred = super._update(from, to, amountToTransfer); | ||
| _runPostTransferHooks(from, to, transferred); | ||
| } | ||
|
|
||
| /// @dev Runs the pre-transfer hooks for all modules. | ||
| function _runPreTransferHooks( | ||
| address from, | ||
| address to, | ||
| euint64 encryptedAmount | ||
| ) internal virtual returns (ebool compliant) { | ||
| address[] memory modules_ = modules(0, type(uint256).max); | ||
| uint256 modulesLength = modules_.length; | ||
| compliant = FHE.asEbool(true); | ||
| for (uint256 i = 0; i < modulesLength; ++i) { | ||
| if (FHE.isInitialized(encryptedAmount)) FHE.allowTransient(encryptedAmount, modules_[i]); | ||
| compliant = FHE.and(compliant, IERC7984HookModule(modules_[i]).preTransfer(from, to, encryptedAmount)); | ||
| } | ||
| } | ||
|
|
||
| /// @dev Runs the post-transfer hooks for all modules. | ||
| function _runPostTransferHooks(address from, address to, euint64 encryptedAmount) internal virtual { | ||
| address[] memory modules_ = modules(0, type(uint256).max); | ||
| uint256 modulesLength = modules_.length; | ||
| for (uint256 i = 0; i < modulesLength; i++) { | ||
| if (FHE.isInitialized(encryptedAmount)) FHE.allowTransient(encryptedAmount, modules_[i]); | ||
| IERC7984HookModule(modules_[i]).postTransfer(from, to, encryptedAmount); | ||
| } | ||
| } | ||
|
|
||
| /// @dev See {HandleAccessManager-_validateHandleAllowance}. Allow modules to access any handle the token has access to. | ||
| function _validateHandleAllowance(bytes32) internal view override returns (bool) { | ||
| return _modules.contains(msg.sender); | ||
| } | ||
| } | ||
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.