Skip to content

Commit

Permalink
Add TransferEngineModule, remove snapshotModule
Browse files Browse the repository at this point in the history
  • Loading branch information
rya-sge committed Nov 13, 2024
1 parent be8669a commit 37078ca
Show file tree
Hide file tree
Showing 16 changed files with 445 additions and 177 deletions.
14 changes: 14 additions & 0 deletions contracts/interfaces/engine/ITransferEngine.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
// SPDX-License-Identifier: MPL-2.0

pragma solidity ^0.8.20;

/*
* @dev minimum interface to define a RuleEngine
*/
interface ITransferEngine {
/**
* @dev Returns true if the operation is a success, and false otherwise.
*/
function operateOnTransfer(address from, address to, uint256 balanceFrom, uint256 balanceTo, uint256 totalSupply) external;

}
2 changes: 2 additions & 0 deletions contracts/libraries/Errors.sol
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,8 @@ library Errors {
error CMTAT_AuthorizationModule_InvalidAuthorization();
error CMTAT_AuthorizationModule_AuthorizationEngineAlreadySet();

error CMTAT_TransferEngineModule_TransferEngineAlreadySet();

// DocumentModule
error CMTAT_DocumentModule_SameValue();

Expand Down
167 changes: 167 additions & 0 deletions contracts/mocks/TransferEngineMock.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
//SPDX-License-Identifier: MPL-2.0

pragma solidity ^0.8.20;
import "@openzeppelin/contracts-upgradeable/token/ERC20/ERC20Upgradeable.sol";
import "@openzeppelin/contracts-upgradeable/access/AccessControlUpgradeable.sol";
import "../modules/internal/base/SnapshotModuleBase.sol";
import "../interfaces/ICMTATSnapshot.sol";
import "../interfaces/engine/ITransferEngine.sol";
/*
* @title a RuleEngine mock for testing, not suitable for production
*/
contract TransferEngineMock is SnapshotModuleBase, AccessControlUpgradeable, ITransferEngine {
ERC20Upgradeable erc20;
constructor(ERC20Upgradeable erc20_, address admin) {
erc20 = erc20_;
_grantRole(DEFAULT_ADMIN_ROLE, admin);
}
/* ============ State Variables ============ */
bytes32 public constant SNAPSHOOTER_ROLE = keccak256("SNAPSHOOTER_ROLE");

/**
* @dev Returns `true` if `account` has been granted `role`.
*/
function hasRole(
bytes32 role,
address account
) public view virtual override(AccessControlUpgradeable) returns (bool) {
// The Default Admin has all roles
if (AccessControlUpgradeable.hasRole(DEFAULT_ADMIN_ROLE, account)) {
return true;
}
return AccessControlUpgradeable.hasRole(role, account);
}
/**
* @dev Update balance and/or total supply snapshots before the values are modified. This is implemented
* in the _beforeTokenTransfer hook, which is executed for _mint, _burn, and _transfer operations.
*/
function operateOnTransfer(address from, address to, uint256 balanceFrom, uint256 balanceTo, uint256 totalSupply) public override {
_setCurrentSnapshot();
if (from != address(0)) {
// for both burn and transfer
_updateAccountSnapshot(from, balanceFrom);
if (to != address(0)) {
// transfer
_updateAccountSnapshot(to, balanceTo);
} else {
// burn
_updateTotalSupplySnapshot(totalSupply);
}
} else {
// mint
_updateAccountSnapshot(to, balanceTo);
_updateTotalSupplySnapshot(totalSupply);
}
}

/*//////////////////////////////////////////////////////////////
PUBLIC/EXTERNAL FUNCTIONS
//////////////////////////////////////////////////////////////*/
/**
* @notice Return snapshotBalanceOf and snapshotTotalSupply to avoid multiple calls
* @return ownerBalance , totalSupply - see snapshotBalanceOf and snapshotTotalSupply
*/
function snapshotInfo(uint256 time, address owner) public view returns (uint256 ownerBalance, uint256 totalSupply) {
ownerBalance = snapshotBalanceOf(time, owner);
totalSupply = snapshotTotalSupply(time);
}

/**
* @notice Return snapshotBalanceOf for each address in the array and the total supply
* @return ownerBalances array with the balance of each address, the total supply
*/
function snapshotInfoBatch(uint256 time, address[] calldata addresses) public view returns (uint256[] memory ownerBalances, uint256 totalSupply) {
ownerBalances = new uint256[](addresses.length);
for(uint256 i = 0; i < addresses.length; ++i){
ownerBalances[i] = snapshotBalanceOf(time, addresses[i]);
}
totalSupply = snapshotTotalSupply(time);
}

/**
* @notice Return snapshotBalanceOf for each address in the array and the total supply
* @return ownerBalances array with the balance of each address, the total supply
*/
function snapshotInfoBatch(uint256[] calldata times, address[] calldata addresses) public view returns (uint256[][] memory ownerBalances, uint256[] memory totalSupply) {
ownerBalances = new uint256[][](times.length);
totalSupply = new uint256[](times.length);
for(uint256 iT = 0; iT < times.length; ++iT){
(ownerBalances[iT], totalSupply[iT]) = snapshotInfoBatch(times[iT],addresses);
}
}

/**
* @notice Return the number of tokens owned by the given owner at the time when the snapshot with the given time was created.
* @return value stored in the snapshot, or the actual balance if no snapshot
*/
function snapshotBalanceOf(
uint256 time,
address owner
) public view returns (uint256) {
return _snapshotBalanceOf(time, owner, erc20.balanceOf(owner));
}

/**
* @dev See {OpenZeppelin - ERC20Snapshot}
* Retrieves the total supply at the specified time.
* @return value stored in the snapshot, or the actual totalSupply if no snapshot
*/
function snapshotTotalSupply(uint256 time) public view returns (uint256) {
return _snapshotTotalSupply(time, erc20.totalSupply());
}
/*//////////////////////////////////////////////////////////////
PUBLIC/EXTERNAL FUNCTIONS
//////////////////////////////////////////////////////////////*/
/**
* @notice
* Schedule a snapshot at the given time specified as a number of seconds since epoch.
* The time cannot be before the time of the latest scheduled, but not yet created snapshot.
*/
function scheduleSnapshot(uint256 time) public onlyRole(SNAPSHOOTER_ROLE) {
_scheduleSnapshot(time);
}

/**
* @notice
* Schedule a snapshot at the given time specified as a number of seconds since epoch.
* The time cannot be before the time of the latest scheduled, but not yet created snapshot.
*/
function scheduleSnapshotNotOptimized(
uint256 time
) public onlyRole(SNAPSHOOTER_ROLE) {
_scheduleSnapshotNotOptimized(time);
}

/**
* @notice
* Reschedule the scheduled snapshot, but not yet created snapshot with the given oldTime to be created at the given newTime specified as a number of seconds since epoch.
* The newTime cannot be before the time of the previous scheduled, but not yet created snapshot, or after the time fo the next scheduled snapshot.
*/
function rescheduleSnapshot(
uint256 oldTime,
uint256 newTime
) public onlyRole(SNAPSHOOTER_ROLE) {
_rescheduleSnapshot(oldTime, newTime);
}

/**
* @notice
* Cancel creation of the scheduled snapshot, but not yet created snapshot with the given time.
* There should not be any other snapshots scheduled after this one.
*/
function unscheduleLastSnapshot(
uint256 time
) public onlyRole(SNAPSHOOTER_ROLE) {
_unscheduleLastSnapshot(time);
}

/**
* @notice
* Cancel creation of the scheduled snapshot, but not yet created snapshot with the given time.
*/
function unscheduleSnapshotNotOptimized(
uint256 time
) public onlyRole(SNAPSHOOTER_ROLE) {
_unscheduleSnapshotNotOptimized(time);
}
}
16 changes: 11 additions & 5 deletions contracts/modules/CMTAT_BASE.sol
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,16 @@ import "./wrapper/controllers/ValidationModule.sol";
import "./wrapper/extensions/MetaTxModule.sol";
import "./wrapper/extensions/DebtModule.sol";
import "./wrapper/extensions/DocumentModule.sol";
import "./wrapper/extensions/TransferEngineModule.sol";
import "./security/AuthorizationModule.sol";
import "../interfaces/ICMTATConstructor.sol";
import "../interfaces/engine/ITransferEngine.sol";
import "../libraries/Errors.sol";

abstract contract CMTAT_BASE is
Initializable,
ContextUpgradeable,
TransferEngineModule,
// Core
BaseModule,
PauseModule,
Expand All @@ -39,7 +42,7 @@ abstract contract CMTAT_BASE is
ERC20BaseModule,
// Extension
MetaTxModule,
ERC20SnapshotModule,
//ERC20SnapshotModule,
DebtModule,
DocumentModule
{
Expand Down Expand Up @@ -96,8 +99,8 @@ abstract contract CMTAT_BASE is
SnapshotModule:
Add these two calls in case you add the SnapshotModule
*/
__SnapshotModuleBase_init_unchained();
__ERC20Snapshot_init_unchained();
//__SnapshotModuleBase_init_unchained();
//__ERC20Snapshot_init_unchained();

__Validation_init_unchained(engines_ .ruleEngine);

Expand All @@ -117,7 +120,7 @@ abstract contract CMTAT_BASE is
SnapshotModule:
Add this call in case you add the SnapshotModule
*/
__ERC20SnasphotModule_init_unchained();
//__ERC20SnasphotModule_init_unchained();
__DocumentModule_init_unchained(engines_ .documentEngine);
__DebtModule_init_unchained(engines_ .debtEngine);

Expand Down Expand Up @@ -199,7 +202,10 @@ abstract contract CMTAT_BASE is
Add this in case you add the SnapshotModule
We call the SnapshotModule only if the transfer is valid
*/
ERC20SnapshotModuleInternal._snapshotUpdate(from, to);
//ERC20SnapshotModuleInternal._snapshotUpdate(from, to);
if(address(transferEngine()) != address(0)){
transferEngine().operateOnTransfer(from, to, balanceOf(from), balanceOf(to), totalSupply());
}
ERC20Upgradeable._update(from, to, amount);
}
/*//////////////////////////////////////////////////////////////
Expand Down
1 change: 1 addition & 0 deletions contracts/modules/internal/ERC20SnapshotModuleInternal.sol
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import "../../interfaces/ICMTATSnapshot.sol";

abstract contract ERC20SnapshotModuleInternal is ICMTATSnapshot, SnapshotModuleBase, ERC20Upgradeable {
using Arrays for uint256[];

/* ============ Initializer Function ============ */
function __ERC20Snapshot_init_unchained() internal onlyInitializing {
// Nothing to do
Expand Down
3 changes: 2 additions & 1 deletion contracts/modules/internal/base/SnapshotModuleBase.sol
Original file line number Diff line number Diff line change
Expand Up @@ -398,7 +398,8 @@ abstract contract SnapshotModuleBase is Initializable {
}

/* ============ Require balance and total supply ============ */
/**

/**
* @dev See {OpenZeppelin - ERC20Snapshot}
*/
function _updateAccountSnapshot(address account, uint256 accountBalance) internal {
Expand Down
71 changes: 71 additions & 0 deletions contracts/modules/wrapper/extensions/TransferEngineModule.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
//SPDX-License-Identifier: MPL-2.0

pragma solidity ^0.8.20;

import "../../security/AuthorizationModule.sol";
import "../../../libraries/Errors.sol";
import "../../../interfaces/engine/ITransferEngine.sol";

abstract contract TransferEngineModule is AuthorizationModule {
/* ============ Events ============ */
/**
* @dev Emitted when a rule engine is set.
*/
event TransferEngine(ITransferEngine indexed newTransferEngine);
/* ============ ERC-7201 ============ */
// keccak256(abi.encode(uint256(keccak256("CMTAT.storage.TransferModule")) - 1)) & ~bytes32(uint256(0xff))
bytes32 private constant TransferEngineModuleStorageLocation = 0x59b7f077fa4ad020f9053fd2197fef0113b19f0b11dcfe516e88cbc0e9226d00;
/* ==== ERC-7201 State Variables === */
struct TransferEngineModuleStorage {
ITransferEngine _transferEngine;
}
/* ============ Initializer Function ============ */
/**
* @dev
*
* - The grant to the admin role is done by AccessControlDefaultAdminRules
* - The control of the zero address is done by AccessControlDefaultAdminRules
*
*/
function __TransferModule_init_unchained(ITransferEngine TransferEngine_)
internal onlyInitializing {
if (address(TransferEngine_) != address (0)) {
TransferEngineModuleStorage storage $ = _getTransferEngineModuleStorage();
$._transferEngine = TransferEngine_;
emit TransferEngine(TransferEngine_);
}
}


/*//////////////////////////////////////////////////////////////
PUBLIC/EXTERNAL FUNCTIONS
//////////////////////////////////////////////////////////////*/

function transferEngine() public view virtual returns (ITransferEngine) {
TransferEngineModuleStorage storage $ = _getTransferEngineModuleStorage();
return $._transferEngine;
}

/*
* @notice set an TransferEngine if not already set
* @dev once an TransferEngine is set, it is not possible to unset it
*/
function setTransferEngine(
ITransferEngine transferEngine_
) external onlyRole(DEFAULT_ADMIN_ROLE) {
TransferEngineModuleStorage storage $ = _getTransferEngineModuleStorage();
$._transferEngine = transferEngine_;
emit TransferEngine(transferEngine_);
}

/*//////////////////////////////////////////////////////////////
INTERNAL/PRIVATE FUNCTIONS
//////////////////////////////////////////////////////////////*/

/* ============ ERC-7201 ============ */
function _getTransferEngineModuleStorage() private pure returns (TransferEngineModuleStorage storage $) {
assembly {
$.slot := TransferEngineModuleStorageLocation
}
}
}
Loading

0 comments on commit 37078ca

Please sign in to comment.