diff --git a/.github/workflows/cov-badge.svg b/.github/workflows/cov-badge.svg
index 83ae508..d090b2f 100644
--- a/.github/workflows/cov-badge.svg
+++ b/.github/workflows/cov-badge.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/Makefile b/Makefile
index 0a6493a..d7250cb 100644
--- a/Makefile
+++ b/Makefile
@@ -54,7 +54,11 @@ force-compile:
.PHONY: test ## run tests
test:
- @export CI=true && forge test --show-progress --gas-report -vvvv
+ @export CI=true && forge test --show-progress --gas-report -vvvv
+
+.PHONY: testfork ## run tests
+testfork:
+ @export CI=true && forge test --fork-url $(network) --gas-report -vvvv
.PHONY: coverage ## run tests coverage report
coverage:
diff --git a/contracts/access/AccessManager.sol b/contracts/access/AccessManager.sol
index fd4b4b4..fcd1a15 100644
--- a/contracts/access/AccessManager.sol
+++ b/contracts/access/AccessManager.sol
@@ -31,47 +31,116 @@ contract AccessManager is Initializable, UUPSUpgradeable, AccessManagerUpgradeab
// return (true, getRoleAdmin(roleId), 0); // => (true, 0, 0)
// }
- // Strategic roles for governance classification within the protocol:
+ // Multisig Assignment & Rotation
+ // ───────────────────────────────────────────────────────────────
+ //
+ // All operational multisigs (Admin, Pauser, Councils, Treasury signers)
+ // are granted and managed by the Community Governance (GOV_ROLE).
+ // Any rotation, addition, or removal of a signer or council member
+ // must be approved by governance through a proposal, queued in the Timelock,
+ // and executed on-chain.
+ //
+ // This ensures that the execution layer (multisigs) remains accountable
+ // to the collective will of the community.
+
+ // Strategic roles for governance classification within the protocol
+ // ───────────────────────────────────────────────────────────────
//
// Community Governance Role:
// - GOV_ROLE: Represents decentralized community governance.
- // Decisions are made through collective voting mechanisms (e.g., token-weighted, quadratic).
+ // Decisions are made collectively through token-weighted, quadratic,
+ // or other approved voting mechanisms, and executed via a Timelock
+ // (e.g., 48–72 hours delay) for transparency and reaction time.
+ //
+ // Group / Council-Based Roles:
+ // - ADMIN_ROLE: Managed by a multisig smart account.
+ // Approves policy attestations, contract upgrades,
+ // hook registrations, and moderates operational parameters.
+ //
+ // - SEC_ROLE: Managed by a designated security council multisig or EOA.
+ // Authorized to pause protocol modules for monitoring, threat mitigation,
+ // or emergency response; actions must be reported and are subject to limits.
+ //
+ // - TREASURER_ROLE: Managed by a treasury multisig smart account.
+ // Executes disbursements and manages treasury flows within spending limits
+ // and policies set by the Community Governance (GOV_ROLE).
+ //
+ // - CONTENT_COUNCIL_ROLE: Managed by a multisig smart account.
+ // Participates in governance referenda and oversees content curation policies.
+ //
+ // - CUSTODY_COUNCIL_ROLE: Managed by a multisig smart account.
+ // Participates in governance referenda for node/custodian validation policies.
+ //
+ // Individual / Contract-Based Roles:
+ // - OPS_ROLE: Internal operational role assigned to protocol-trusted contracts,
+ // enabling direct interaction with core modules. No human control.
+ // - VER_ROLE: Individual role granted to trusted creators,
+ // allowing them to upload content without conventional KYC-style verification.
+
+ // OPS_ROLE
+ // ───────────────────────────────────────────────────────────────
+ // Critical operational role used by internal protocol contracts
+ // (e.g., Vault, Escrow) to call sensitive functions like lockFunds
+ // and releaseFunds.
//
- // Group/Council Based Roles:
- // - ADMIN_ROLE: Managed by a smart account or council.
- // Handles protocol upgrades, pause mechanisms, and operational role assignments.
- // - MOD_ROLE: Managed by a smart account or council.
- // Approves policy submissions and moderates hook operations.
- // - REF_ROLE: Managed by a smart account or council.
- // Participates in governance referenda for content curation and distributor selection.
+ // The roleAdmin of OPS_ROLE is held by the ADMIN_ROLE multisig,
+ // which itself is controlled by Community Governance (GOV_ROLE)
+ // via proposals + timelock and supervised by SEC_ROLE guardian.
//
- // Individual/Contract Based Roles:
- // - OPS_ROLE: Internal operational role assigned to protocol-trusted contracts
- // for direct module interactions. No human involvement.
- // - VER_ROLE: Individual role assigned to trusted creators, enabling
- // content uploads without conventional verification.
+ // This design preserves flexibility to onboard future audited
+ // protocol modules while preventing unilateral assignment:
+ // any change requires a governance proposal, a timelock delay,
+ // and transparent on-chain execution with published audit evidence.
+ //
+ // OPS_ROLE must never be granted to EOAs or multisigs directly.
+ // Hierarchy / Relationship Diagram
+ // ───────────────────────────────────────────────────────────────
/*
- GOV_ROLE (Community Governance)
+ GOV_ROLE (Community Governance)
+ │
+ ├── ADMIN_ROLE (Multisig Council)
+ │ ├── OPS_ROLE (Internal Contract Role)
+ │ └── SEC_ROLE (Security Council / Guardian)
+ │
+ ├── TREASURER_ROLE (Treasury Multisig under GOV policy)
│
- ├── ADMIN_ROLE (Smart Account / Council)
- │ │
- │ ├── MOD_ROLE (Smart Account / Council)
- │ │
- │ └── OPS_ROLE (Internal Contract Role)
+ ├── CONTENT_COUNCIL_ROLE (Multisig Council)
│
- ├── REF_ROLE (Smart Account / Council)
+ ├── CUSTODY_COUNCIL_ROLE (Multisig Council)
│
- ├── VER_ROLE (Individual Trusted Creator)
+ └── VER_ROLE (Individual Trusted Creator)
*/
- _setRoleAdmin(C.VER_ROLE, C.GOV_ROLE);
- _setRoleAdmin(C.REF_ROLE, C.GOV_ROLE);
- _setRoleAdmin(C.MOD_ROLE, C.ADMIN_ROLE);
+ // Proposals Lifecycle (per-domain)
+ // ───────────────────────────────────────────────────────────────
+ // PROPOSER (domain governor/council) EXECUTOR (domain timelock)
+ // └─────────────── propose/schedule ───────────────┘
+ // domain ──> domain Timelock (delay) ──> execution on domain modules
+ //
+ // Example:
+ // admin-governor (only allowlisted proposers) ──> AdminTimelock ──> Admin modules
+ //
+ // Notes:
+ // • Each domain has its own Governor (proposers allowlisted) and its own Timelock.
+ // • The domain Timelock is the ONLY authority recognized by that domain’s modules
+ // (i.e., it holds the role or is the roleAdmin for that domain).
+ // • EXECUTOR is typically open (EXECUTOR_ROLE = address(0)); anyone can execute after delay.
+ // • SEC_ROLE: emergency actions MAY execute directly (no timelock) with strict scope limits.
+
+ // Role Admin Hierarchy (as configured)
+ // ───────────────────────────────────────────────────────────────
+ // Admin domain controls low-level ops & security roles:
_setRoleAdmin(C.OPS_ROLE, C.ADMIN_ROLE);
- }
+ _setRoleAdmin(C.SEC_ROLE, C.ADMIN_ROLE);
- // TODO pause protocol based on permission and roles
+ // Governance domain controls councils & treasury/community-facing roles:
+ _setRoleAdmin(C.VER_ROLE, C.GOV_ROLE);
+ _setRoleAdmin(C.ADMIN_ROLE, C.GOV_ROLE); // locked role
+ _setRoleAdmin(C.TREASURER_ROLE, C.GOV_ROLE);
+ _setRoleAdmin(C.CUSTODY_COUNCIL_ROLE, C.GOV_ROLE);
+ _setRoleAdmin(C.CONTENT_COUNCIL_ROLE, C.GOV_ROLE);
+ }
/// @dev Authorizes the upgrade of the contract.
/// @notice Only the admin can authorize the upgrade.
diff --git a/contracts/assets/AssetOwnership.sol b/contracts/assets/AssetRegistry.sol
similarity index 92%
rename from contracts/assets/AssetOwnership.sol
rename to contracts/assets/AssetRegistry.sol
index da5d8c3..775ae13 100644
--- a/contracts/assets/AssetOwnership.sol
+++ b/contracts/assets/AssetRegistry.sol
@@ -10,30 +10,30 @@ import { ERC721Upgradeable } from "@openzeppelin/contracts-upgradeable/token/ERC
import { ERC721EnumerableUpgradeable } from "@openzeppelin/contracts-upgradeable/token/ERC721/extensions/ERC721EnumerableUpgradeable.sol";
import { ERC721StatefulUpgradeable } from "@synaps3/core/primitives/upgradeable/ERC721StatefulUpgradeable.sol";
import { AccessControlledUpgradeable } from "@synaps3/core/primitives/upgradeable/AccessControlledUpgradeable.sol";
-import { IAssetVerifiable } from "@synaps3/core/interfaces/assets/IAssetVerifiable.sol";
-import { IAssetOwnership } from "@synaps3/core/interfaces/assets/IAssetOwnership.sol";
+import { IAssetReferendumVerifiable } from "@synaps3/core/interfaces/assets/IAssetReferendumVerifiable.sol";
+import { IAssetRegistry } from "@synaps3/core/interfaces/assets/IAssetRegistry.sol";
// TODO: Evaluate ERC-404 for fractionalization support
// TODO: Evaluate ERC-2981 for royalty management
// TODO: Evaluate ERC-4804 for URL-based on-chain asset references
-/// @title AssetOwnership
+/// @title AssetRegistry
/// @notice This contract manages ownership and lifecycle of digital assets using ERC721.
/// @dev Implements UUPS upgradeability, access control, and stateful asset management.
-contract AssetOwnership is
+contract AssetRegistry is
Initializable,
UUPSUpgradeable,
ERC721Upgradeable,
AccessControlledUpgradeable,
ERC721EnumerableUpgradeable,
ERC721StatefulUpgradeable,
- IAssetOwnership
+ IAssetRegistry
{
/// @custom:oz-upgrades-unsafe-allow state-variable-immutable
/// @notice Reference to the asset verification contract for content approval.
/// Our immutables behave as constants after deployment
/// slither-disable-next-line naming-convention
- IAssetVerifiable public immutable ASSET_REFERENDUM;
+ IAssetReferendumVerifiable public immutable ASSET_REFERENDUM;
/// @dev Emitted when a new asset is registered on the platform.
/// @param owner The address of the creator or owner of the registered asset.
@@ -89,7 +89,7 @@ contract AssetOwnership is
/// https://forum.openzeppelin.com/t/what-does-disableinitializers-function-mean/28730/5
_disableInitializers();
// we need to verify that asset has passed the community approval.
- ASSET_REFERENDUM = IAssetVerifiable(assetReferendum);
+ ASSET_REFERENDUM = IAssetReferendumVerifiable(assetReferendum);
}
/// @notice Initializes the upgradeable contract.
@@ -111,12 +111,11 @@ contract AssetOwnership is
return super.supportsInterface(interfaceId);
}
- // TODO: build getURI => from custodian /erc721-metadata
// TODO: Update asset info control version restricted/approved by governance
// TODO: Transfer Ownership Fee: Introducing a fee for transferring
// ownership discourages frequent or unnecessary transfers,
// adding an economic cost to any potential abuse of the system. Like bypassing content
- // verification by governance using a verified account.
+ // verification by governance using a verified account.
// TODO: approved content get an incentive: a cooling mechanism is needed eg:
// log decay, max registered asset rate, etc
@@ -125,7 +124,7 @@ contract AssetOwnership is
/// @dev Requires approval before an asset can be registered.
/// @param to The address that will own the minted NFT.
/// @param assetId The unique identifier for the asset, serving as the NFT ID.
- function register(address to, uint256 assetId) external onlyApprovedAsset(to, assetId) {
+ function register(address to, uint256 assetId) external whenNotPaused onlyApprovedAsset(to, assetId) {
_mint(to, assetId);
_enableAsset(assetId);
emit RegisteredAsset(to, assetId);
@@ -135,15 +134,17 @@ contract AssetOwnership is
/// @dev This action is irreversible and restricted to governance control.
/// @param assetId The unique identifier of the asset to be revoked.
function revoke(uint256 assetId) external restricted {
+ address previousOwner = ownerOf(assetId);
+
_burn(assetId);
_disableAsset(assetId);
- emit RevokedAsset(ownerOf(assetId), assetId);
+ emit RevokedAsset(previousOwner, assetId);
}
/// @notice Transfers an asset to a new owner.
/// @param to The address of the new owner.
/// @param assetId The unique identifier of the asset being transferred.
- function transfer(address to, uint256 assetId) external {
+ function transfer(address to, uint256 assetId) external onlyOwner(assetId) {
_transfer(msg.sender, to, assetId);
emit TransferredAsset(msg.sender, to, assetId);
}
diff --git a/contracts/assets/AssetSafe.sol b/contracts/assets/AssetSafe.sol
index 7cc91d9..5431ce4 100644
--- a/contracts/assets/AssetSafe.sol
+++ b/contracts/assets/AssetSafe.sol
@@ -5,7 +5,7 @@ import { UUPSUpgradeable } from "@openzeppelin/contracts-upgradeable/proxy/utils
import { Initializable } from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
import { AccessControlledUpgradeable } from "@synaps3/core/primitives/upgradeable/AccessControlledUpgradeable.sol";
-import { IAssetOwnership } from "@synaps3/core/interfaces/assets/IAssetOwnership.sol";
+import { IAssetRegistry } from "@synaps3/core/interfaces/assets/IAssetRegistry.sol";
import { IAssetSafe } from "@synaps3/core/interfaces/assets/IAssetSafe.sol";
import { T } from "@synaps3/core/primitives/Types.sol";
@@ -17,7 +17,7 @@ contract AssetSafe is Initializable, UUPSUpgradeable, AccessControlledUpgradeabl
/// @custom:oz-upgrades-unsafe-allow state-variable-immutable
/// Our immutables behave as constants after deployment
/// slither-disable-next-line naming-convention
- IAssetOwnership public immutable ASSET_OWNERSHIP;
+ IAssetRegistry public immutable ASSET_REGISTRY;
/// @dev Mapping to securely store encrypted content using a unique key derived from assetId and cipher type.
mapping(bytes32 => bytes) private _secured;
@@ -38,16 +38,16 @@ contract AssetSafe is Initializable, UUPSUpgradeable, AccessControlledUpgradeabl
/// @param assetId The identifier of the asset.
/// @dev Reverts if the sender is not the owner of the asset based on the Ownership contract.
modifier onlyHolder(uint256 assetId) {
- if (ASSET_OWNERSHIP.ownerOf(assetId) != msg.sender) {
+ if (ASSET_REGISTRY.ownerOf(assetId) != msg.sender) {
revert InvalidAssetRightsHolder();
}
_;
}
/// @custom:oz-upgrades-unsafe-allow constructor
- constructor(address assetOwnership) {
+ constructor(address assetRegistry) {
_disableInitializers();
- ASSET_OWNERSHIP = IAssetOwnership(assetOwnership);
+ ASSET_REGISTRY = IAssetRegistry(assetRegistry);
}
/// @notice Initializes the proxy state.
diff --git a/contracts/core/interfaces/assets/IAssetReferendum.sol b/contracts/core/interfaces/assets/IAssetReferendum.sol
index 458fcd9..01fc43c 100644
--- a/contracts/core/interfaces/assets/IAssetReferendum.sol
+++ b/contracts/core/interfaces/assets/IAssetReferendum.sol
@@ -2,12 +2,12 @@
// NatSpec format convention - https://docs.soliditylang.org/en/v0.5.10/natspec-format.html
pragma solidity 0.8.26;
-import { IAssetRegistrable } from "@synaps3/core/interfaces/assets/IAssetRegistrable.sol";
-import { IAssetVerifiable } from "@synaps3/core/interfaces/assets/IAssetVerifiable.sol";
-import { IAssetRevokable } from "@synaps3/core/interfaces/assets/IAssetRevokable.sol";
+import { IAssetReferendumRegistrable } from "@synaps3/core/interfaces/assets/IAssetReferendumRegistrable.sol";
+import { IAssetReferendumVerifiable } from "@synaps3/core/interfaces/assets/IAssetReferendumVerifiable.sol";
+import { IAssetReferendumRevokable } from "@synaps3/core/interfaces/assets/IAssetReferendumRevokable.sol";
/// @title IAssetReferendum
/// @notice Unified interface for managing content registration and verifications within a referendum-based system.
-/// @dev This interface extends both IAssetRegistrable and IAssetVerifiable to provide a single entry point for
+/// @dev This interface extends both IAssetReferendumRegistrable and IAssetReferendumVerifiable to provide a single entry point for
/// handling asset registration and verification processes.
-interface IAssetReferendum is IAssetRegistrable, IAssetVerifiable, IAssetRevokable {}
+interface IAssetReferendum is IAssetReferendumRegistrable, IAssetReferendumVerifiable, IAssetReferendumRevokable {}
diff --git a/contracts/core/interfaces/assets/IAssetRegistrable.sol b/contracts/core/interfaces/assets/IAssetReferendumRegistrable.sol
similarity index 91%
rename from contracts/core/interfaces/assets/IAssetRegistrable.sol
rename to contracts/core/interfaces/assets/IAssetReferendumRegistrable.sol
index 94ad016..16467bb 100644
--- a/contracts/core/interfaces/assets/IAssetRegistrable.sol
+++ b/contracts/core/interfaces/assets/IAssetReferendumRegistrable.sol
@@ -2,10 +2,10 @@
// NatSpec format convention - https://docs.soliditylang.org/en/v0.5.10/natspec-format.html
pragma solidity 0.8.26;
-/// @title IAssetRegistrable Interface
+/// @title IAssetReferendumRegistrable Interface
/// @notice Defines the essential functions for managing asset registration and governance through a referendum process.
/// @dev This interface mirrors the FSM behavior from `IQuorum`, but scoped to asset governance.
-interface IAssetRegistrable {
+interface IAssetReferendumRegistrable {
/// @notice Submits a new asset proposition for a referendum.
/// @dev This function should allow entities to propose an asset for approval.
/// @param assetId The unique identifier of the asset being submitted.
diff --git a/contracts/core/interfaces/assets/IAssetRevokable.sol b/contracts/core/interfaces/assets/IAssetReferendumRevokable.sol
similarity index 91%
rename from contracts/core/interfaces/assets/IAssetRevokable.sol
rename to contracts/core/interfaces/assets/IAssetReferendumRevokable.sol
index c7eed24..d2005d6 100644
--- a/contracts/core/interfaces/assets/IAssetRevokable.sol
+++ b/contracts/core/interfaces/assets/IAssetReferendumRevokable.sol
@@ -2,10 +2,10 @@
// NatSpec format convention - https://docs.soliditylang.org/en/v0.5.10/natspec-format.html
pragma solidity 0.8.26;
-/// @title IAssetRevokable Interface
+/// @title IAssetReferendumRevokable Interface
/// @notice Defines the functions for invalidating or withdrawing assets from the system.
/// @dev This interface focuses on asset rejection and revocation, used in the governance lifecycle.
-interface IAssetRevokable {
+interface IAssetReferendumRevokable {
/// @notice Rejects an asset proposition in the referendum.
/// @dev If rejected, the asset cannot be used in the system unless resubmitted.
/// @param assetId The unique identifier of the asset to be rejected.
diff --git a/contracts/core/interfaces/assets/IAssetVerifiable.sol b/contracts/core/interfaces/assets/IAssetReferendumVerifiable.sol
similarity index 94%
rename from contracts/core/interfaces/assets/IAssetVerifiable.sol
rename to contracts/core/interfaces/assets/IAssetReferendumVerifiable.sol
index 4a518e0..42a4d9c 100644
--- a/contracts/core/interfaces/assets/IAssetVerifiable.sol
+++ b/contracts/core/interfaces/assets/IAssetReferendumVerifiable.sol
@@ -2,10 +2,10 @@
// NatSpec format convention - https://docs.soliditylang.org/en/v0.5.10/natspec-format.html
pragma solidity 0.8.26;
-/// @title IAssetVerifiable
+/// @title IAssetReferendumVerifiable
/// @notice Interface for verifying the approval and active status of assets.
/// @dev This interface is used to check whether an asset has been approved and whether it is currently active.
-interface IAssetVerifiable {
+interface IAssetReferendumVerifiable {
/// @notice Checks if a given asset has been approved.
/// @dev This function verifies if the asset has passed the necessary approval process.
/// @param initiator The address that submitted the asset for approval.
diff --git a/contracts/core/interfaces/assets/IAssetOwnership.sol b/contracts/core/interfaces/assets/IAssetRegistry.sol
similarity index 59%
rename from contracts/core/interfaces/assets/IAssetOwnership.sol
rename to contracts/core/interfaces/assets/IAssetRegistry.sol
index 268cf5e..eaed6d6 100644
--- a/contracts/core/interfaces/assets/IAssetOwnership.sol
+++ b/contracts/core/interfaces/assets/IAssetRegistry.sol
@@ -5,14 +5,24 @@ pragma solidity 0.8.26;
import { IERC721 } from "@openzeppelin/contracts/token/ERC721/IERC721.sol";
import { IERC721Metadata } from "@openzeppelin/contracts/token/ERC721/extensions/IERC721Metadata.sol";
-/// @title IAssetOwnership
+/// @title IAssetRegistry
/// @notice Interface for managing asset ownership as ERC721 tokens.
/// @dev Extends ERC721 and ERC721Metadata to provide full NFT functionality,
/// including ownership tracking and metadata retrieval.
-interface IAssetOwnership is IERC721, IERC721Metadata {
+interface IAssetRegistry is IERC721, IERC721Metadata {
/// @notice Registers a new asset as an NFT.
/// @dev The asset must have a unique identifier (`assetId`) that serves as the token ID.
/// @param to The address that will own the minted NFT.
/// @param assetId The unique identifier for the asset, serving as the NFT ID.
function register(address to, uint256 assetId) external;
+
+ /// @notice Revokes an asset, permanently disabling it within the system.
+ /// @dev This action is irreversible and restricted to governance control.
+ /// @param assetId The unique identifier of the asset to be revoked.
+ function revoke(uint256 assetId) external;
+
+ /// @notice Transfers an asset to a new owner.
+ /// @param to The address of the new owner.
+ /// @param assetId The unique identifier of the asset being transferred.
+ function transfer(address to, uint256 assetId) external;
}
diff --git a/contracts/core/interfaces/base/IBalanceDepositor.sol b/contracts/core/interfaces/base/IBalanceDepositor.sol
index 1476999..100f97c 100644
--- a/contracts/core/interfaces/base/IBalanceDepositor.sol
+++ b/contracts/core/interfaces/base/IBalanceDepositor.sol
@@ -16,5 +16,5 @@ interface IBalanceDepositor {
/// @param recipient The address of the account to credit with the deposit.
/// @param amount The amount of currency to deposit.
/// @param currency The address of the ERC20 token to deposit.
- function deposit(address recipient, uint256 amount, address currency) external returns (uint256);
+ function deposit(address recipient, uint256 amount, address currency) external payable returns (uint256);
}
diff --git a/contracts/core/interfaces/base/ILockClaimer.sol b/contracts/core/interfaces/base/ILockClaimer.sol
new file mode 100644
index 0000000..e8651ed
--- /dev/null
+++ b/contracts/core/interfaces/base/ILockClaimer.sol
@@ -0,0 +1,23 @@
+/// SPDX-License-Identifier: BUSL-1.1
+pragma solidity 0.8.26;
+
+/// @title ILockClaimer
+/// @notice Interface for claiming locked funds in favor of an authorized claimer.
+interface ILockClaimer {
+ /// @notice Emitted when locked funds are claimed.
+ /// @param initiator Address that initiates the operation (claimer).
+ /// @param from Account from which funds are claimed.
+ /// @param amount Amount claimed.
+ /// @param currency Currency address; use address(0) for native coin.
+ event FundsClaimed(address indexed initiator, address indexed from, uint256 amount, address indexed currency);
+
+ /// @notice Error raised when there are not enough locked funds to claim.
+ error NoFundsToClaim();
+
+ /// @notice Claims a specific amount of locked funds on behalf of an authorized claimer.
+ /// @param account The address of the account whose funds are being claimed.
+ /// @param amount The amount of funds to claim.
+ /// @param currency The currency to associate with the claim; use address(0) for the native coin.
+ /// @return An identifier or updated state depending on implementation.
+ function claim(address account, uint256 amount, address currency) external returns (uint256);
+}
diff --git a/contracts/core/interfaces/base/ILockLocker.sol b/contracts/core/interfaces/base/ILockLocker.sol
new file mode 100644
index 0000000..ec36cee
--- /dev/null
+++ b/contracts/core/interfaces/base/ILockLocker.sol
@@ -0,0 +1,23 @@
+/// SPDX-License-Identifier: BUSL-1.1
+pragma solidity 0.8.26;
+
+/// @title ILockLocker
+/// @notice Interface for locking funds in an account.
+interface ILockLocker {
+ /// @notice Emitted when funds are locked.
+ /// @param initiator Address that initiates the operation.
+ /// @param from Account whose funds are being locked.
+ /// @param amount Amount locked.
+ /// @param currency Currency address; use address(0) for native coin.
+ event FundsLocked(address indexed initiator, address indexed from, uint256 amount, address indexed currency);
+
+ /// @notice Error raised when there are not enough funds to lock.
+ error NoFundsToLock();
+
+ /// @notice Locks a specific amount of funds for a given account.
+ /// @param account The address of the account whose funds will be locked.
+ /// @param amount The amount of funds to lock.
+ /// @param currency The currency to associate with the lock; use address(0) for the native coin.
+ /// @return An identifier or updated state depending on implementation.
+ function lock(address account, uint256 amount, address currency) external returns (uint256);
+}
diff --git a/contracts/core/interfaces/base/ILockOperator.sol b/contracts/core/interfaces/base/ILockOperator.sol
new file mode 100644
index 0000000..b68800a
--- /dev/null
+++ b/contracts/core/interfaces/base/ILockOperator.sol
@@ -0,0 +1,12 @@
+// SPDX-License-Identifier: BUSL-1.1
+pragma solidity 0.8.26;
+
+import { ILockClaimer } from "@synaps3/core/interfaces/base/ILockClaimer.sol";
+import { ILockReleaser } from "@synaps3/core/interfaces/base/ILockReleaser.sol";
+import { ILockLocker } from "@synaps3/core/interfaces/base/ILockLocker.sol";
+import { ILockVerifiable } from "@synaps3/core/interfaces/base/ILockVerifiable.sol";
+
+/// @title ILockOperator
+/// @notice Unified interface that composes locker, releaser, and claimer capabilities.
+/// @dev Adds a common read method to query the locked balance.
+interface ILockOperator is ILockClaimer, ILockLocker, ILockReleaser, ILockVerifiable {}
diff --git a/contracts/core/interfaces/base/ILockReleaser.sol b/contracts/core/interfaces/base/ILockReleaser.sol
new file mode 100644
index 0000000..df61de1
--- /dev/null
+++ b/contracts/core/interfaces/base/ILockReleaser.sol
@@ -0,0 +1,23 @@
+/// SPDX-License-Identifier: BUSL-1.1
+pragma solidity 0.8.26;
+
+/// @title ILockReleaser
+/// @notice Interface for releasing previously locked funds.
+interface ILockReleaser {
+ /// @notice Emitted when locked funds are released.
+ /// @param initiator Address that initiates the operation.
+ /// @param recipient Account receiving the released funds.
+ /// @param amount Amount released.
+ /// @param currency Currency address; use address(0) for native coin.
+ event FundsReleased(address indexed initiator, address indexed recipient, uint256 amount, address indexed currency);
+
+ /// @notice Error raised when there are not enough locked funds to release.
+ error NoFundsToRelease();
+
+ /// @notice Releases a specific amount of funds from the locked pool.
+ /// @param account The address of the account whose funds will be released.
+ /// @param amount The amount of funds to release.
+ /// @param currency The currency to associate with the release; use address(0) for the native coin.
+ /// @return An identifier or updated state depending on implementation.
+ function release(address account, uint256 amount, address currency) external returns (uint256);
+}
diff --git a/contracts/core/interfaces/base/ILockVerifiable.sol b/contracts/core/interfaces/base/ILockVerifiable.sol
new file mode 100644
index 0000000..15b8f3f
--- /dev/null
+++ b/contracts/core/interfaces/base/ILockVerifiable.sol
@@ -0,0 +1,13 @@
+// SPDX-License-Identifier: BUSL-1.1
+// NatSpec format convention - https://docs.soliditylang.org/en/v0.5.10/natspec-format.html
+pragma solidity 0.8.26;
+
+/// @title ILockVerifiable
+/// @notice Exposes read-only access to locked balances.
+interface ILockVerifiable {
+ /// @notice Returns the locked balance for an account and currency.
+ /// @param account The address whose locked funds are being queried.
+ /// @param currency The currency associated with the locked funds.
+ /// @return The amount of locked funds.
+ function getLockedBalance(address account, address currency) external view returns (uint256);
+}
diff --git a/contracts/core/interfaces/custody/ICustodianExpirable.sol b/contracts/core/interfaces/custody/ICustodianExpirable.sol
deleted file mode 100644
index 365d7c5..0000000
--- a/contracts/core/interfaces/custody/ICustodianExpirable.sol
+++ /dev/null
@@ -1,15 +0,0 @@
-// SPDX-License-Identifier: BUSL-1.1
-// NatSpec format convention - https://docs.soliditylang.org/en/v0.5.10/natspec-format.html
-pragma solidity 0.8.26;
-
-/// @title ICustodianExpirable Interface
-/// @notice This interface defines the methods for managing expiration periods
-/// related to enrollments or registrations.
-interface ICustodianExpirable {
- /// @notice Retrieves the current expiration period for enrollments or registrations.
- function getExpirationPeriod() external view returns (uint256);
-
- /// @notice Sets a new expiration period for an enrollment or registration.
- /// @param period The new expiration period, in seconds.
- function setExpirationPeriod(uint256 period) external;
-}
diff --git a/contracts/core/interfaces/custody/ICustodianInspectable.sol b/contracts/core/interfaces/custody/ICustodianInspectable.sol
index f63da5a..eb4e7df 100644
--- a/contracts/core/interfaces/custody/ICustodianInspectable.sol
+++ b/contracts/core/interfaces/custody/ICustodianInspectable.sol
@@ -4,11 +4,6 @@ pragma solidity 0.8.26;
/// @title ICustodianInspectable
/// @dev Interface for retrieving custodian enrollment data.
interface ICustodianInspectable {
- /// @notice Retrieves the enrollment deadline for a custodian.
- /// @param custodian The address of the custodian.
- /// @return The enrollment deadline timestamp.
- function getEnrollmentDeadline(address custodian) external view returns (uint256);
-
/// @notice Retrieves the total number of enrollments.
/// @return The number of enrollments.
function getEnrollmentCount() external view returns (uint256);
diff --git a/contracts/core/interfaces/custody/ICustodianReferendum.sol b/contracts/core/interfaces/custody/ICustodianReferendum.sol
index 75cb022..6e7c8b3 100644
--- a/contracts/core/interfaces/custody/ICustodianReferendum.sol
+++ b/contracts/core/interfaces/custody/ICustodianReferendum.sol
@@ -2,7 +2,6 @@
// NatSpec format convention - https://docs.soliditylang.org/en/v0.5.10/natspec-format.html
pragma solidity 0.8.26;
-import { ICustodianExpirable } from "@synaps3/core/interfaces/custody/ICustodianExpirable.sol";
import { ICustodianRegistrable } from "@synaps3/core/interfaces/custody/ICustodianRegistrable.sol";
import { ICustodianInspectable } from "@synaps3/core/interfaces/custody/ICustodianInspectable.sol";
import { ICustodianVerifiable } from "@synaps3/core/interfaces/custody/ICustodianVerifiable.sol";
@@ -13,7 +12,6 @@ import { ICustodianRevokable } from "@synaps3/core/interfaces/custody/ICustodian
interface ICustodianReferendum is
ICustodianRegistrable,
ICustodianVerifiable,
- ICustodianExpirable,
ICustodianInspectable,
ICustodianRevokable
{}
diff --git a/contracts/core/interfaces/custody/ICustodianRegistrable.sol b/contracts/core/interfaces/custody/ICustodianRegistrable.sol
index ca7919a..3794b3f 100644
--- a/contracts/core/interfaces/custody/ICustodianRegistrable.sol
+++ b/contracts/core/interfaces/custody/ICustodianRegistrable.sol
@@ -7,12 +7,11 @@ pragma solidity 0.8.26;
/// @dev This interface indirectly implements the FSM defined in `IQuorum` using `QuorumUpgradeable`.
/// Functions here are semantically equivalent to the FSM transitions: register → approve.
interface ICustodianRegistrable {
- /// @notice Registers data with a given identifier.
- /// @param proof The unique identifier of the agreement to be enforced.
- /// @param currency The currency used to pay enrollment.
- function register(uint256 proof, address currency) external;
+ /// @notice Registers a custodian to be approved by council.
+ /// @param custodian The address of the custodian to register.
+ function register(address custodian) external;
- /// @notice Approves the data associated with the given identifier.
+ /// @notice Approves the data custodian with the given address.
/// @param custodian The address of the custodian to approve.
function approve(address custodian) external;
}
diff --git a/contracts/core/interfaces/economics/ITreasury.sol b/contracts/core/interfaces/economics/ITreasury.sol
index cb64d55..ebd97cc 100644
--- a/contracts/core/interfaces/economics/ITreasury.sol
+++ b/contracts/core/interfaces/economics/ITreasury.sol
@@ -1,12 +1,14 @@
// SPDX-License-Identifier: BUSL-1.1
// NatSpec format convention - https://docs.soliditylang.org/en/v0.5.10/natspec-format.html
pragma solidity 0.8.26;
+import { IBalanceOperator } from "@synaps3/core/interfaces/base/IBalanceOperator.sol";
/// @title ITreasury Interface
/// @notice Defines the standard functions for a Treasury contract.
-interface ITreasury {
+interface ITreasury is IBalanceOperator {
/// @notice Collects accrued fees for a specified currency from an authorized fee collector.
- /// @param collector The address of an authorized fee collector.
+ /// @param amount The amount to collect from fee collector.
/// @param currency The address of the currency for which fees are being collected.
- function collectFees(address collector, address currency) external;
+ /// @param collector The address of an authorized fee collector.
+ function collectFees(uint256 amount, address currency, address collector) external;
}
diff --git a/contracts/core/interfaces/financial/ILedgerVault.sol b/contracts/core/interfaces/financial/ILedgerVault.sol
index 6a74bfc..3b61d39 100644
--- a/contracts/core/interfaces/financial/ILedgerVault.sol
+++ b/contracts/core/interfaces/financial/ILedgerVault.sol
@@ -4,28 +4,21 @@ pragma solidity 0.8.26;
import { IBalanceOperator } from "@synaps3/core/interfaces/base/IBalanceOperator.sol";
import { IAllowanceOperator } from "@synaps3/core/interfaces/base/IAllowanceOperator.sol";
+import { ILockOperator } from "@synaps3/core/interfaces/base/ILockOperator.sol";
/// @title ILedgerVault
/// @notice Interface for managing locked funds and their operations.
/// @dev Extends IBalanceOperator for managing user balances in a vault-like system.
-interface ILedgerVault is IBalanceOperator, IAllowanceOperator {
- /// @notice Locks a specific amount of funds for a given account.
- /// @dev The funds are immobilized and cannot be withdrawn or transferred until released or claimed.
- /// @param account The address of the account for which the funds will be locked.
- /// @param amount The amount of funds to lock.
- /// @param currency The currency to associate fees with. Use address(0) for the native coin.
- function lock(address account, uint256 amount, address currency) external returns (uint256);
+interface ILedgerVault is IBalanceOperator, IAllowanceOperator, ILockOperator {
+ /// @notice Allows a currency to be used within the ledger operations.
+ /// @param currency The address of the currency to allow. Use address(0) for the native coin.
+ function allowCurrency(address currency) external;
- /// @notice Claims a specific amount of locked funds on behalf of a claimer.
- /// @dev The claimer is authorized to withdraw or process the funds from the account.
- /// @param account The address of the account whose funds are being claimed.
- /// @param amount The amount of funds to claim.
- /// @param currency The currency to associate fees with. Use address(0) for the native coin.
- function claim(address account, uint256 amount, address currency) external returns (uint256);
+ /// @notice Blocks a currency from being used within the ledger operations.
+ /// @param currency The address of the currency to block. Use address(0) for the native coin.
+ function blockCurrency(address currency) external;
- /// @notice Release a specific amount of funds from locked pool.
- /// @param account The address of the account for which the funds will be released.
- /// @param amount The amount of funds to release.
- /// @param currency The currency to associate release with. Use address(0) for the native coin.
- function release(address account, uint256 amount, address currency) external returns (uint256);
+ /// @notice Returns whether a currency is approved for ledger operations.
+ /// @param currency The address of the currency to verify.
+ function isCurrencyAllowed(address currency) external view returns (bool);
}
diff --git a/contracts/core/interfaces/policies/IPolicy.sol b/contracts/core/interfaces/policies/IPolicy.sol
index 5afe759..a8d2753 100644
--- a/contracts/core/interfaces/policies/IPolicy.sol
+++ b/contracts/core/interfaces/policies/IPolicy.sol
@@ -8,14 +8,6 @@ import { T } from "@synaps3/core/primitives/Types.sol";
/// @notice Interface for managing access to content based on licensing terms.
/// @dev This interface defines the basic information about the policy, such as its name and description.
interface IPolicy {
- /// @notice Returns the string identifier associated with the policy.
- /// @dev This function provides a way to identify the specific policy being used.
- function name() external pure returns (string memory);
-
- /// @notice Returns the business/strategy model implemented by the policy.
- /// @dev A description of the business model as bytes, allowing more complex representations (such as encoded data).
- function description() external pure returns (string memory);
-
/// @notice Initializes the policy with specific data for a given holder.
/// @dev Only the Rights Policies Authorizer contract has permission to call this function.
/// @param holder The address of the holder for whom the policy is being initialized.
@@ -46,4 +38,12 @@ interface IPolicy {
/// @notice Retrieves the address of the attestation provider.
/// @return The address of the provider associated with the policy.
function getAttestationProvider() external view returns (address);
+
+ /// @notice Returns the string identifier associated with the policy.
+ /// @dev This function provides a way to identify the specific policy being used.
+ function name() external pure returns (string memory);
+
+ /// @notice Returns the business/strategy model implemented by the policy.
+ /// @dev A description of the business model as bytes, allowing more complex representations (such as encoded data).
+ function description() external pure returns (string memory);
}
diff --git a/contracts/core/interfaces/policies/IPolicyAuditorVerifiable.sol b/contracts/core/interfaces/policies/IPolicyAuditorVerifiable.sol
index 6b8ae02..a5497a4 100644
--- a/contracts/core/interfaces/policies/IPolicyAuditorVerifiable.sol
+++ b/contracts/core/interfaces/policies/IPolicyAuditorVerifiable.sol
@@ -6,7 +6,15 @@ pragma solidity 0.8.26;
/// @notice Interface that defines the methods required to verify if a policy has been audited.
/// @dev This interface can be implemented by any contract that aims to provide audit verification for policies.
interface IPolicyAuditorVerifiable {
- /// @notice Checks if a specific policy contract has been audited.
+ /// @notice Checks if a specific policy has been approved and remains active.
/// @param policy The address of the policy contract to verify.
- function isAudited(address policy) external view returns (bool);
+ function isApproved(address policy) external view returns (bool);
+
+ /// @notice Checks if a specific policy has been rejected or blocked by the auditor.
+ /// @param policy The address of the policy contract to verify.
+ function isRejected(address policy) external view returns (bool);
+
+ /// @notice Checks if a specific policy is awaiting approval.
+ /// @param policy The address of the policy contract to verify.
+ function isPending(address policy) external view returns (bool);
}
diff --git a/contracts/core/libraries/ArrayOps.sol b/contracts/core/libraries/ArrayOps.sol
deleted file mode 100644
index 9e8a03f..0000000
--- a/contracts/core/libraries/ArrayOps.sol
+++ /dev/null
@@ -1,24 +0,0 @@
-// SPDX-License-Identifier: BUSL-1.1
-// NatSpec format convention - https://docs.soliditylang.org/en/v0.5.10/natspec-format.html
-pragma solidity 0.8.26;
-
-/// @title ArrayOps
-/// @notice Library providing utility functions for manipulating arrays in memory.
-library ArrayOps {
- // TODO expand types using private methods and bytes32 as base type
-
- /// @notice Returns a new array containing only the first `cap` elements.
- /// @dev Creates a new array with a maximum size of `cap` and copies
- /// only the first `cap` elements from the original array.
- /// @param array The input array from which elements will be copied.
- /// @param cap The maximum number of elements to keep in the new array.
- /// @return sliced A new array containing only the first `cap` elements.
- function slice(address[] memory array, uint256 cap) internal pure returns (address[] memory sliced) {
- if (cap > array.length) cap = array.length; // Ensure cap does not exceed array length
- sliced = new address[](cap);
-
- for (uint256 i = 0; i < cap; i++) {
- sliced[i] = array[i];
- }
- }
-}
diff --git a/contracts/core/libraries/CriteriaOps.sol b/contracts/core/libraries/CriteriaOps.sol
new file mode 100644
index 0000000..bd1e739
--- /dev/null
+++ b/contracts/core/libraries/CriteriaOps.sol
@@ -0,0 +1,40 @@
+// SPDX-License-Identifier: BUSL-1.1
+// NatSpec format convention - https://docs.soliditylang.org/en/v0.5.10/natspec-format.html
+pragma solidity 0.8.26;
+
+/// @title CriteriaOps
+/// @notice Standard (de)serialization for policy criteria across core/periphery.
+/// @dev Canonical shape: `criteria := abi.encode(uint8 kind, bytes value)`.
+library CriteriaOps {
+ /// @notice Encodes criteria with an address-type payload.
+ /// @param kind The discriminant indicating the criterion type.
+ /// @param addr The address payload (e.g. holder, group, etc.).
+ /// @return criteria Canonical `(uint8 kind, bytes value)` encoding.
+ function encode(uint256 kind, address addr) internal pure returns (bytes memory criteria) {
+ return abi.encode(kind, abi.encode(addr));
+ }
+
+ /// @notice Encodes criteria with a uint256-type payload.
+ /// @param kind The discriminant indicating the criterion type.
+ /// @param id The numeric payload (e.g. assetId, tokenId, etc.).
+ /// @return criteria Canonical `(uint8 kind, bytes value)` encoding.
+ function encode(uint256 kind, uint256 id) internal pure returns (bytes memory criteria) {
+ return abi.encode(kind, abi.encode(id));
+ }
+
+ /// @notice Encodes criteria with a bytes32-type payload.
+ /// @param kind The discriminant indicating the criterion type.
+ /// @param key The fixed-size 32-byte payload (e.g. collectionId, hashed key, etc.).
+ /// @return criteria Canonical `(uint8 kind, bytes value)` encoding.
+ function encode(uint256 kind, bytes32 key) internal pure returns (bytes memory criteria) {
+ return abi.encode(kind, abi.encode(key));
+ }
+
+ /// @notice Decodes a canonical criteria blob into its discriminant and raw ABI-encoded value.
+ /// @param criteria The canonical criteria blob to decode.
+ /// @return kind The discriminant to interpret `value`.
+ /// @return value The ABI-encoded payload; decode it further according to `kind`.
+ function decode(bytes memory criteria) internal pure returns (uint256 kind, bytes memory value) {
+ return abi.decode(criteria, (uint8, bytes));
+ }
+}
diff --git a/contracts/core/libraries/FinancialOps.sol b/contracts/core/libraries/FinancialOps.sol
index a640c48..cb0ce3a 100644
--- a/contracts/core/libraries/FinancialOps.sol
+++ b/contracts/core/libraries/FinancialOps.sol
@@ -22,8 +22,12 @@ library FinancialOps {
/// @param to The address to which the native cryptocurrency will be transferred.
/// @param amount The amount of native cryptocurrency to transfer.
function _nativeTransfer(address to, uint256 amount) internal {
+ // if address is zero fails
+ // if empty address contract -> success true = unrecoverable funds
(bool success, ) = payable(to).call{ value: amount }("");
- if (!success) revert FailDuringTransfer("Transfer failed");
+ if (!success) {
+ revert FailDuringTransfer("Transfer failed");
+ }
}
/// @notice Handles the transfer of ERC20 tokens.
@@ -39,9 +43,12 @@ library FinancialOps {
/// @param amount The amount to deposit.
/// @return The deposited amount.
function _nativeDeposit(uint256 amount) internal returns (uint256) {
- if (amount > msg.value) revert FailDuringDeposit("Amount exceeds balance sent.");
+ if (amount != msg.value) {
+ revert FailDuringDeposit("Invalid expected sent balance.");
+ }
+
// the transfer is not needed since the transfer is implicit here
- return amount;
+ return msg.value;
}
/// @notice Deposits ERC20 tokens from a specified address.
@@ -51,12 +58,21 @@ library FinancialOps {
/// @param token The address of the ERC20 token contract.
/// @return The deposited amount.
function _erc20Deposit(address from, uint256 amount, address token) internal returns (uint256) {
- if (amount > allowance(from, token)) revert FailDuringDeposit("Amount exceeds allowance.");
+ if (amount > allowance(from, token)) {
+ revert FailDuringDeposit("Amount exceeds allowance.");
+ }
+
+ // Reconcile balances to block fee-on-transfer tokens that would deliver less than the requested amount.
// disable slitter use 'arbitrary transfer form' since the use of `safeDeposit` is handled in a safe manner.
// eg. msg.sender.safeDeposit(total, currency); <- Use msg.sender as from in transferFrom.
// slither-disable-next-line arbitrary-send-erc20
+ uint256 balanceBefore = IERC20(token).balanceOf(address(this));
IERC20(token).safeTransferFrom(from, address(this), amount);
- return amount;
+ uint256 balanceAfter = IERC20(token).balanceOf(address(this));
+
+ uint256 received = balanceAfter - balanceBefore;
+ if (received != amount) revert FailDuringDeposit("Token not supported.");
+ return received;
}
/// @notice Increases the allowance of a given `spender` for a specified ERC20 `token`.
@@ -66,7 +82,10 @@ library FinancialOps {
/// @param token The address of the ERC20 token contract for which the allowance is increased.
/// Use `address(0)` for native tokens, where this function will have no effect.
function increaseAllowance(address spender, uint256 amount, address token) internal {
- if (token == address(0) || amount == 0) revert FailDuringDeposit("Invalid spender or allowance attempt");
+ if (token == address(0) || amount == 0) {
+ revert FailDuringDeposit("Invalid spender or allowance attempt");
+ }
+
IERC20(token).safeIncreaseAllowance(spender, amount);
}
@@ -77,7 +96,10 @@ library FinancialOps {
/// @param token The address of the ERC20 token contract or `address(0)` for native tokens.
/// @return The allowance amount for ERC20 tokens, or `msg.value` if it’s a native token.
function allowance(address owner, address token) internal view returns (uint256) {
- if (token == address(0)) return msg.value;
+ if (token == address(0)) {
+ return msg.value;
+ }
+
return IERC20(token).allowance(owner, address(this));
}
@@ -88,8 +110,14 @@ library FinancialOps {
/// @param amount The amount of tokens to deposit.
/// @param token The address of the token to deposit.
function safeDeposit(address from, uint256 amount, address token) internal returns (uint256) {
- if (amount == 0) revert FailDuringDeposit("Invalid zero amount.");
- if (token == address(0)) return _nativeDeposit(amount);
+ if (amount == 0 || from == address(0)) {
+ revert FailDuringDeposit("Invalid amount or sender.");
+ }
+
+ if (token == address(0)) {
+ return _nativeDeposit(amount);
+ }
+
return _erc20Deposit(from, amount, token);
}
@@ -97,7 +125,10 @@ library FinancialOps {
/// @param target The address whose balance will be retrieved.
/// @param token The address of the token to check. Use address(0) for native tokens.
function balanceOf(address target, address token) internal view returns (uint256) {
- if (token == address(0)) return target.balance;
+ if (token == address(0)) {
+ return target.balance;
+ }
+
return IERC20(token).balanceOf(target);
}
@@ -107,9 +138,18 @@ library FinancialOps {
/// @param amount The amount of tokens to transfer.
/// @param token The address of the ERC20 token to transfer or address(0) for native token.
function transfer(address to, uint256 amount, address token) internal {
- if (amount == 0) revert FailDuringTransfer("Invalid zero amount to transfer.");
- if (balanceOf(address(this), token) < amount) revert FailDuringTransfer("Insufficient balance.");
- if (token == address(0)) return _nativeTransfer(to, amount);
+ if (amount == 0 || to == address(0)) {
+ revert FailDuringTransfer("Invalid amount or recipient.");
+ }
+
+ if (balanceOf(address(this), token) < amount) {
+ revert FailDuringTransfer("Insufficient balance.");
+ }
+
+ if (token == address(0)) {
+ return _nativeTransfer(to, amount);
+ }
+
_erc20Transfer(to, amount, token);
}
}
diff --git a/contracts/core/primitives/Constants.sol b/contracts/core/primitives/Constants.sol
index 5294ebe..90f41ee 100644
--- a/contracts/core/primitives/Constants.sol
+++ b/contracts/core/primitives/Constants.sol
@@ -11,12 +11,26 @@ library C {
uint256 internal constant SCALE_FACTOR = 100;
uint256 internal constant BPS_MAX = 10_000;
- uint64 internal constant ADMIN_ROLE = 0; // alias type(uint64).min AccessManager
- uint64 internal constant GOV_ROLE = 1; // governance role
- uint64 internal constant MOD_ROLE = 2; // moderator role
+ // Criteria provide a standardized, compact (kind, value) reference to identify the resource or context to which a policy applies.
+ // The protocol’s core treats criteria as opaque data, while external components interpret the semantics.
+ // This abstraction unifies enforcement, access verification, and term resolution under a single interface, enabling extensibility
+ // and interoperability across policies and resource types without altering the core.
+ /// @notice Criterion based on the rights holder (e.g., the content owner).
+ /// @dev Encoded as `uint256` value `0`.
+ uint256 internal constant HOLDER_CRITERIA = 0;
+ /// @notice Criterion based on a specific asset, identified by its unique ID.
+ /// @dev Encoded as `uint256` value `1`.
+ uint256 internal constant ASSET_CRITERIA = 1;
+
+ uint64 internal constant GOV_ROLE = 0; // alias type(uint64).min AccessManager
+ uint64 internal constant ADMIN_ROLE = 1; // admin role
+ uint64 internal constant OPS_ROLE = 2; // operations roles
uint64 internal constant VER_ROLE = 3; // account verified role
- uint64 internal constant OPS_ROLE = 4; // operations roles
- uint64 internal constant REF_ROLE = 5; // referendum roles
+ uint64 internal constant SEC_ROLE = 4; // protocol security council
+ uint64 internal constant TREASURER_ROLE = 5; // protocol treasurer
+
+ uint64 internal constant CONTENT_COUNCIL_ROLE = 6; // content validation/curation roles
+ uint64 internal constant CUSTODY_COUNCIL_ROLE = 7; // nodes validations roles
bytes32 internal constant REFERENDUM_SUBMIT_TYPEHASH =
keccak256("Submission(uint256 assetId, address initiator, uint256 nonce)");
diff --git a/contracts/core/primitives/Types.sol b/contracts/core/primitives/Types.sol
index 6b82cbe..1660901 100644
--- a/contracts/core/primitives/Types.sol
+++ b/contracts/core/primitives/Types.sol
@@ -35,20 +35,6 @@ library T {
EC // Elliptic Curve cryptography
}
- /// @title Agreement
- /// @dev Represents an agreement between multiple parties regarding the distribution and management of asset.
- /// @notice This struct captures the total amount involved, net amount after deductions, distribution fees,
- /// and the relevant addresses involved in the agreement.
- struct Agreement {
- address arbiter; // the designated escrow agent enforcing the agreement.
- address currency; // the currency used in transaction
- address initiator; // the initiator of the transaction
- uint256 total; // the transaction total amount
- uint256 fees; // the agreement protocol fees
- address[] parties; // the accounts or beneficiaries bounded to the agreement
- bytes payload; // any additional data needed during agreement execution
- }
-
/// @title TimeFrame
/// @notice Enum representing the time frame for calculations or actions.
enum TimeFrame {
@@ -58,6 +44,33 @@ library T {
MONTHLY // Indicates a rate basis of per month
}
+ /// @title Agreement
+ /// @dev Represents an escrow-backed agreement involving payment, distribution or access rights.
+ /// @notice This struct supports flexible interaction models, including 1:1 and 1:N transfers,
+ /// purchases, access control, and delegated rights via arbitration.
+ struct Agreement {
+ /// @notice The authorized arbiter that enforces the agreement logic (e.g., asset transfer or access).
+ address arbiter;
+ /// @notice The currency used for settlement (e.g., ETH or ERC20 address).
+ address currency;
+ /// @notice The address that initiated the agreement and provided the funds.
+ /// @dev In a purchase scenario, the initiator is the asset buyer and final recipient of the asset.
+ /// In access-based agreements, the initiator may be the funder but not necessarily the consumer.
+ address initiator;
+ /// @notice Total amount involved in the agreement, including fees.
+ uint256 total;
+ /// @notice Protocol or arbitration fees deducted from the total.
+ uint256 fees;
+ /// @notice Protocol internal property to handle total locked amount during agreement lifecycle
+ uint256 locked;
+ /// @notice List of accounts involved as beneficiaries or consumers of rights.
+ /// @dev In purchase scenarios, this may be empty (use `initiator` as beneficiary).
+ /// In access-sharing modelsor multiple agreement this may contain multiple users granted access to content or rights.
+ address[] parties;
+ /// @notice Arbitrary data passed for context, such as assetId, license type, content hash, etc.
+ bytes payload;
+ }
+
/// @title Terms
/// @notice Represents the financial and contractual terms associated with a specific policy or agreement.
/// @dev This struct is used to capture both on-chain and off-chain terms for content or agreement management.
diff --git a/contracts/core/primitives/upgradeable/AccessControlledUpgradeable.sol b/contracts/core/primitives/upgradeable/AccessControlledUpgradeable.sol
index 93ed73f..bcdbe3c 100644
--- a/contracts/core/primitives/upgradeable/AccessControlledUpgradeable.sol
+++ b/contracts/core/primitives/upgradeable/AccessControlledUpgradeable.sol
@@ -3,6 +3,7 @@
pragma solidity 0.8.26;
import { Initializable } from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
+import { PausableUpgradeable } from "@openzeppelin/contracts-upgradeable/utils/PausableUpgradeable.sol";
// solhint-disable-next-line max-line-length
import { AccessManagedUpgradeable } from "@openzeppelin/contracts-upgradeable/access/manager/AccessManagedUpgradeable.sol";
import { IAccessManager } from "@synaps3/core/interfaces/access/IAccessManager.sol";
@@ -11,7 +12,7 @@ import { C } from "@synaps3/core/primitives/Constants.sol";
/// @title AccessControlledUpgradeable
/// @dev Abstract contract that provides role-based access control functionality to upgradeable contracts.
/// This contract requires an AccessManager to manage roles.
-abstract contract AccessControlledUpgradeable is Initializable, AccessManagedUpgradeable {
+abstract contract AccessControlledUpgradeable is Initializable, AccessManagedUpgradeable, PausableUpgradeable {
/// @custom:storage-location erc7201:accesscontrolledupgradeable
struct AccessControlStorage {
address _accessManager;
@@ -36,7 +37,21 @@ abstract contract AccessControlledUpgradeable is Initializable, AccessManagedUpg
_;
}
- // @dev Initializes the contract and ensures it is upgradeable.
+ /// @notice Pauses the contract, disabling state-changing operations.
+ /// @dev Can only be called by an account with the operator/admin role (`restricted`).
+ /// Once paused, functions guarded by `whenNotPaused` will revert until `unpause` is called.
+ function pause() external restricted {
+ _pause();
+ }
+
+ /// @notice Unpauses the contract, re-enabling state-changing operations.
+ /// @dev Can only be called by an account with the operator/admin role (`restricted`).
+ /// Once unpaused, functions guarded by `whenNotPaused` will operate normally again.
+ function unpause() external restricted {
+ _unpause();
+ }
+
+ /// @dev Initializes the contract and ensures it is upgradeable.
/// Even if the initialization is harmless, this ensures the contract follows upgradeable contract patterns.
/// This is the method to initialize this contract and any other extended contracts.
/// @param accessManager The address of the AccessManager contract.
diff --git a/contracts/core/primitives/upgradeable/AllowanceOperatorUpgradeable.sol b/contracts/core/primitives/upgradeable/AllowanceOperatorUpgradeable.sol
index 1706f42..1dfffb3 100644
--- a/contracts/core/primitives/upgradeable/AllowanceOperatorUpgradeable.sol
+++ b/contracts/core/primitives/upgradeable/AllowanceOperatorUpgradeable.sol
@@ -23,7 +23,7 @@ abstract contract AllowanceOperatorUpgradeable is Initializable, LedgerUpgradeab
/// @dev Storage slot for AllowanceOperatorUpgradeable, calculated using a unique namespace to avoid conflicts.
/// The `ALLOWANCE_OPERATOR_SLOT` constant is used to point to the location of the storage.
bytes32 private constant ALLOWANCE_OPERATOR_SLOT =
- 0xa8707513830ffbd3c47e0c83d1f5f0270db240ae37bb1f9a13f077f85b949c00;
+ 0x60503404921b66adfd164bd2cecacde1d9102b9421dba47f75ca95001753aa00;
/// @dev Initializes the contract and ensures it is upgradeable.
/// Even if the initialization is harmless, this ensures the contract follows upgradeable contract patterns.
@@ -53,11 +53,11 @@ abstract contract AllowanceOperatorUpgradeable is Initializable, LedgerUpgradeab
/// @param to The address of the recipient for whom the funds are being approved.
/// @param amount The amount of funds to approve.
/// @param currency The address of the ERC20 token to approve. Use `address(0)` for native tokens.
- function approve(
+ function _approve(
address to,
uint256 amount,
address currency
- ) public virtual onlyValidOperation(to, amount) returns (uint256) {
+ ) internal onlyValidOperation(to, amount) returns (uint256) {
if (msg.sender == to) revert InvalidOperationParameters();
_sumApprovedAmount(msg.sender, to, amount, currency);
emit FundsApproved(msg.sender, to, amount, currency);
@@ -68,11 +68,11 @@ abstract contract AllowanceOperatorUpgradeable is Initializable, LedgerUpgradeab
/// @param to The address of the recipient whose approval is being revoked.
/// @param currency The address of the ERC20 token associated with the approval. Use `address(0)` for native tokens.
/// @return The amount of funds that were revoked from the approval.
- function revoke(
+ function _revoke(
address to,
uint256 amount,
address currency
- ) public virtual onlyValidOperation(to, amount) returns (uint256) {
+ ) internal onlyValidOperation(to, amount) returns (uint256) {
if (getApprovedAmount(msg.sender, to, currency) < amount) revert NoFundsToRevoke();
_subApprovedAmount(msg.sender, to, amount, currency);
emit FundsRevoked(msg.sender, to, amount, currency);
@@ -83,11 +83,11 @@ abstract contract AllowanceOperatorUpgradeable is Initializable, LedgerUpgradeab
/// @param from The address of the account from which the approved funds are being collected.
/// @param amount The amount of funds to collect.
/// @param currency The address of the ERC20 token to collect. Use `address(0)` for native tokens.
- function collect(
+ function _collect(
address from,
uint256 amount,
address currency
- ) public virtual onlyValidOperation(from, amount) returns (uint256) {
+ ) internal onlyValidOperation(from, amount) returns (uint256) {
if (getApprovedAmount(from, msg.sender, currency) < amount) revert NoFundsToCollect(); //
if (getLedgerBalance(from, currency) < amount) revert NoFundsToCollect(); // no balance
diff --git a/contracts/core/primitives/upgradeable/BalanceOperatorUpgradeable.sol b/contracts/core/primitives/upgradeable/BalanceOperatorUpgradeable.sol
index f7dc9b2..99aba4e 100644
--- a/contracts/core/primitives/upgradeable/BalanceOperatorUpgradeable.sol
+++ b/contracts/core/primitives/upgradeable/BalanceOperatorUpgradeable.sol
@@ -4,7 +4,6 @@ pragma solidity 0.8.26;
import { Initializable } from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
// solhint-disable-next-line max-line-length
-import { ReentrancyGuardTransientUpgradeable } from "@openzeppelin/contracts-upgradeable/utils/ReentrancyGuardTransientUpgradeable.sol";
import { LedgerUpgradeable } from "@synaps3/core/primitives/upgradeable/LedgerUpgradeable.sol";
import { IBalanceOperator } from "@synaps3/core/interfaces/base/IBalanceOperator.sol";
import { FinancialOps } from "@synaps3/core/libraries/FinancialOps.sol";
@@ -13,12 +12,7 @@ import { FinancialOps } from "@synaps3/core/libraries/FinancialOps.sol";
/// @dev Abstract contract for managing deposits and withdrawals with ledger tracking capabilities.
/// Provides core functionalities to handle funds in an upgradeable system.
/// This contract integrates with the ledger system to record balances and transactions.
-abstract contract BalanceOperatorUpgradeable is
- Initializable,
- LedgerUpgradeable,
- ReentrancyGuardTransientUpgradeable,
- IBalanceOperator
-{
+abstract contract BalanceOperatorUpgradeable is Initializable, LedgerUpgradeable, IBalanceOperator {
using FinancialOps for address;
/// @custom:storage-location erc7201:balanceoperatorupgradeable
@@ -36,7 +30,6 @@ abstract contract BalanceOperatorUpgradeable is
/// This is the method to initialize this contract and any other extended contracts.
function __BalanceOperator_init() internal onlyInitializing {
__Ledger_init();
- __ReentrancyGuardTransient_init();
}
/// @dev Function to initialize the contract without chaining, typically used in child contracts.
@@ -54,11 +47,11 @@ abstract contract BalanceOperatorUpgradeable is
/// @param recipient The address of the account to credit with the deposit.
/// @param amount The amount of currency to deposit.
/// @param currency The address of the ERC20 token to deposit.
- function deposit(
+ function _deposit(
address recipient,
uint256 amount,
address currency
- ) public virtual onlyValidOperation(recipient, amount) returns (uint256) {
+ ) internal onlyValidOperation(recipient, amount) returns (uint256) {
uint256 confirmed = msg.sender.safeDeposit(amount, currency);
_sumLedgerEntry(recipient, confirmed, currency);
emit FundsDeposited(recipient, msg.sender, confirmed, currency);
@@ -69,11 +62,11 @@ abstract contract BalanceOperatorUpgradeable is
/// @param recipient The address that will receive the withdrawn tokens.
/// @param amount The amount of tokens to withdraw.
/// @param currency The currency to associate fees with. Use address(0) for the native coin.
- function withdraw(
+ function _withdraw(
address recipient,
uint256 amount,
address currency
- ) public virtual onlyValidOperation(recipient, amount) nonReentrant returns (uint256) {
+ ) internal onlyValidOperation(recipient, amount) returns (uint256) {
if (getLedgerBalance(msg.sender, currency) < amount) revert NoFundsToWithdraw();
_subLedgerEntry(msg.sender, amount, currency);
recipient.transfer(amount, currency); // transfer fund to recipient
@@ -85,11 +78,11 @@ abstract contract BalanceOperatorUpgradeable is
/// @param recipient The address of the account to credit with the transfer.
/// @param amount The amount of tokens to transfer.
/// @param currency The address of the currency to transfer. Use `address(0)` for the native coin.
- function transfer(
+ function _transfer(
address recipient,
uint256 amount,
address currency
- ) public virtual onlyValidOperation(recipient, amount) returns (uint256) {
+ ) internal onlyValidOperation(recipient, amount) returns (uint256) {
if (msg.sender == recipient) revert InvalidOperationParameters();
if (getLedgerBalance(msg.sender, currency) < amount) revert NoFundsToTransfer();
diff --git a/contracts/core/primitives/upgradeable/LockOperatorUpgradeable.sol b/contracts/core/primitives/upgradeable/LockOperatorUpgradeable.sol
new file mode 100644
index 0000000..50ff807
--- /dev/null
+++ b/contracts/core/primitives/upgradeable/LockOperatorUpgradeable.sol
@@ -0,0 +1,136 @@
+// SPDX-License-Identifier: BUSL-1.1
+// NatSpec format convention - https://docs.soliditylang.org/en/v0.5.10/natspec-format.html
+pragma solidity 0.8.26;
+
+import { Initializable } from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
+import { LedgerUpgradeable } from "@synaps3/core/primitives/upgradeable/LedgerUpgradeable.sol";
+import { ILockOperator } from "@synaps3/core/interfaces/base/ILockOperator.sol";
+import { FinancialOps } from "@synaps3/core/libraries/FinancialOps.sol";
+
+abstract contract LockOperatorUpgradeable is Initializable, LedgerUpgradeable, ILockOperator {
+ using FinancialOps for address;
+
+ /// @custom:storage-location erc7201:lockoperatorupgradeable
+ struct LockOperatorStorage {
+ /// @dev Holds the relation between approved funds, the currency, and amount
+ /// @dev Holds the registry of locked funds for accounts.
+ mapping(address => mapping(address => uint256)) _locked;
+ }
+
+ /// @dev Storage slot for LockOperatorUpgradeable, calculated using a unique namespace to avoid conflicts.
+ /// The `LOCK_OPERATOR_SLOT` constant is used to point to the location of the storage.
+ bytes32 private constant LOCK_OPERATOR_SLOT = 0xece3ff917f3a3127e521e0c3f2f90ff09a3c8199be32f9b40bff79e776960800;
+
+ /// @dev Initializes the contract and ensures it is upgradeable.
+ /// Even if the initialization is harmless, this ensures the contract follows upgradeable contract patterns.
+ /// This is the method to initialize this contract and any other extended contracts.
+ /// slither-disable-next-line naming-convention
+ function __LockOperator_init() internal onlyInitializing {
+ __Ledger_init();
+ }
+
+ /// @dev Function to initialize the contract without chaining, typically used in child contracts.
+ /// This is the method to initialize this contract as standalone.
+ /// slither-disable-next-line naming-convention
+ function __LockOperator_init_unchained() internal onlyInitializing {}
+
+ /// @notice Retrieves the locked balance of an account for a specific currency.
+ /// @dev Returns the value stored in the `_locked` mapping for the given `account` and `currency`.
+ /// @param account The address of the account whose locked balance is being queried.
+ /// @param currency The address of the currency to check the locked balance for.
+ /// @return The locked balance of the specified account for the given currency.
+ function getLockedBalance(address account, address currency) external view returns (uint256) {
+ return _getLockedBalance(account, currency);
+ }
+
+ /// @notice Locks a specific amount of funds for a given account.
+ /// @dev The funds are immobilized and cannot be withdrawn or transferred until released.
+ /// Only operator role can handle this methods.
+ /// An approval is not needed, the protocol operate directly on the user funds to simplify operations.
+ /// @param account The address of the account for which the funds will be locked.
+ /// @param amount The amount of funds to lock.
+ /// @param currency The currency to associate lock with. Use address(0) for the native coin.
+ function _lock(
+ address account,
+ uint256 amount,
+ address currency
+ ) internal onlyValidOperation(account, amount) returns (uint256) {
+ if (getLedgerBalance(account, currency) < amount) revert NoFundsToLock();
+ _subLedgerEntry(account, amount, currency);
+ _sumLockedAmount(account, amount, currency);
+ emit FundsLocked(msg.sender, account, amount, currency);
+ return amount;
+ }
+
+ /// @notice Release a specific amount of funds from locked pool.
+ /// @param account The address of the account for which the funds will be released.
+ /// @param amount The amount of funds to release.
+ /// @param currency The currency to associate release with. Use address(0) for the native coin.
+ function _release(
+ address account,
+ uint256 amount,
+ address currency
+ ) internal onlyValidOperation(account, amount) returns (uint256) {
+ if (_getLockedBalance(account, currency) < amount) revert NoFundsToRelease();
+ _subLockedAmount(account, amount, currency);
+ _sumLedgerEntry(account, amount, currency);
+ emit FundsReleased(msg.sender, account, amount, currency);
+ return amount;
+ }
+
+ /// @notice Claims a specific amount of locked funds on behalf of a claimer.
+ /// @dev The claimer is authorized to process the funds from the account.
+ /// Only operator role can handle this methods.
+ /// @param account The address of the account whose funds are being claimed.
+ /// @param amount The amount of funds to claim.
+ /// @param currency The currency to associate claim with. Use address(0) for the native coin.
+ function _claim(
+ address account,
+ uint256 amount,
+ address currency
+ ) internal onlyValidOperation(account, amount) returns (uint256) {
+ if (_getLockedBalance(account, currency) < amount) revert NoFundsToClaim();
+ _subLockedAmount(account, amount, currency); //
+ _sumLedgerEntry(msg.sender, amount, currency);
+ emit FundsClaimed(msg.sender, account, amount, currency);
+ return amount;
+ }
+
+ /// @notice Reduces the locked funds of an account for a specific currency.
+ /// @dev Deducts the specified `amount` from the `_locked` mapping for the given `account` and `currency`.
+ /// @param account The address of the account whose locked funds are being reduced.
+ /// @param amount The amount to subtract from the locked balance.
+ /// @param currency The address of the currency being reduced.
+ function _subLockedAmount(address account, uint256 amount, address currency) private {
+ LockOperatorStorage storage $ = _getLockOperatorStorage();
+ $._locked[account][currency] -= amount;
+ }
+
+ /// @notice Increases the locked funds of an account for a specific currency.
+ /// @dev Adds the specified `amount` to the `_locked` mapping for the given `account` and `currency`.
+ /// @param account The address of the account whose locked funds are being increased.
+ /// @param amount The amount to add to the locked balance.
+ /// @param currency The address of the currency being increased.
+ function _sumLockedAmount(address account, uint256 amount, address currency) private {
+ LockOperatorStorage storage $ = _getLockOperatorStorage();
+ $._locked[account][currency] += amount;
+ }
+
+ /// @notice Retrieves the locked balance of an account for a specific currency.
+ /// @dev Returns the value stored in the `_locked` mapping for the given `account` and `currency`.
+ /// @param account The address of the account whose locked balance is being queried.
+ /// @param currency The address of the currency to check the locked balance for.
+ /// @return The locked balance of the specified account for the given currency.
+ function _getLockedBalance(address account, address currency) internal view returns (uint256) {
+ LockOperatorStorage storage $ = _getLockOperatorStorage();
+ return $._locked[account][currency];
+ }
+
+ /// @notice Internal function to get the allowance operator storage.
+ /// @dev Uses assembly to retrieve the storage at the pre-calculated storage slot.
+ function _getLockOperatorStorage() private pure returns (LockOperatorStorage storage $) {
+ assembly {
+ $.slot := LOCK_OPERATOR_SLOT
+ }
+ }
+}
diff --git a/contracts/custody/CustodianImpl.sol b/contracts/custody/CustodianImpl.sol
index 4cfe613..c397c98 100644
--- a/contracts/custody/CustodianImpl.sol
+++ b/contracts/custody/CustodianImpl.sol
@@ -48,6 +48,7 @@ contract CustodianImpl is
/// @dev Ensures that the provided endpoint is valid and initializes ERC165 and Ownable contracts.
function initialize(string calldata endpoint, address owner) external initializer {
if (bytes(endpoint).length == 0) revert InvalidEndpoint();
+
__ERC165_init();
__Ownable_init(owner);
_endpoint = endpoint;
diff --git a/contracts/custody/CustodianReferendum.sol b/contracts/custody/CustodianReferendum.sol
index d675c3d..bd58687 100644
--- a/contracts/custody/CustodianReferendum.sol
+++ b/contracts/custody/CustodianReferendum.sol
@@ -7,11 +7,8 @@ import { Initializable } from "@openzeppelin/contracts-upgradeable/proxy/utils/I
// solhint-disable-next-line max-line-length
import { AccessControlledUpgradeable } from "@synaps3/core/primitives/upgradeable/AccessControlledUpgradeable.sol";
import { QuorumUpgradeable } from "@synaps3/core/primitives/upgradeable/QuorumUpgradeable.sol";
-import { IAgreementSettler } from "@synaps3/core/interfaces/financial/IAgreementSettler.sol";
-import { IFeeSchemeValidator } from "@synaps3/core/interfaces/economics/IFeeSchemeValidator.sol";
import { ICustodianReferendum } from "@synaps3/core/interfaces/custody/ICustodianReferendum.sol";
import { ICustodianFactory } from "@synaps3/core/interfaces/custody/ICustodianFactory.sol";
-import { FinancialOps } from "@synaps3/core/libraries/FinancialOps.sol";
import { T } from "@synaps3/core/primitives/Types.sol";
/// @title CustodianReferendum
@@ -24,27 +21,17 @@ contract CustodianReferendum is
UUPSUpgradeable,
QuorumUpgradeable,
AccessControlledUpgradeable,
- ICustodianReferendum,
- IFeeSchemeValidator
+ ICustodianReferendum
{
- using FinancialOps for address;
-
/// @custom:oz-upgrades-unsafe-allow state-variable-immutable
- IAgreementSettler public immutable AGREEMENT_SETTLER;
ICustodianFactory public immutable CUSTODIAN_FACTORY;
//slither-disable-end naming-convention
- /// @dev Defines the expiration period for enrollment, determining how long a custodian remains active.
- uint256 private _expirationPeriod;
/// @dev Tracks the number of active enrollments within the system.
uint256 private _enrollmentsCount;
- /// @dev Maps a custodian's address to their respective enrollment deadline timestamp.
- mapping(address => uint256) private _enrollmentDeadline;
-
/// @notice Event emitted when a custodian is registered
/// @param custodian The address of the registered custodian
- /// @param paidFees The amount of fees that were paid upon registration
- event Registered(address indexed custodian, uint256 paidFees);
+ event Registered(address indexed custodian);
/// @notice Event emitted when a custodian is approved
/// @param custodian The address of the approved custodian
@@ -54,34 +41,25 @@ contract CustodianReferendum is
/// @param custodian The address of the revoked custodian
event Revoked(address indexed custodian);
- /// @notice Emitted when a new period is set
- /// @param newPeriod The new period that is set, could be in seconds, blocks, or any other unit
- event PeriodSet(uint256 newPeriod);
-
/// @notice Error thrown when the custodian is not recognized by the factory.
/// @param custodian The address of the unregistered custodian contract.
error UnregisteredCustodian(address custodian);
- /// @notice Error thrown when the custodian does not match the agreement's registered party.
- /// @param custodian The custodian provided for the operation.s
- error CustodianAgreementMismatch(address custodian);
-
/// @notice Modifier to ensure the custodian was deployed through the trusted factory.
/// @param custodian The address of the custodian contract to verify.
modifier onlyValidCustodian(address custodian) {
// ensure the custodian was deployed through the trusted factory and is known to the protocol
if (!CUSTODIAN_FACTORY.isRegistered(custodian)) {
- revert UnregisteredCustodian(msg.sender);
+ revert UnregisteredCustodian(custodian);
}
_;
}
/// @custom:oz-upgrades-unsafe-allow constructor
- constructor(address agreementSettler, address custodianFactory) {
+ constructor(address custodianFactory) {
/// https://forum.openzeppelin.com/t/what-does-disableinitializers-function-mean/28730/5
/// https://forum.openzeppelin.com/t/uupsupgradeable-vulnerability-post-mortem/15680
_disableInitializers();
- AGREEMENT_SETTLER = IAgreementSettler(agreementSettler);
CUSTODIAN_FACTORY = ICustodianFactory(custodianFactory);
}
@@ -90,54 +68,18 @@ contract CustodianReferendum is
__Quorum_init();
__UUPSUpgradeable_init();
__AccessControlled_init(accessManager);
- // 6 months initially..
- _expirationPeriod = 180 days;
- }
-
- /// @notice Checks if the given fee scheme is supported in this context.
- /// @param scheme The fee scheme to validate.
- /// @return True if the scheme is supported.
- function isFeeSchemeSupported(T.Scheme scheme) external pure returns (bool) {
- // support only FLAT scheme
- return scheme == T.Scheme.FLAT;
- }
-
- /// @notice Retrieves the current expiration period for enrollments or registrations.
- function getExpirationPeriod() external view returns (uint256) {
- return _expirationPeriod;
- }
-
- /// @notice Retrieves the enrollment deadline for a custodian.
- /// @param custodian The address of the custodian.
- function getEnrollmentDeadline(address custodian) external view returns (uint256) {
- return _enrollmentDeadline[custodian];
- }
-
- /// @notice Retrieves the total number of enrollments.
- function getEnrollmentCount() external view returns (uint256) {
- return _enrollmentsCount;
}
/// @notice Checks if the entity is active.
/// @dev This function verifies the active status of the custodian.
/// @param custodian The custodian's address to check.
function isActive(address custodian) external view returns (bool) {
- // TODO a renovation mechanism is needed to update the enrollment time
- /// It ensures that custodians remain engaged and do not become inactive for extended periods.
- /// The enrollment deadline enforces a time-based mechanism where custodians must renew
- /// their registration to maintain their active status. This prevents dormant custodians
- /// from continuing to benefit from the protocol without contributing.
-
// TODO add stateful management to custodians contract, the custodian can
// change his state to "maintenance mode" or "inactive" if its facing issues
// in that way the custodian is omitted during load balancing.
// in this line we can check if the custodian contract is active custodian.isActive()
// this is important feature if the custodians want to avoid harm reputation
-
- // This mechanism helps to verify the availability of the custodian,
- // forcing recurrent registrations and ensuring ongoing participation.
- bool notExpiredDeadline = _enrollmentDeadline[custodian] > block.timestamp;
- return _status(uint160(custodian)) == T.Status.Active && notExpiredDeadline;
+ return _status(uint160(custodian)) == T.Status.Active;
}
/// @notice Checks if the entity is waiting.
@@ -154,61 +96,34 @@ contract CustodianReferendum is
return _status(uint160(custodian)) == T.Status.Blocked;
}
- /// @notice Registers a custodian by sending a payment to the contract.
- /// @param proof The unique identifier of the agreement to be enforced.
+ /// @notice Registers a custodian to be approved by council.
/// @param custodian The address of the custodian to register.
- function register(uint256 proof, address custodian) external onlyValidCustodian(custodian) {
- /// TODO penalize invalid endpoints, and revoked during referendum
- // !IMPORTANT:
- // Fees act as a mechanism to prevent abuse or spam by users
- // when submitting custodians for approval. This discourages users from
- // making frivolous or excessive registrations without genuine intent.
- //
- // Additionally, the fees establish a foundation of real interest and commitment
- // from the custodian. This ensures that only those who see value in the protocol
- // and are willing to contribute to its ecosystem will participate.
- //
- // The collected fees are used to support the protocol's operations, aligning
- // individual actions with the broader sustainability of the network.
-
- // IMPORTANT:
- // The expected fees are locked in the agreement, based on the custodians fees defined by the tollgate.
- // If the fee amount is insufficient, the transaction will revert during agreement creation.
- // Only valid agreements can be settled; the validity is guaranteed by the proof.
- T.Agreement memory agreement = AGREEMENT_SETTLER.settleAgreement(proof, msg.sender);
- if (agreement.parties[0] != custodian) {
- revert CustodianAgreementMismatch(custodian);
- }
-
+ function register(address custodian) external whenNotPaused onlyValidCustodian(custodian) {
// register custodian as pending approval
_register(uint160(custodian));
// set the custodian active enrollment period..
- // after this time the custodian is considered inactive and cannot collect his profits...
- _enrollmentDeadline[custodian] = block.timestamp + _expirationPeriod;
- emit Registered(custodian, agreement.fees);
+ emit Registered(custodian);
}
/// @notice Approves a custodian's registration.
/// @param custodian The address of the custodian to approve.
function approve(address custodian) external restricted {
- _enrollmentsCount++;
_approve(uint160(custodian));
+ _enrollmentsCount++;
emit Approved(custodian);
}
/// @notice Revokes the registration of a custodian.
/// @param custodian The address of the custodian to revoke.
function revoke(address custodian) external restricted {
- _enrollmentsCount--;
_revoke(uint160(custodian));
+ _enrollmentsCount--;
emit Revoked(custodian);
}
- /// @notice Sets a new expiration period for an enrollment or registration.
- /// @param newPeriod The new expiration period, in seconds.
- function setExpirationPeriod(uint256 newPeriod) external restricted {
- _expirationPeriod = newPeriod;
- emit PeriodSet(newPeriod);
+ /// @notice Retrieves the total number of enrollments.
+ function getEnrollmentCount() external view returns (uint256) {
+ return _enrollmentsCount;
}
/// @notice Function that should revert when msg.sender is not authorized to upgrade the contract.
diff --git a/contracts/economics/Tollgate.sol b/contracts/economics/Tollgate.sol
index 6aa6387..57d7f90 100644
--- a/contracts/economics/Tollgate.sol
+++ b/contracts/economics/Tollgate.sol
@@ -24,10 +24,10 @@ contract Tollgate is Initializable, UUPSUpgradeable, AccessControlledUpgradeable
/// @dev Tracks registered currencies for specific targets.
/// Uses EnumerableSet for efficient storage and querying of currency addresses.
mapping(address => EnumerableSet.AddressSet) private _registeredCurrencies;
- /// @dev Stores fees associated with specific target and currencies..
- mapping(bytes32 => uint256) private _currencyFees;
- /// @dev Stores the target supported schema.
- mapping(address => T.Scheme) private _targetScheme;
+ /// @dev Stores fees associated with specific target and currencies.
+ mapping(address => mapping(address => uint256)) private _currencyFees;
+ /// @dev Stores the target supported scheme per currency.
+ mapping(address => mapping(address => T.Scheme)) private _targetScheme;
/// @notice Emitted when fees are set or updated.
/// @param target The address or context where the fee applies.
@@ -120,14 +120,8 @@ contract Tollgate is Initializable, UUPSUpgradeable, AccessControlledUpgradeable
/// @param currency The address of the currency.
/// @return The fee value.
function getFees(address target, address currency) external view returns (uint256, T.Scheme) {
- // if scheme is supported return the fee and scheme
- if (!_isSchemeSupported(target, currency)) {
- revert UnsupportedCurrency(target, currency);
- }
-
- T.Scheme scheme = _targetScheme[target];
- bytes32 composedKey = _computeComposedKey(target, currency, scheme);
- uint256 fee = _currencyFees[composedKey];
+ T.Scheme scheme = _targetScheme[target][currency];
+ uint256 fee = _currencyFees[target][currency];
return (fee, scheme);
}
@@ -142,18 +136,10 @@ contract Tollgate is Initializable, UUPSUpgradeable, AccessControlledUpgradeable
uint256 fee,
address currency
) external onlySupportedScheme(scheme, target) onlyValidFeeRepresentation(scheme, fee) restricted {
- // Compute a unique composed key based on the target, currency, and scheme.
- // The composed key uniquely identifies a deterministic combination of these parameters
- // in a flat storage mapping. This avoids nested mappings, improving gas efficiency
- // and simplifying data access through deterministic association.
- // Example: If the target is the policy manager contract, the currency is MMC (ERC20 token),
- // and the scheme is NOMINAL, setting a fee of 10% means:
- // "In the policy manager contract, for MMC, using a nominal scheme, the fee is 10%."
+ // Each (target, currency) pair maintains its own scheme and fee entry.
if (target == address(0)) revert InvalidTargetScheme(target);
- bytes32 composedKey = _computeComposedKey(target, currency, scheme);
-
- _targetScheme[target] = scheme; // eg: rights manager => FLAT
- _currencyFees[composedKey] = fee; // target + currency + scheme = fee
+ _targetScheme[target][currency] = scheme; // eg: rights manager => FLAT per currency
+ _currencyFees[target][currency] = fee; // store fee per target/currency
_registeredCurrencies[target].add(currency);
emit FeesSet(target, currency, scheme, fee);
}
@@ -167,17 +153,7 @@ contract Tollgate is Initializable, UUPSUpgradeable, AccessControlledUpgradeable
/// @param currency The address of the currency to verify.
/// @return `true` if the currency is supported, otherwise `false`.
function _isSchemeSupported(address target, address currency) private view returns (bool) {
- T.Scheme scheme = _targetScheme[target];
- bytes32 composedKey = _computeComposedKey(target, currency, scheme);
- return _registeredCurrencies[target].contains(currency) && _currencyFees[composedKey] > 0;
+ return _registeredCurrencies[target].contains(currency);
}
- /// @notice Computes a unique key for a currency and scheme combination.
- /// @param target The target context.
- /// @param currency The currency associated with the fee.
- /// @param scheme The fee scheme.
- /// @return The computed key as a `bytes32` hash.
- function _computeComposedKey(address target, address currency, T.Scheme scheme) private pure returns (bytes32) {
- return keccak256(abi.encodePacked(target, currency, scheme));
- }
}
diff --git a/contracts/economics/Treasury.sol b/contracts/economics/Treasury.sol
index 294bdb4..e785bf3 100644
--- a/contracts/economics/Treasury.sol
+++ b/contracts/economics/Treasury.sol
@@ -5,10 +5,11 @@ pragma solidity 0.8.26;
import { Initializable } from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
import { UUPSUpgradeable } from "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol";
import { AccessControlledUpgradeable } from "@synaps3/core/primitives/upgradeable/AccessControlledUpgradeable.sol";
+import { ReentrancyGuardTransientUpgradeable } from "@openzeppelin/contracts-upgradeable/utils/ReentrancyGuardTransientUpgradeable.sol";
import { BalanceOperatorUpgradeable } from "@synaps3/core/primitives/upgradeable/BalanceOperatorUpgradeable.sol";
import { ITreasury } from "@synaps3/core/interfaces/economics/ITreasury.sol";
-// import { IFeesCollector } from "@synaps3/core/interfaces/economics/IFeesCollector.sol";
+import { IFeesCollector } from "@synaps3/core/interfaces/economics/IFeesCollector.sol";
import { FinancialOps } from "@synaps3/core/libraries/FinancialOps.sol";
import { LoopOps } from "@synaps3/core/libraries/LoopOps.sol";
@@ -19,6 +20,7 @@ contract Treasury is
Initializable,
UUPSUpgradeable,
AccessControlledUpgradeable,
+ ReentrancyGuardTransientUpgradeable,
BalanceOperatorUpgradeable,
ITreasury
{
@@ -41,6 +43,7 @@ contract Treasury is
function initialize(address accessManager) public initializer {
__UUPSUpgradeable_init();
__BalanceOperator_init();
+ __ReentrancyGuardTransient_init();
__AccessControlled_init(accessManager);
}
@@ -52,32 +55,67 @@ contract Treasury is
// function allocate(address pool, uint256 amount) restricted;
// eg: proposal: deposit N fees to staking pool, deposit N fees to development pool, rewards, etc
- /// @notice Deposits a specified amount of currency into the treasury for a given recipient.
- /// @param pool The address of the pool to credit with the deposit.
+ /// @notice Deposits a specified amount of currency into the treasury for a given recipient (pool).
+ /// @dev Only whitelisted accounts (via `restricted`) can interact with this method.
+ /// This prevents arbitrary accounts from injecting funds into the treasury.
+ /// @param pool The address of the pool credited with the deposit.
/// @param amount The amount of currency to deposit.
- /// @param currency The address of the ERC20 token to deposit.
+ /// @param currency The address of the ERC20 token to deposit (use `address(0)` for native).
+ /// @return The confirmed deposited amount.
function deposit(
address pool,
uint256 amount,
address currency
- ) public override(BalanceOperatorUpgradeable) restricted returns (uint256) {
- // restricted deposit to avoid invalid operations
- // only allowed accounts can interact with this method
- return super.deposit(pool, amount, currency);
+ ) external payable whenNotPaused restricted returns (uint256) {
+ return _deposit(pool, amount, currency);
+ }
+
+ /// @notice Withdraws tokens from the treasury to a specified recipient.
+ /// @dev Restricted to authorized accounts (via `restricted`).
+ /// Ensures treasury funds are only withdrawn under governance-approved flows.
+ /// @param recipient The address receiving the withdrawn tokens.
+ /// @param amount The amount of tokens to withdraw.
+ /// @param currency The token address for the withdrawal (use `address(0)` for native).
+ /// @return The confirmed withdrawn amount.
+ function withdraw(
+ address recipient,
+ uint256 amount,
+ address currency
+ ) external whenNotPaused restricted returns (uint256) {
+ return _withdraw(recipient, amount, currency);
+ }
+
+ /// @notice Transfers tokens internally in the treasury ledger from the caller to a recipient.
+ /// @dev Restricted to authorized accounts (via `restricted`).
+ /// Unlike `withdraw`, this does not move funds externally but shifts balances inside the ledger.
+ /// @param recipient The address credited with the transfer.
+ /// @param amount The amount to transfer.
+ /// @param currency The token being transferred (use `address(0)` for native).
+ /// @return The confirmed transferred amount.
+ function transfer(
+ address recipient,
+ uint256 amount,
+ address currency
+ ) external whenNotPaused restricted returns (uint256) {
+ return _transfer(recipient, amount, currency);
}
/// @notice Collects accrued fees for a specified currency from an authorized fee collector. (visitable)
/// @dev This function requests the given collector to disburse its collected fees
/// for the specified currency. The collected funds are then credited to the treasury pool.
/// Only the governor can execute this function, ensuring controlled fee collection.
- /// @param collector The address of an authorized fee collector.
+ /// @param amount The amount to collect from fee collector.
/// @param currency The address of the ERC20 token for which fees are being collected.
- function collectFees(address collector, address currency) external restricted nonReentrant {
- // TODO update adding amount param on disburse call
- // IFeesCollector feesCollector = IFeesCollector(collector);
- // uint256 collected = feesCollector.disburse(currency);
- // _sumLedgerEntry(address(this), collected, currency);
- // emit FeesCollected(collector, collected, currency);
+ /// @param collector The address of an authorized fee collector.
+ function collectFees(
+ uint256 amount,
+ address currency,
+ address collector
+ ) external restricted whenNotPaused nonReentrant {
+ IFeesCollector feesCollector = IFeesCollector(collector);
+ uint256 collected = feesCollector.disburse(amount, currency);
+ _sumLedgerEntry(address(this), collected, currency);
+ emit FeesCollected(collector, collected, currency);
}
/// @notice Function that should revert when msg.sender is not authorized to upgrade the contract.
diff --git a/contracts/financial/AgreementManager.sol b/contracts/financial/AgreementManager.sol
index 4c854d1..5fe5c6c 100644
--- a/contracts/financial/AgreementManager.sol
+++ b/contracts/financial/AgreementManager.sol
@@ -2,7 +2,6 @@
// NatSpec format convention - https://docs.soliditylang.org/en/v0.5.10/natspec-format.html
pragma solidity 0.8.26;
-import { EnumerableSet } from "@openzeppelin/contracts/utils/structs/EnumerableSet.sol";
import { Initializable } from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
import { UUPSUpgradeable } from "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol";
import { AccessControlledUpgradeable } from "@synaps3/core/primitives/upgradeable/AccessControlledUpgradeable.sol";
@@ -13,8 +12,7 @@ import { ITollgate } from "@synaps3/core/interfaces/economics/ITollgate.sol";
import { FinancialOps } from "@synaps3/core/libraries/FinancialOps.sol";
import { FeesOps } from "@synaps3/core/libraries/FeesOps.sol";
import { T } from "@synaps3/core/primitives/Types.sol";
-
-// TODO Doc: Trustless escrow system - modular escrow framework - escrow mechanism (agreement settlement)
+import { C } from "@synaps3/core/primitives/Constants.sol";
/// @title AgreementManager
/// @notice Manages the lifecycle (trustless escrow system) of agreements, including creation and retrieval.
@@ -23,7 +21,6 @@ import { T } from "@synaps3/core/primitives/Types.sol";
contract AgreementManager is Initializable, UUPSUpgradeable, AccessControlledUpgradeable, IAgreementManager {
using FeesOps for uint256;
using FinancialOps for address;
- using EnumerableSet for EnumerableSet.UintSet;
/// KIM: any initialization here is ephemeral and not included in bytecode..
/// so the code within a logic contract’s constructor or global declaration
@@ -38,6 +35,12 @@ contract AgreementManager is Initializable, UUPSUpgradeable, AccessControlledUpg
ILedgerVault public immutable LEDGER_VAULT;
//slither-disable-end naming-convention
+ /// @notice Maximum allowed number of parties per agreement.
+ /// @dev Can be updated by admin to adapt system limits.
+ uint256 private _maxParties;
+ /// @notice The incremental number of proofs generated
+ /// @dev After each proof nonce increments.
+ uint256 private _proofNonce;
/// @dev Holds a bounded key expressing the agreement between the parts.
mapping(uint256 => T.Agreement) private _agreementsByProof;
@@ -54,10 +57,13 @@ contract AgreementManager is Initializable, UUPSUpgradeable, AccessControlledUpg
/// @notice Error thrown when a currency is not supported by the specified target.
/// @param target The address or context for which the currency is unsupported.
/// @param currency The address of the unsupported currency.
- error UnsupportedAgreementCurrency(address target, address currency);
+ error UnsupportedAgreementTarget(address target, address currency);
+
+ /// @notice Error thrown when trying to set an invalid maximum number of parties.
+ error InvalidMaxParties(uint256 value);
- /// @notice Error thrown when an agreement includes no parties.
- error NoPartiesInAgreement();
+ /// @notice Error thrown when the number of parties exceeds the protocol limit.
+ error ExceedsMaxParties();
/// @notice Ensures that the specified currency is supported for the given target.
/// @dev This modifier verifies if the `currency` is accepted under the context of `target`.
@@ -66,7 +72,7 @@ contract AgreementManager is Initializable, UUPSUpgradeable, AccessControlledUpg
/// @param currency The address of the currency being checked.
modifier onlySupportedCurrency(address target, address currency) {
bool isCurrencySupported = TOLLGATE.isSupportedCurrency(target, currency);
- if (!isCurrencySupported) revert UnsupportedAgreementCurrency(target, currency);
+ if (!isCurrencySupported) revert UnsupportedAgreementTarget(target, currency);
_;
}
@@ -84,6 +90,19 @@ contract AgreementManager is Initializable, UUPSUpgradeable, AccessControlledUpg
function initialize(address accessManager) public initializer {
__UUPSUpgradeable_init();
__AccessControlled_init(accessManager);
+ _maxParties = 5;
+ }
+
+ /// @notice Updates the maximum number of allowed parties.
+ /// @param newMax The new maximum number of parties.
+ function setMaxParties(uint256 newMax) external onlyAdmin {
+ if (newMax == 0) revert InvalidMaxParties(newMax);
+ _maxParties = newMax;
+ }
+
+ /// @notice Retrieves the current max number of parties.
+ function maxParties() external view returns (uint256) {
+ return _maxParties;
}
/// @notice Creates and stores a new agreement.
@@ -100,12 +119,12 @@ contract AgreementManager is Initializable, UUPSUpgradeable, AccessControlledUpg
bytes calldata payload
) external onlySupportedCurrency(arbiter, currency) returns (uint256) {
// IMPORTANT: The process of distributing funds to accounts should be handled within the settlement logic.
- uint256 confirmed = LEDGER_VAULT.lock(msg.sender, amount, currency);
- T.Agreement memory agreement = previewAgreement(confirmed, currency, arbiter, parties, payload);
+ T.Agreement memory agreement = previewAgreement(amount, currency, arbiter, parties, payload);
+ uint256 confirmed = LEDGER_VAULT.lock(msg.sender, agreement.locked, currency);
// only the initiator can operate with this agreement proof, or transfer the proof to the other party..
// each agreement is unique and immutable, ensuring that it cannot be modified or reconstructed.
uint256 proof = _createAndStoreProof(agreement);
- emit AgreementCreated(msg.sender, proof, amount, currency);
+ emit AgreementCreated(msg.sender, proof, confirmed, currency);
return proof;
}
@@ -128,14 +147,6 @@ contract AgreementManager is Initializable, UUPSUpgradeable, AccessControlledUpg
address[] calldata parties,
bytes calldata payload
) public view onlySupportedCurrency(arbiter, currency) returns (T.Agreement memory) {
- if (parties.length == 0) {
- revert NoPartiesInAgreement();
- }
-
- // TODO Even if we are covered by gas fees, during execution a good way to avoid abuse
- // is penalize parties after N length eg. The max parties allowed is 5, any extra
- // parties are charged with a extra * fee. Denial of Service risk
-
// IMPORTANT:
// Agreements transport value and represent a defined commitment between parties.
// Think of an agreement as similar to a bonus, gift card, prepaid card, or check:
@@ -148,7 +159,13 @@ contract AgreementManager is Initializable, UUPSUpgradeable, AccessControlledUpg
// By locking in fees during agreement creation, the protocol avoids scenarios
// where fee structures change (favorably or unfavorably) after creation,
// which could lead to abuse or exploitation.
- uint256 deductions = _calcFees(amount, arbiter, currency);
+ uint256 baseFees = _calcFees(amount, arbiter, currency);
+ // Even if we are covered by gas fees, during execution a good way to avoid abuse
+ // is penalize parties after N length eg. The initial max parties allowed is 5, any extra
+ // parties are charged with an extra. Denial of Service risk mitigation..
+ uint256 penalization = _calculatePenalization(parties.length, amount);
+ uint256 totalToLock = amount + penalization;
+
// This design ensures fairness and transparency by preventing any future
// adjustments to fees or protocol conditions from affecting the terms of this agreement.
return
@@ -157,8 +174,9 @@ contract AgreementManager is Initializable, UUPSUpgradeable, AccessControlledUpg
currency: currency, // the currency used in transaction
initiator: msg.sender, // the tx initiator
total: amount, // the transaction amount
- fees: deductions, // the protocol fees of the agreement
- parties: parties, // the accounts related to agreement
+ fees: baseFees, // the protocol fees of the agreement
+ locked: totalToLock, // the total to lock, may contain penalization
+ parties: parties, // the additional accounts related to agreement 1:N agreement
payload: payload // any additional data needed during agreement execution
});
}
@@ -171,12 +189,40 @@ contract AgreementManager is Initializable, UUPSUpgradeable, AccessControlledUpg
/// @dev Generates a unique proof for an agreement using keccak256 hashing.
function _createAndStoreProof(T.Agreement memory agreement) private returns (uint256) {
// yes, we can encode full struct as abi.encode with extra overhead..
- bytes memory rawProof = abi.encode(agreement, block.number, address(this));
+ uint256 nonce = _proofNonce++; // each proof MUST be unique
+ bytes memory rawProof = abi.encode(agreement, blockhash(block.number - 1), address(this), nonce);
uint256 proof = uint256(keccak256(rawProof));
_agreementsByProof[proof] = agreement;
return proof;
}
+ /// @dev Calculates the penalization based on parties len and total amount
+ function _calculatePenalization(uint256 partiesLen, uint256 amount) private view returns (uint256 penalization) {
+ if (partiesLen <= _maxParties) return 0;
+ uint256 excess = partiesLen - _maxParties;
+ uint256 multiplierBps = _penaltyBps(excess);
+ penalization = amount.perOf(multiplierBps);
+ }
+
+ /// @dev Computes the penalty BPS as a arithmetic succession.
+ /// 1st extra = 1%, 2nd extra = +2%, 3rd extra = +3%, ...
+ /// Formula: (N * (N + 1) / 2) * 100
+ /// @param excess Number of parties beyond the allowed max.
+ /// @return penaltyBps Total penalty in basis points.
+ function _penaltyBps(uint256 excess) private pure returns (uint256 penaltyBps) {
+ if (excess == 0) return 0;
+ // Formula for the sum of an arithmetic succession: S = n(n + 1) / 2
+ // Example: excess = 3 -> 1 + 2 + 3 = 6 %
+ unchecked {
+ penaltyBps = ((excess * (excess + 1)) / 2) * 100;
+ }
+
+ // strict hard cap revert if bps > 10_0000
+ if (penaltyBps > C.BPS_MAX) {
+ revert ExceedsMaxParties();
+ }
+ }
+
/// @notice Calculates the fee based on the provided total amount, agent, and currency.
/// @dev Reverts if the currency is not supported by the Tollgate or if no fee scheme is defined for the agent.
/// @param total The total amount from which the fee will be calculated.
@@ -184,12 +230,12 @@ contract AgreementManager is Initializable, UUPSUpgradeable, AccessControlledUpg
/// @param currency The address of the currency for which the fee is being calculated.
/// @return The calculated fee amount based on the applicable fee scheme.
function _calcFees(uint256 total, address target, address currency) private view returns (uint256) {
- // !IMPORTANT if fees manager does not support the currency or the target, will revert..
- // TODO avoid revert, just check if the currency is supported and return 0
(uint256 fees, T.Scheme scheme) = TOLLGATE.getFees(target, currency);
+
if (scheme == T.Scheme.BPS) return total.perOf(fees); // bps calc
if (scheme == T.Scheme.NOMINAL) return total.perOf(fees.calcBps()); // nominal to bps
if (total < fees) revert FlatFeeExceedsTotal(total, fees); // if flat fee
+
return fees; // ok flat fee is safe
}
}
diff --git a/contracts/financial/AgreementSettler.sol b/contracts/financial/AgreementSettler.sol
index 40064c5..26d4360 100644
--- a/contracts/financial/AgreementSettler.sol
+++ b/contracts/financial/AgreementSettler.sol
@@ -152,17 +152,26 @@ contract AgreementSettler is
// Penalty fees retained here also help maintain the protocol's economic balance
// and ensure that the system operates sustainably over time.
uint256 fees = agreement.fees; // keep fees as penalty
- uint256 available = agreement.total - fees; // initiator rollback
address initiator = agreement.initiator; // the original initiator
address currency = agreement.currency;
+ // eg. total = 100;
+ // locked = 105 (total + penalization)
+ // penalization = 5 <- paid by initiator during agreement
+ // fees = 10 <- 10%
+ //
+ // available = 90
+ // protocolTake = 10 + 5
+ uint256 penalization = agreement.locked - agreement.total;
+ uint256 available = agreement.total - fees; // initiator rollback
+ uint256 protocolTake = fees + penalization;
_setProofAsSettled(proof);
// slither-disable-start unused-return
- LEDGER_VAULT.claim(initiator, fees, currency);
+ if (protocolTake > 0) LEDGER_VAULT.claim(initiator, protocolTake, currency);
// part of the agreement locked amount is released to the account
if (available > 0) LEDGER_VAULT.release(initiator, available, currency);
// slither-disable-end unused-return
- emit AgreementCancelled(initiator, proof, fees);
+ emit AgreementCancelled(initiator, proof, protocolTake);
return agreement;
}
@@ -193,26 +202,33 @@ contract AgreementSettler is
uint256 proof,
address counterparty
) public onlyValidAgreement(proof) /**hookExec(counterParty) */ returns (T.Agreement memory) {
+ // Arbiter contracts encapsulate distribution logic, so the counterparty can be any address the arbiter authorizes.
// retrieve the agreement to storage to inactivate it and return it
T.Agreement memory agreement = AGREEMENT_MANAGER.getAgreement(proof);
if (agreement.arbiter != msg.sender) revert UnauthorizedEscrowAgent();
- uint256 total = agreement.total; // protocol
- uint256 fees = agreement.fees; // protocol
- uint256 available = total - fees; // holder earnings
+ uint256 total = agreement.total;
+ uint256 locked = agreement.locked;
+ uint256 fees = agreement.fees;
+ uint256 penalization = locked - total;
+ uint256 available = total - fees;
+
address initiator = agreement.initiator;
address currency = agreement.currency;
+ uint256 protocolTake = fees + penalization;
// TODO: Implement a time window to enforce the validity period for agreement settlement.
// Once the window expires, the agreement should be marked as invalid or revert,
// then quit is only way to close the agreement.
+
_setProofAsSettled(proof);
+ // locked may include penalization
// move the funds to settler and transfer the available to counterparty
- LEDGER_VAULT.claim(initiator, total, currency);
+ LEDGER_VAULT.claim(initiator, locked, currency);
// could exists cases where available become zero when fees are flat
if (available > 0) LEDGER_VAULT.transfer(counterparty, available, currency);
- emit AgreementSettled(msg.sender, counterparty, proof, fees);
+ emit AgreementSettled(msg.sender, counterparty, proof, protocolTake);
return agreement;
}
diff --git a/contracts/financial/LedgerVault.sol b/contracts/financial/LedgerVault.sol
index 4c87bb3..83d805e 100644
--- a/contracts/financial/LedgerVault.sol
+++ b/contracts/financial/LedgerVault.sol
@@ -6,6 +6,8 @@ import { Initializable } from "@openzeppelin/contracts-upgradeable/proxy/utils/I
import { UUPSUpgradeable } from "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol";
import { AccessControlledUpgradeable } from "@synaps3/core/primitives/upgradeable/AccessControlledUpgradeable.sol";
import { AllowanceOperatorUpgradeable } from "@synaps3/core/primitives/upgradeable/AllowanceOperatorUpgradeable.sol";
+import { LockOperatorUpgradeable } from "@synaps3/core/primitives/upgradeable/LockOperatorUpgradeable.sol";
+import { ReentrancyGuardTransientUpgradeable } from "@openzeppelin/contracts-upgradeable/utils/ReentrancyGuardTransientUpgradeable.sol";
import { BalanceOperatorUpgradeable } from "@synaps3/core/primitives/upgradeable/BalanceOperatorUpgradeable.sol";
import { ILedgerVault } from "@synaps3/core/interfaces/financial/ILedgerVault.sol";
@@ -18,48 +20,31 @@ import { FinancialOps } from "@synaps3/core/libraries/FinancialOps.sol";
contract LedgerVault is
Initializable,
UUPSUpgradeable,
+ ReentrancyGuardTransientUpgradeable,
AccessControlledUpgradeable,
AllowanceOperatorUpgradeable,
BalanceOperatorUpgradeable,
+ LockOperatorUpgradeable,
ILedgerVault
{
- using FinancialOps for address;
-
- /// @dev Holds the registry of locked funds for accounts.
- mapping(address => mapping(address => uint256)) private _locked;
-
- /// @notice Emitted when funds are locked in the ledger.
- /// @param initiator The address of the entity initiating the lock.
- /// @param from The address of the account whose funds were locked.
- /// @param amount The amount of funds that were locked.
- /// @param currency The address of the currency in which the funds were locked.
- event FundsLocked(address indexed initiator, address indexed from, uint256 amount, address indexed currency);
-
- /// @notice Emitted when locked funds are successfully released.
- /// @param initiator The address of the entity initiating the release.
- /// @param recipient The address of the account whose funds were released.
- /// @param amount The amount of funds that were locked.
- /// @param currency The address of the currency in which the funds were locked.
- event FundsReleased(address indexed initiator, address indexed recipient, uint256 amount, address indexed currency);
-
- /// @notice Emitted when locked funds are successfully claimed.
- /// @param initiator The address of the entity initiating the claim.
- /// @param from The address of the account whose funds were claimed.
- /// @param amount The amount of funds claimed.
- /// @param currency The address of the currency in which the funds were claimed.
- event FundsClaimed(address indexed initiator, address indexed from, uint256 amount, address indexed currency);
-
- /// @notice Thrown when there are no available funds to lock.
- /// @dev This error occurs if an account attempts to lock more funds than available.
- error NoFundsToLock();
-
- /// @notice Thrown when there are no locked funds available to claim.
- /// @dev This error occurs if an account or claimer tries to claim funds that are not locked or insufficient.
- error NoFundsToClaim();
-
- /// @notice Thrown when there are no locked funds available to release.
- /// @dev This error occurs if an operator tries to releases funds that are not locked or insufficient.
- error NoFundsToRelease();
+ /// @dev Tracks which currencies are approved for ledger operations.
+ mapping(address => bool) private _approvedCurrencies;
+
+ /// @notice Emitted when a currency approval state changes.
+ /// @param currency The address of the currency whose approval status changed.
+ /// @param allowed The new approval status.
+ /// @param admin The admin that triggered the change.
+ event CurrencyApprovalUpdated(address indexed currency, bool allowed, address indexed admin);
+
+ /// @notice Error thrown when attempting to use an unapproved currency.
+ /// @param currency The currency that is not approved.
+ error CurrencyNotAllowed(address currency);
+
+ /// @dev Ensures that the provided currency has been approved for ledger operations.
+ modifier onlyAllowedCurrency(address currency) {
+ if (!_isCurrencyAllowed(currency)) revert CurrencyNotAllowed(currency);
+ _;
+ }
/// @custom:oz-upgrades-unsafe-allow constructor
constructor() {
@@ -69,9 +54,12 @@ contract LedgerVault is
}
function initialize(address accessManager) public initializer {
+ __Pausable_init();
+ __LockOperator_init();
__UUPSUpgradeable_init();
__BalanceOperator_init();
__AllowanceOperator_init();
+ __ReentrancyGuardTransient_init();
__AccessControlled_init(accessManager);
}
@@ -86,12 +74,14 @@ contract LedgerVault is
address account,
uint256 amount,
address currency
- ) external restricted onlyValidOperation(account, amount) returns (uint256) {
- if (getLedgerBalance(account, currency) < amount) revert NoFundsToLock();
- _subLedgerEntry(account, amount, currency);
- _sumLockedAmount(account, amount, currency);
- emit FundsLocked(msg.sender, account, amount, currency);
- return amount;
+ ) external
+ restricted
+ whenNotPaused
+ onlyValidOperation(account, amount)
+ onlyAllowedCurrency(currency)
+ returns (uint256)
+ {
+ return _lock(account, amount, currency);
}
/// @notice Release a specific amount of funds from locked pool.
@@ -102,12 +92,14 @@ contract LedgerVault is
address account,
uint256 amount,
address currency
- ) external restricted onlyValidOperation(account, amount) returns (uint256) {
- if (_getLockedAmount(account, currency) < amount) revert NoFundsToRelease();
- _subLockedAmount(account, amount, currency);
- _sumLedgerEntry(account, amount, currency);
- emit FundsReleased(msg.sender, account, amount, currency);
- return amount;
+ ) external
+ restricted
+ whenNotPaused
+ onlyValidOperation(account, amount)
+ onlyAllowedCurrency(currency)
+ returns (uint256)
+ {
+ return _release(account, amount, currency);
}
/// @notice Claims a specific amount of locked funds on behalf of a claimer.
@@ -120,43 +112,120 @@ contract LedgerVault is
address account,
uint256 amount,
address currency
- ) external restricted onlyValidOperation(account, amount) returns (uint256) {
- if (_getLockedAmount(account, currency) < amount) revert NoFundsToClaim();
- _subLockedAmount(account, amount, currency); //
- _sumLedgerEntry(msg.sender, amount, currency);
- emit FundsClaimed(msg.sender, account, amount, currency);
- return amount;
+ ) external
+ restricted
+ whenNotPaused
+ onlyValidOperation(account, amount)
+ onlyAllowedCurrency(currency)
+ returns (uint256)
+ {
+ return _claim(account, amount, currency);
}
- /// @notice Reduces the locked funds of an account for a specific currency.
- /// @dev Deducts the specified `amount` from the `_locked` mapping for the given `account` and `currency`.
- /// @param account The address of the account whose locked funds are being reduced.
- /// @param amount The amount to subtract from the locked balance.
- /// @param currency The address of the currency being reduced.
- function _subLockedAmount(address account, uint256 amount, address currency) private {
- _locked[account][currency] -= amount;
+ /// @notice Approves a specific amount of funds from the caller's balance for a recipient.
+ /// @param to The address of the recipient for whom the funds are being approved.
+ /// @param amount The amount of funds to approve.
+ /// @param currency The address of the ERC20 token to approve. Use `address(0)` for native tokens.
+ function approve(
+ address to,
+ uint256 amount,
+ address currency
+ ) external whenNotPaused onlyAllowedCurrency(currency) returns (uint256) {
+ return _approve(to, amount, currency);
}
- /// @notice Increases the locked funds of an account for a specific currency.
- /// @dev Adds the specified `amount` to the `_locked` mapping for the given `account` and `currency`.
- /// @param account The address of the account whose locked funds are being increased.
- /// @param amount The amount to add to the locked balance.
- /// @param currency The address of the currency being increased.
- function _sumLockedAmount(address account, uint256 amount, address currency) private {
- _locked[account][currency] += amount;
+ /// @notice Revokes the approved funds from the caller's balance for a specific recipient.
+ /// @param to The address of the recipient whose approval is being revoked.
+ /// @param currency The address of the ERC20 token associated with the approval. Use `address(0)` for native tokens.
+ /// @return The amount of funds that were revoked from the approval.
+ function revoke(
+ address to,
+ uint256 amount,
+ address currency
+ ) external whenNotPaused onlyAllowedCurrency(currency) returns (uint256) {
+ return _revoke(to, amount, currency);
}
- /// @notice Retrieves the locked balance of an account for a specific currency.
- /// @dev Returns the value stored in the `_locked` mapping for the given `account` and `currency`.
- /// @param account The address of the account whose locked balance is being queried.
- /// @param currency The address of the currency to check the locked balance for.
- /// @return The locked balance of the specified account for the given currency.
- function _getLockedAmount(address account, address currency) private view returns (uint256) {
- return _locked[account][currency];
+ /// @notice Collects a specific amount of previously approved funds.
+ /// @param from The address of the account from which the approved funds are being collected.
+ /// @param amount The amount of funds to collect.
+ /// @param currency The address of the ERC20 token to collect. Use `address(0)` for native tokens.
+ function collect(
+ address from,
+ uint256 amount,
+ address currency
+ ) external whenNotPaused onlyAllowedCurrency(currency) returns (uint256) {
+ return _collect(from, amount, currency);
+ }
+
+ /// @notice Deposits a specified amount of currency into the contract for a given recipient.
+ /// @param recipient The address of the account to credit with the deposit.
+ /// @param amount The amount of currency to deposit.
+ /// @param currency The address of the ERC20 token to deposit.
+ function deposit(
+ address recipient,
+ uint256 amount,
+ address currency
+ ) external payable whenNotPaused onlyAllowedCurrency(currency) returns (uint256) {
+ return _deposit(recipient, amount, currency);
+ }
+
+ /// @notice Withdraws tokens from the contract to a specified recipient's address.
+ /// @param recipient The address that will receive the withdrawn tokens.
+ /// @param amount The amount of tokens to withdraw.
+ /// @param currency The currency to associate fees with. Use address(0) for the native coin.
+ function withdraw(
+ address recipient,
+ uint256 amount,
+ address currency
+ ) external whenNotPaused onlyAllowedCurrency(currency) nonReentrant returns (uint256) {
+ return _withdraw(recipient, amount, currency);
+ }
+
+ /// @notice Transfers tokens internally within the ledger from the caller to a specified recipient.
+ /// @param recipient The address of the account to credit with the transfer.
+ /// @param amount The amount of tokens to transfer.
+ /// @param currency The address of the currency to transfer. Use `address(0)` for the native coin.
+ function transfer(
+ address recipient,
+ uint256 amount,
+ address currency
+ ) external whenNotPaused onlyAllowedCurrency(currency) returns (uint256) {
+ return _transfer(recipient, amount, currency);
+ }
+
+ /// @notice Allows a currency to be used within the ledger operations.
+ /// @param currency The address of the currency to allow. Use address(0) for the native coin.
+ function allowCurrency(address currency) external restricted {
+ _setCurrencyState(currency, true);
+ }
+
+ /// @notice Blocks a currency from being used within the ledger operations.
+ /// @param currency The address of the currency to block. Use address(0) for the native coin.
+ function blockCurrency(address currency) external restricted {
+ _setCurrencyState(currency, false);
+ }
+
+ /// @notice Returns whether a currency is approved for ledger operations.
+ /// @param currency The address of the currency to verify.
+ function isCurrencyAllowed(address currency) external view returns (bool) {
+ return _isCurrencyAllowed(currency);
}
/// @notice Function that should revert when msg.sender is not authorized to upgrade the contract.
/// @param newImplementation The address of the new implementation contract.
/// @dev See https://docs.openzeppelin.com/contracts/4.x/api/proxy#UUPSUpgradeable-_authorizeUpgrade-address-
function _authorizeUpgrade(address newImplementation) internal override onlyAdmin {}
+
+ /// @dev Registers the approval status for a currency and emits an event if the value changes.
+ function _setCurrencyState(address currency, bool allowed) private {
+ if (_approvedCurrencies[currency] == allowed) return;
+ _approvedCurrencies[currency] = allowed;
+ emit CurrencyApprovalUpdated(currency, allowed, msg.sender);
+ }
+
+ /// @dev Returns true when the currency is currently approved, false otherwise.
+ function _isCurrencyAllowed(address currency) private view returns (bool) {
+ return _approvedCurrencies[currency];
+ }
}
diff --git a/contracts/governance/Governance.sol b/contracts/governance/Governance.sol
deleted file mode 100644
index f1d14d4..0000000
--- a/contracts/governance/Governance.sol
+++ /dev/null
@@ -1,137 +0,0 @@
-// SPDX-License-Identifier: BUSL-1.1
-// NatSpec format convention - https://docs.soliditylang.org/en/v0.5.10/natspec-format.html
-pragma solidity 0.8.26;
-
-import { Governor } from "@openzeppelin/contracts/governance/Governor.sol";
-import { ERC20Votes } from "@openzeppelin/contracts/token/ERC20/extensions/ERC20Votes.sol";
-import { TimelockController } from "@openzeppelin/contracts/governance/TimelockController.sol";
-import { GovernorVotes } from "@openzeppelin/contracts/governance/extensions/GovernorVotes.sol";
-import { GovernorSettings } from "@openzeppelin/contracts/governance/extensions/GovernorSettings.sol";
-import { GovernorCountingSimple } from "@openzeppelin/contracts/governance/extensions/GovernorCountingSimple.sol";
-import { GovernorTimelockControl } from "@openzeppelin/contracts/governance/extensions/GovernorTimelockControl.sol";
-// solhint-disable-next-line max-line-length
-import { GovernorVotesQuorumFraction } from "@openzeppelin/contracts/governance/extensions/GovernorVotesQuorumFraction.sol";
-
-/**
- * @title Governance
- * @notice This contract implements a governance mechanism based on OpenZeppelin's Governor contract.
- * It uses an ERC20Votes token for voting, a TimelockController for queuing and executing proposals, and
- * several governance extensions for enhanced functionality.
- */
-contract Governance is
- Governor,
- GovernorVotes,
- GovernorCountingSimple,
- GovernorVotesQuorumFraction,
- GovernorSettings,
- GovernorTimelockControl
-{
- /**
- * @notice Initializes the governance contract with the specified parameters.
- * @param _mmc The ERC20Votes token contract to be used for voting.
- * @param _timelock The TimelockController contract to be used for queuing and executing proposals.
- *
- * The constructor sets up the governance contract with:
- * - Governor("MMCGovernance"): The name of the governance contract.
- * - GovernorVotes(_mmc): The ERC20 token with voting capabilities.
- * - GovernorVotesQuorumFraction(4): The quorum required is 4% of the total token supply.
- * - GovernorSettings(1, 45818, 0): Configures the governance settings:
- * - Voting Delay: 1 block (the time that must pass between the creation of a proposal and the start of voting).
- * - Voting Period: 45818 blocks (approximately 1 week, the period during which voting is open).
- * - Proposal Threshold: 0 tokens (the minimum number of tokens required to propose a new proposal).
- */
- constructor(
- ERC20Votes _mmc,
- TimelockController _timelock
- )
- Governor("MMCGovernance")
- GovernorVotes(_mmc)
- GovernorVotesQuorumFraction(4)
- GovernorSettings(1 /* 1 block */, 45818 /* 1 week */, 0)
- GovernorTimelockControl(_timelock)
- {}
-
- /// @notice Returns the state of a proposal.
- /// @param proposalId The ID of the proposal.
- function state(uint256 proposalId) public view override(Governor, GovernorTimelockControl) returns (ProposalState) {
- return super.state(proposalId);
- }
-
- /// @notice Checks if a proposal needs to be queued.
- /// @param proposalId The ID of the proposal.
- function proposalNeedsQueuing(
- uint256 proposalId
- ) public view virtual override(Governor, GovernorTimelockControl) returns (bool) {
- return super.proposalNeedsQueuing(proposalId);
- }
-
- /**
- * @notice Queues a proposal's operations.
- * @param proposalId The ID of the proposal.
- * @param targets The addresses of the contracts to call.
- * @param values The values (in wei) to send with the calls.
- * @param calldatas The calldata to send with the calls.
- * @param descriptionHash The hash of the proposal's description.
- * @return The timestamp at which the operations are queued.
- */
- function _queueOperations(
- uint256 proposalId,
- address[] memory targets,
- uint256[] memory values,
- bytes[] memory calldatas,
- bytes32 descriptionHash
- ) internal override(Governor, GovernorTimelockControl) returns (uint48) {
- return super._queueOperations(proposalId, targets, values, calldatas, descriptionHash);
- }
-
- /**
- * @notice Executes a proposal's operations.
- * @param proposalId The ID of the proposal.
- * @param targets The addresses of the contracts to call.
- * @param values The values (in wei) to send with the calls.
- * @param calldatas The calldata to send with the calls.
- * @param descriptionHash The hash of the proposal's description.
- */
- function _executeOperations(
- uint256 proposalId,
- address[] memory targets,
- uint256[] memory values,
- bytes[] memory calldatas,
- bytes32 descriptionHash
- ) internal override(Governor, GovernorTimelockControl) {
- super._executeOperations(proposalId, targets, values, calldatas, descriptionHash);
- }
-
- /**
- * @notice Cancels a proposal.
- * @param targets The addresses of the contracts to call.
- * @param values The values (in wei) to send with the calls.
- * @param calldatas The calldata to send with the calls.
- * @param descriptionHash The hash of the proposal's description.
- * @return The ID of the canceled proposal.
- */
- function _cancel(
- address[] memory targets,
- uint256[] memory values,
- bytes[] memory calldatas,
- bytes32 descriptionHash
- ) internal override(Governor, GovernorTimelockControl) returns (uint256) {
- return super._cancel(targets, values, calldatas, descriptionHash);
- }
-
- /**
- * @notice Returns the address of the executor.
- * @return The address of the executor.
- */
- function _executor() internal view override(Governor, GovernorTimelockControl) returns (address) {
- return super._executor();
- }
-
- /**
- * @notice Returns the proposal threshold.
- * @return The minimum number of tokens required to propose a new proposal.
- */
- function proposalThreshold() public view virtual override(Governor, GovernorSettings) returns (uint256) {
- return super.proposalThreshold();
- }
-}
diff --git a/contracts/governance/TimeLockCtl.sol b/contracts/governance/TimeLockCtl.sol
deleted file mode 100644
index 94b3d3f..0000000
--- a/contracts/governance/TimeLockCtl.sol
+++ /dev/null
@@ -1,14 +0,0 @@
-// SPDX-License-Identifier: BUSL-1.1
-// NatSpec format convention - https://docs.soliditylang.org/en/v0.5.10/natspec-format.html
-pragma solidity 0.8.26;
-
-import { TimelockController } from "@openzeppelin/contracts/governance/TimelockController.sol";
-
-contract Timelock is TimelockController {
- constructor(
- uint256 minDelay,
- address[] memory proposers,
- address[] memory executors,
- address admin
- ) TimelockController(minDelay, proposers, executors, admin) {}
-}
diff --git a/contracts/lifecycle/HookRegistry.sol b/contracts/lifecycle/HookRegistry.sol
index bbe9f44..70f23e5 100644
--- a/contracts/lifecycle/HookRegistry.sol
+++ b/contracts/lifecycle/HookRegistry.sol
@@ -74,8 +74,7 @@ contract HookRegistry is Initializable, UUPSUpgradeable, AccessControlledUpgrade
/// @param hook The address of the hook contract to register.
/// @param interfaceId The interface ID that this hook implements.
/// This allows different kinds of hook logic to be categorized and retrieved by interface ID.
- // TODO: restricted to MOD_ROLE
- function submit(address hook, bytes4 interfaceId) external onlyValidHook(hook) restricted {
+ function submit(address hook, bytes4 interfaceId) external onlyValidHook(hook) onlyAdmin {
_register(uint160(hook));
_hooks[interfaceId] = hook;
emit HookRegistered(hook, interfaceId, msg.sender);
@@ -84,7 +83,7 @@ contract HookRegistry is Initializable, UUPSUpgradeable, AccessControlledUpgrade
/// @notice Approves a registered hook contract.
/// @param hook The address of the hook to be approved.
/// Emits a HookApproved event upon success.
- function approve(address hook) external restricted {
+ function approve(address hook) external onlyAdmin {
_approve(uint160(hook));
emit HookApproved(hook, msg.sender);
}
@@ -92,7 +91,7 @@ contract HookRegistry is Initializable, UUPSUpgradeable, AccessControlledUpgrade
/// @notice Revokes a previously approved hook contract.
/// @param hook The address of the hook to revoke.
/// Emits a HookRevoked event upon success.
- function reject(address hook) external restricted {
+ function reject(address hook) external onlyAdmin {
_revoke(uint160(hook));
emit HookRevoked(hook, msg.sender);
}
diff --git a/contracts/policies/PolicyAudit.sol b/contracts/policies/PolicyAudit.sol
index a26f250..a528bd0 100644
--- a/contracts/policies/PolicyAudit.sol
+++ b/contracts/policies/PolicyAudit.sol
@@ -67,7 +67,7 @@ contract PolicyAudit is Initializable, UUPSUpgradeable, AccessControlledUpgradea
/// @notice Submits an audit request for the given policy.
/// This registers the policy for audit within the system.
/// @param policy The address of the policy to be submitted for auditing.
- function submit(address policy) external onlyValidPolicy(policy) {
+ function submit(address policy) external whenNotPaused onlyValidPolicy(policy) {
_register(uint160(policy));
emit PolicySubmitted(policy, msg.sender);
}
@@ -75,7 +75,7 @@ contract PolicyAudit is Initializable, UUPSUpgradeable, AccessControlledUpgradea
/// @notice Approves the audit of a given policy by a specified auditor.
/// @param policy The address of the policy to be audited.
/// @dev This function emits the PolicyApproved event upon successful audit approval.
- function approve(address policy) external restricted {
+ function approve(address policy) external whenNotPaused onlyAdmin {
_approve(uint160(policy));
emit PolicyApproved(policy, msg.sender);
}
@@ -83,17 +83,29 @@ contract PolicyAudit is Initializable, UUPSUpgradeable, AccessControlledUpgradea
/// @notice Revokes the audit of a given policy by a specified auditor.
/// @param policy The address of the policy whose audit is to be revoked.
/// @dev This function emits the PolicyRevoked event upon successful audit revocation.
- function reject(address policy) external restricted {
+ function reject(address policy) external whenNotPaused onlyAdmin {
_revoke(uint160(policy));
emit PolicyRevoked(policy, msg.sender);
}
- /// @notice Checks if a specific policy contract has been audited.
+ /// @notice Checks if a policy has been approved and remains active.
/// @param policy The address of the policy contract to verify.
- function isAudited(address policy) external view returns (bool) {
+ function isApproved(address policy) external view returns (bool) {
return _status(uint160(policy)) == T.Status.Active;
}
+ /// @notice Checks if a policy has been rejected or blocked by the auditor.
+ /// @param policy The address of the policy contract to verify.
+ function isRejected(address policy) external view returns (bool) {
+ return _status(uint160(policy)) == T.Status.Blocked;
+ }
+
+ /// @notice Checks if a policy is awaiting approval.
+ /// @param policy The address of the policy contract to verify.
+ function isPending(address policy) external view returns (bool) {
+ return _status(uint160(policy)) == T.Status.Waiting;
+ }
+
/// @dev Authorizes the upgrade of the contract.
/// @notice Only the owner can authorize the upgrade.
/// @param newImplementation The address of the new implementation contract.
diff --git a/contracts/policies/PolicyBase.sol b/contracts/policies/PolicyBase.sol
index 37a90e3..5a8de6d 100644
--- a/contracts/policies/PolicyBase.sol
+++ b/contracts/policies/PolicyBase.sol
@@ -7,7 +7,7 @@ import { IRightsPolicyManagerVerifiable } from "@synaps3/core/interfaces/rights/
// solhint-disable-next-line max-line-length
import { IRightsPolicyAuthorizerVerifiable } from "@synaps3/core/interfaces/rights/IRightsPolicyAuthorizerVerifiable.sol";
import { IAttestationProvider } from "@synaps3/core/interfaces/base/IAttestationProvider.sol";
-import { IAssetOwnership } from "@synaps3/core/interfaces/assets/IAssetOwnership.sol";
+import { IAssetRegistry } from "@synaps3/core/interfaces/assets/IAssetRegistry.sol";
import { IPolicy } from "@synaps3/core/interfaces/policies/IPolicy.sol";
import { T } from "@synaps3/core/primitives/Types.sol";
@@ -23,7 +23,7 @@ abstract contract PolicyBase is ERC165, IPolicy {
/// @custom:oz-upgrades-unsafe-allow state-variable-immutable
IAttestationProvider public immutable ATTESTATION_PROVIDER;
/// @custom:oz-upgrades-unsafe-allow state-variable-immutable
- IAssetOwnership public immutable ASSET_OWNERSHIP;
+ IAssetRegistry public immutable ASSET_REGISTRY;
/// @dev Registry to store the relation between (context & account) key => attestation
mapping(bytes32 => uint256) private _attestations;
@@ -84,16 +84,11 @@ abstract contract PolicyBase is ERC165, IPolicy {
_;
}
- constructor(
- address rightsPolicyManager,
- address rightsAuthorizer,
- address assetOwnership,
- address providerAddress
- ) {
+ constructor(address rightsPolicyManager, address rightsAuthorizer, address assetRegistry, address providerAddress) {
RIGHTS_AUTHORIZER = IRightsPolicyAuthorizerVerifiable(rightsAuthorizer);
RIGHTS_POLICY_MANAGER = IRightsPolicyManagerVerifiable(rightsPolicyManager);
ATTESTATION_PROVIDER = IAttestationProvider(providerAddress);
- ASSET_OWNERSHIP = IAssetOwnership(assetOwnership);
+ ASSET_REGISTRY = IAssetRegistry(assetRegistry);
}
/// @notice Retrieves the address of the attestation provider.
@@ -122,7 +117,7 @@ abstract contract PolicyBase is ERC165, IPolicy {
/// @notice Returns the asset holder registered in the ownership contract.
/// @param assetId the asset ID to retrieve the holder.
function _getHolder(uint256 assetId) internal view returns (address) {
- return ASSET_OWNERSHIP.ownerOf(assetId); // Returns the registered owner.
+ return ASSET_REGISTRY.ownerOf(assetId); // Returns the registered owner.
}
/// @dev Internal function to commit an agreement and create an attestation.
@@ -134,10 +129,20 @@ abstract contract PolicyBase is ERC165, IPolicy {
T.Agreement memory agreement,
uint256 expireAt
) internal returns (uint256[] memory) {
+ uint256 fees = agreement.fees;
+ uint256 total = agreement.total;
+ address initiator = agreement.initiator;
+ address[] memory parties = agreement.parties;
+
bytes memory payload = abi.encode(agreement);
- bytes memory data = abi.encode(holder, agreement.initiator, address(this), agreement.parties, payload);
- emit AgreementCommitted(holder, agreement.parties.length, agreement.total, agreement.fees);
- return ATTESTATION_PROVIDER.attest(agreement.parties, expireAt, data);
+ bytes memory data = abi.encode(holder, initiator, address(this), parties, payload);
+
+ // expected invariant results 1:1 between attestations <> parties relation
+ uint256[] memory attestationIds = ATTESTATION_PROVIDER.attest(parties, expireAt, data);
+ assert(attestationIds.length == parties.length);
+
+ emit AgreementCommitted(holder, parties.length, total, fees);
+ return attestationIds;
}
/// @notice Internal function to create and register an attestation.
diff --git a/contracts/rights/RightsAssetCustodian.sol b/contracts/rights/RightsAssetCustodian.sol
index 637ab6b..debc743 100644
--- a/contracts/rights/RightsAssetCustodian.sol
+++ b/contracts/rights/RightsAssetCustodian.sol
@@ -196,6 +196,7 @@ contract RightsAssetCustodian is Initializable, UUPSUpgradeable, AccessControlle
/// @notice Selects a custodian for the given holder using weighted randomness.
/// @dev Balancing is based on priority, demand, and economic backing (balance).
/// Not cryptographically secure randomness; avoid for critical paths.
+ /// Intended purely as an off-chain hint for frontends or operators, and must not gate on-chain logic.
/// @param holder Address of the rights holder.
/// @param currency Token used for economic weight evaluation.
/// @return chosen The address of the selected custodian.
@@ -375,7 +376,7 @@ contract RightsAssetCustodian is Initializable, UUPSUpgradeable, AccessControlle
/// @dev Calculates a pseudo-random value based on the block hash, holder address, currency address,
/// and a total weight. The randomness is derived using keccak256 hashing and modulo operation.
- /// Note: This method is not suitable for secure randomness as it relies on blockhash, which can be influenced.
+ /// IMPORTANT: This method is not suitable for secure randomness as it relies on blockhash, which can be influenced.
///
/// @param holder The address of the holder involved in the calculation.
/// @param currency The address of the currency involved in the calculation.
diff --git a/contracts/rights/RightsPolicyAuthorizer.sol b/contracts/rights/RightsPolicyAuthorizer.sol
index b884326..ff7aebd 100644
--- a/contracts/rights/RightsPolicyAuthorizer.sol
+++ b/contracts/rights/RightsPolicyAuthorizer.sol
@@ -11,7 +11,6 @@ import { ReentrancyGuardTransientUpgradeable } from "@openzeppelin/contracts-upg
import { IRightsPolicyAuthorizer } from "@synaps3/core/interfaces/rights/IRightsPolicyAuthorizer.sol";
import { IPolicyAuditorVerifiable } from "@synaps3/core/interfaces/policies/IPolicyAuditorVerifiable.sol";
import { IPolicy } from "@synaps3/core/interfaces/policies/IPolicy.sol";
-import { ArrayOps } from "@synaps3/core/libraries/ArrayOps.sol";
import { LoopOps } from "@synaps3/core/libraries/LoopOps.sol";
/// @title RightsPolicyAuthorizer
@@ -26,7 +25,6 @@ contract RightsPolicyAuthorizer is
IRightsPolicyAuthorizer
{
using LoopOps for uint256;
- using ArrayOps for address[];
using EnumerableSet for EnumerableSet.AddressSet;
/// KIM: any initialization here is ephemeral and not included in bytecode..
@@ -97,7 +95,10 @@ contract RightsPolicyAuthorizer is
// type safe low level call to policy, call policy initialization with provided data..
(bool success, ) = policy.call(abi.encodeCall(IPolicy.setup, (msg.sender, data)));
if (!success) revert InvalidPolicyInitialization("Error during policy initialization call");
- _authorizedPolicies[msg.sender].add(policy);
+
+ bool authorized = _authorizedPolicies[msg.sender].add(policy);
+ if (!authorized) revert InvalidPolicyInitialization("Error during duplicated policy registration");
+
emit RightsGranted(policy, msg.sender, data);
}
@@ -147,7 +148,7 @@ contract RightsPolicyAuthorizer is
// it may contain uninitialized elements (`address(0)`) if some policies were invalid.
// - The variable `j` represents the number of valid policies that passed the filtering process.
// - To ensure that the returned array contains only these valid policies and no extra default values,
- // we call `slice(j)`, which creates a new array of exact length `j` and copies only
+ // we slice, which creates a new array of exact length `j` and copies only
// the first `j` elements from `filtered`.
// - This prevents returning an array with trailing `address(0)` values, ensuring data integrity
// and reducing unnecessary gas costs when the array is processed elsewhere.
@@ -167,6 +168,6 @@ contract RightsPolicyAuthorizer is
/// and that the policy has been audited.
/// @param policy The address of the policy contract to verify.
function _isValidPolicy(address policy) private view returns (bool) {
- return (policy != address(0) && POLICY_AUDIT.isAudited(policy));
+ return (policy != address(0) && POLICY_AUDIT.isApproved(policy));
}
}
diff --git a/contracts/rights/RightsPolicyManager.sol b/contracts/rights/RightsPolicyManager.sol
index da56958..aa6e0fb 100644
--- a/contracts/rights/RightsPolicyManager.sol
+++ b/contracts/rights/RightsPolicyManager.sol
@@ -16,7 +16,6 @@ import { IRightsPolicyManager } from "@synaps3/core/interfaces/rights/IRightsPol
// solhint-disable-next-line max-line-length
import { IRightsPolicyAuthorizerVerifiable } from "@synaps3/core/interfaces/rights/IRightsPolicyAuthorizerVerifiable.sol";
import { LoopOps } from "@synaps3/core/libraries/LoopOps.sol";
-import { ArrayOps } from "@synaps3/core/libraries/ArrayOps.sol";
import { T } from "@synaps3/core/primitives/Types.sol";
/// @title RightsPolicyManager
@@ -32,7 +31,6 @@ contract RightsPolicyManager is
IRightsPolicyManager
{
using EnumerableSet for EnumerableSet.AddressSet;
- using ArrayOps for address[];
using LoopOps for uint256;
/// Our immutables behave as constants after deployment
@@ -110,6 +108,10 @@ contract RightsPolicyManager is
// 1- retrieves the agreement and marks it as settled..
T.Agreement memory agreement = AGREEMENT_SETTLER.settleAgreement(proof, holder);
bytes memory callData = abi.encodeCall(IPolicy.enforce, (holder, agreement));
+ if (agreement.parties.length == 0) {
+ revert EnforcementFailed("No parties in agreement: at least one party is required.");
+ }
+
/// Type-safe low-level call to policy. The policy is registered to the parties.
/// The policy address is already validated during policy audit and authorization.
/// During `onlyAuthorizedPolicy`, the policy is verified about safety.
@@ -143,7 +145,7 @@ contract RightsPolicyManager is
/// @notice Retrieves the list of active policies matching the criteria for an account.
/// @dev This function filters out policies that are not active, ensuring the returned array
/// contains only valid policies. It first creates a temporary array of the same size as `policies`,
- /// then filters and resizes it to the exact number of valid policies using `slice()`.
+ /// then filters and resizes it to the exact number of valid policies using `slicing`.
/// @param account Address of the account to evaluate.
/// @param criteria Encoded data containing parameters for access verification. eg: assetId, holder, groups, etc
function getActivePolicies(address account, bytes memory criteria) external view returns (address[] memory) {
@@ -167,7 +169,7 @@ contract RightsPolicyManager is
// it may contain uninitialized elements (`address(0)`) if some policies were invalid.
// - The variable `j` represents the number of valid policies that passed the filtering process.
// - To ensure that the returned array contains only these valid policies and no extra default values,
- // we call `slice(j)`, which creates a new array of exact length `j` and copies only
+ // we slice, which creates a new array of exact length `j` and copies only
// the first `j` elements from `filtered`.
// - This prevents returning an array with trailing `address(0)` values, ensuring data integrity
// and reducing unnecessary gas costs when the array is processed elsewhere.
@@ -177,7 +179,7 @@ contract RightsPolicyManager is
return filtered;
}
- /// @notice Retrieves the list of policies associated with a specific account and content ID.
+ /// @notice Retrieves the list of policies associated with a specific account.
/// @param account The address of the account for which policies are being retrieved.
function getPolicies(address account) public view returns (address[] memory) {
// https://docs.openzeppelin.com/contracts/5.x/api/utils#EnumerableSet-values-struct-EnumerableSet-AddressSet-
@@ -215,7 +217,7 @@ contract RightsPolicyManager is
/// @dev Verifies access permissions by calling the policy contract.
/// @param account The address of the user requesting access.
/// @param policy The address of the policy contract.
- /// @param criteria Encoded parameters required for access verification.
+ /// @param criteria Encoded parameters required for access verification. eg. assetId, rightsHolder
/// @return `true` if the policy grants access, otherwise `false`.
function _verifyPolicyAccess(address account, address policy, bytes memory criteria) private view returns (bool) {
bytes memory callData = abi.encodeCall(IPolicy.isAccessAllowed, (account, criteria));
diff --git a/docs/SYNAPSE_AUDIT_DOC.md b/docs/SYNAPSE_AUDIT_DOC.md
new file mode 100644
index 0000000..3b618e7
--- /dev/null
+++ b/docs/SYNAPSE_AUDIT_DOC.md
@@ -0,0 +1,372 @@
+## Synapse Protocol – Auditor Documentation (draft)
+
+> **Scope**: Assets, Rights & Policies, Finance (Escrow & Settlements), Economics, Governance, Access Control
+> **Out of Scope**: Custodian Network (DePIN), DWRR routing, replication/delivery infrastructure
+
+---
+
+### 1. Overview
+
+Synapse Protocol provides a deterministic coordination layer for digital asset registration, licensing, and monetization. Creators define programmable access rules enforced on-chain, with all state transitions governed by role-based permissions and quorum-driven governance. This document summarizes architecture, modules, dependencies, and key audit checkpoints.
+
+
+
+---
+
+
+### 2. Layered System Architecture
+
+| Layer | Purpose | Key Components | Notes |
+| --- | --- | --- | --- |
+| **Blockchain Layer** | Anchors protocol state on EVM networks; executes verifiable rights and settlement logic. | Deployed contracts for Assets, Rights, Policies, Finance, Economics, Governance, Access Control. | Deterministic calldata interactions enable auditability and integrations. |
+| **Coordination Layer** | Core execution environment managing registries, policies, governance, financial ops and economics modules. | Asset registry, referendum, policy authorizer/manager, settlement engines, economics controllers (fees/tollgate, treasury). | Primary audit focus; ensures modules compose deterministically. |
+| **Integration Layer / Peripheral Layer** | Bridges external applications, attestation services, marketplaces. | Peripheral contracts such as `SubscriptionPolicy`, `IAttestationProvider` adapters (e.g., EAS), future SEP integrations. | Minimal in current scope; policies/attestors plug into the coordination layer via `PolicyBase`. |
+
+---
+
+### 3. Smart Contract Summary
+
+| Module | Description & Logic | Core Contracts | Critical Functions | Relationships & Dependencies |
+| --- | --- | --- | --- | --- |
+| **Access Control** | Central role matrix, permission routing, pausing, upgrade authorization. | `AccessManager`, `AccessControlledUpgradeable`, OZ `AccessManagerUpgradeable` | `grantRole`, `revokeRole`, `setTargetFunctionRole`, `setRoleAdmin`, `setTargetClosed`, `_authorizeUpgrade` | All upgradeable modules initialize with `__AccessControlled_init(accessManager)`. Roles (`ADMIN_ROLE`, `GOV_ROLE`, `OPS_ROLE`, etc.) defined in `C`. Uses UUPS upgrade pattern. Contracts inheriting this mixin are pausable (`whenNotPaused`, `restricted`) and can be halted via governance-controlled roles. |
+| **Governance** | Quorum-based approval pipelines for assets and policies; manages FSM transitions. | `AssetReferendum`, `PolicyAudit`, `QuorumUpgradeable` | `submit`, `approve`, `reject`, `revoke`, `isApproved`, `isRejected`, `isPending` | Governed by `AccessManager` roles; relies on `T.Status` state machine. Emits `Submitted`, `Approved`, `Rejected`, `PolicyApproved`, `PolicyRevoked`. |
+| **Assets** | ERC-721 lifecycle management with governance approval and activation controls. | `AssetRegistry`, `AssetReferendum`, `AssetSafe` | `AssetRegistry.register`, `revoke`, `transfer`, `switchState`; `AssetReferendum.submit`, `approve`, `reject`, `revoke`, `isApproved` | `AssetRegistry` queries `IAssetReferendumVerifiable.isApproved`; inherits `AccessControlledUpgradeable`. `AssetReferendum` extends `QuorumUpgradeable`. |
+| **Policies** | Reusable policy primitives providing attestation workflows and auditing pipeline. | `PolicyBase` (abstract), `PolicyAudit`, domain policy implementations | `PolicyBase.setup`, `enforce`, `_commit`, `_setAttestation`, `getLicense`; `PolicyAudit.submit`, `approve`, `reject`, `isApproved`, `isRejected`, `isPending` | `PolicyBase` holds immutables for `RightsPolicyManager`, `RightsPolicyAuthorizer`, `IAssetRegistry`, `IAttestationProvider`. `PolicyAudit` is consumed by authorizer via `isApproved`. |
+| **Rights** | Authorizes policies and enforces access rights over assets; coordinates settlement process. | `RightsPolicyAuthorizer`, `RightsPolicyManager`, `RightsAssetCustodian` | `RightsPolicyAuthorizer.authorizePolicy`, `revokePolicy`, `isPolicyAuthorized`, `getAuthorizedPolicies`; `RightsPolicyManager.registerPolicy`, `getActivePolicy`, `getActivePolicies`, `getPolicies`, `isActivePolicy` | Authorizer depends on `PolicyAudit` (approval), `AccessManager` roles, internal `_authorizing` guard. Manager interacts with `IAgreementSettler`, `IAgreementManager`, `IRightsPolicyAuthorizerVerifiable`, and policy contracts (via `PolicyBase`). |
+| **Finance** | Agreement creation, escrow, settlement, fee routing, ledger management. | `AgreementManager`, `AgreementSettler`, `LedgerVault`, `Treasury` | `AgreementManager.createAgreement`, `previewAgreement`, `setMaxParties`; `AgreementSettler.settleAgreement`, `quitAgreement`; `LedgerVault.deposit`, `release`, `claim`; `Treasury` distribution functions | `AgreementManager` validates fees via `Tollgate`, stores collateral in `LedgerVault`. `AgreementSettler` executes payouts, emits `AgreementSettled`. |
+| **Economics** | Configures fees, basis-point schedules, token economics, and treasury flows. | `Tollgate`, `Treasury`, economic permission scripts | `Tollgate.setFees`, `getFees`, `supportedCurrencies`, `updateSupportedCurrency`; `Treasury.withdraw`, `distribute` | Economics modules referenced by `AgreementManager`/`AgreementSettler`. `Tollgate` controlled via governance (`AccessManager` roles). |
+
+#### Core Directory Components
+
+| Subdirectory | Purpose | Key Elements |
+| --- | --- | --- |
+| `contracts/core/interfaces` | Canonical interfaces consumed across modules. | Rights (`IRightsPolicyAuthorizer`), Finance (`IAgreementManager`), Assets (`IAssetReferendumVerifiable`), Attestations (`IAttestationProvider`). |
+| `contracts/core/primitives` | Shared primitives (Structs, constants, upgradeable mixins). | `AccessControlledUpgradeable`, `QuorumUpgradeable`, `T` (Types), `C` (Constants). |
+| `contracts/core/libraries` | Deterministic utility libraries. | `FinancialOps`, `FeesOps`, `LoopOps`, `CriteriaOps`. |
+| `contracts/core/primitives/upgradeable` | UUPS-ready mixins for inheritance. | `AccessControlledUpgradeable`, `ReentrancyGuardTransientUpgradeable`. |
+
+
+---
+
+### 4. Governance Structure
+
+| Role | Description | Responsibilities | Assigned Entities |
+| --- | --- | --- | --- |
+| `ADMIN_ROLE` | Core administrators controlling upgrades, and role granting. | `_authorizeUpgrade`, `setTargetFunctionRole`, `setTargetClosed`, emergency actions. | Multisig / governance executor (per deployment). |
+| `GOV_ROLE` | Community governance authority. | Approves council compositions, economics changes, policy & asset referendums. | DAO governance process. |
+| `CONTENT_COUNCIL_ROLE` | Oversees content curation and asset approvals. | Voting on `AssetReferendum` submissions. | Council multisig. |
+| `CUSTODY_COUNCIL_ROLE` | Manages custodial operations and related referendums. | Approving custodial contracts, emergency custodial actions. | Custody council. |
+| `OPS_ROLE` | Operational role granted to trusted system contracts. | Invoke restricted functions (`restricted` modifier), handle automated flows. | Contracts like `AgreementManager`, `RightsPolicyManager`, etc. |
+| `SEC_ROLE` | Security guardianship (if implemented). | Fast emergency responses (pause, disable assets). | Security council. |
+| `TREASURER_ROLE` | Treasury management. | `Treasury` withdrawals/distributions, economic adjustments. | Treasury multisig. |
+| `VER_ROLE` | Verified creators/participants. | Bypass certain checks (e.g., asset verification) when designated. | Trusted creators or nodes. |
+
+*Hierarchy*: `GOV_ROLE` (community governance) → councils (`ADMIN_ROLE`,`CONTENT_COUNCIL_ROLE`, `CUSTODY_COUNCIL_ROLE`, `TREASURER_ROLE`) → ops roles (`OPS_ROLE`, `SEC_ROLE`) → contracts/users. `ADMIN_ROLE` executes governance-approved actions (upgrades, role assignments) within `AccessManager`.
+
+
+---
+
+### 5. Module Topology & Flow
+
+**Narrative Flow**
+1. `AccessManager` assigns roles; governance modules operate via `QuorumUpgradeable`.
+2. Asset creators submit proposals to `AssetReferendum`; `AssetRegistry.register` mints ERC-721 upon approval.
+3. Policies pass `PolicyAudit`; rights holders call `RightsPolicyAuthorizer.authorizePolicy`.
+4. `AgreementManager` creates escrow-backed agreements (fees via `Tollgate`, collateral into `LedgerVault`), returning a proof.
+5. `RightsPolicyManager.registerPolicy` consumes the proof, triggers `AgreementSettler`, and executes `PolicyBase.enforce`, storing policy references.
+6. Economics layer (`Tollgate`, `Treasury`) distributes protocol fees during settlement; finance components (`LedgerVault`, `Treasury`) handle balances and payouts.
+
+**ASCII Diagram**
+```
+[AccessManager / Role Control]
+ |
+ v
+ [AssetReferendum] --> approves --> [AssetRegistry]
+ | |
+ v v
+ [PolicyAudit] --(isApproved)--> [RightsPolicyAuthorizer]
+ | |
+ | v
+ | [RightsPolicyManager]
+ | |
+ | (settle) v
+ | [AgreementSettler]
+ | |
+ | [Tollgate / Treasury]
+ | |
+ | [LedgerVault payouts]
+ |
+ governance oversight, upgrades, fee configuration
+```
+
+---
+
+### 6. Contract Relationships
+
+| Relationship | Direction | Description | Events / Standards |
+| --- | --- | --- | --- |
+| Role Assignment | `AccessManager` → All modules | Configures privileged functions, pausing, upgrades. | OpenZeppelin Access Manager, UUPS. |
+| Content Approval | `AssetReferendum` → `AssetRegistry` | Registration gated by referendum-approved asset ID. | `Submitted`, `Approved`, `RegisteredAsset`. |
+| Policy Auditing | `PolicyAudit` → `RightsPolicyAuthorizer` | `authorizePolicy` permitted only if `isApproved`. | `PolicyApproved`, `PolicyRevoked`. |
+| Rights Enforcement | `RightsPolicyAuthorizer` → `PolicyBase.setup` & `RightsPolicyManager.registerPolicy` | Holder-driven authorization; manager enforces via authorized policies; reentrancy guard prevents recursion. | `RightsGranted`, `RightsRevoked`, `Registered`. |
+| Policy Registration | `RightsPolicyManager` → `AgreementSettler` → `PolicyBase` (`enforce`) | Settles agreements, retrieves attestations, registers policies. | `AgreementSettled`, `AttestedAgreement`. |
+| Settlement Execution | `AgreementSettler` → `AgreementManager` / `LedgerVault` / `Treasury` | Reads agreements, claims protocol fees, releases funds to counterparties. | `AgreementSettled`, ledger transfer events. |
+| Escrow & Fees | `AgreementManager` ↔ `Tollgate` / `LedgerVault` | Validates fees, stores collateral, handles releases. | ERC-20 operations via `FinancialOps`; events `AgreementCreated`, `AgreementSettled`. |
+| Economics Distribution | `Tollgate` / `Treasury` → Finance & Governance | Tollgate manages fee schedules; Treasury receives protocol take after settlement. | Fee events in `Tollgate`; Treasury distributions (if implemented). |
+| Attestation Integration | `PolicyBase` → `IAttestationProvider` | Issues attestation IDs for parties; stored for `isActivePolicy`. | Future SEP integration noted. |
+| Upgrade Authorization | `AccessControlledUpgradeable` → `AccessManager` | `_authorizeUpgrade` restricted to admin. | UUPS proxy. |
+
+---
+
+### 7. Critical Invariants & Controls
+
+- **Access Control**: All privileged functions protected by target function roles (`restricted`, `onlyAdmin`). Contracts inheriting `AccessControlledUpgradeable` are pausable (`whenNotPaused`) and subject to governance-controlled halt/resume. Misconfiguration centralizes risk at `AccessManager`.
+- **Quorum FSM**: `QuorumUpgradeable` enforces state transitions (Pending → Waiting → Active/Blocked) for `AssetReferendum` and `PolicyAudit`.
+- **Policy Authorization**: `RightsPolicyAuthorizer` uses `_authorizing` guard to prevent recursive `authorizePolicy`; `RightsPolicyManager` relies on `onlyAuthorizedPolicy`.
+- **Settlement Integrity**: `RightsPolicyManager.registerPolicy` reverts on settlement or enforcement failure, preventing half-complete state.
+- **Attestation Lifecycle**: `PolicyBase._commit` ensures attestation arrays match parties, `_setAttestation` binds context to IDs.
+- **Financial Safety**: `FinancialOps`/`FeesOps` check zero amounts, zero recipients, balance sufficiency; auditors should review edge cases and revert behavior.
+- **Economics Configuration**: `Tollgate` fee schedules are governance-controlled; verify no fee bypass. `Treasury` distribution flows must match governance rules.
+- **Upgrade Safety**: UUPS contracts restrict `_authorizeUpgrade` to admin role; confirm governance process for upgrade proposals/execution.
+- **Event Traceability**: Domain events (`RegisteredAsset`, `PolicyApproved`, `AgreementSettled`, fee events) provide verifiable trails.
+
+---
+
+### 8. Sequence Diagrams
+
+> The diagrams follow the logical lifecycle: asset onboarding → policy vetting → rights registration → content delivery → settlement.
+
+#### 8.1 Asset Approval & Registration
+```mermaid
+sequenceDiagram
+ participant Creator
+ participant AssetReferendum
+ participant GovernanceCouncil
+ participant AssetRegistry
+
+ Creator->>AssetReferendum: submit(assetId)
+ AssetReferendum->>AssetReferendum: _register(assetId) (Waiting state)
+ AssetReferendum->>GovernanceCouncil: emit Submitted(assetId)
+ GovernanceCouncil->>AssetReferendum: approve(assetId)
+ AssetReferendum-->>Creator: assetId marked Active
+ Creator->>AssetRegistry: register(to, assetId)
+ AssetRegistry->>AssetReferendum: isApproved(to, assetId)
+ AssetReferendum-->>AssetRegistry: true
+ AssetRegistry->>AssetRegistry: mint & enable asset
+ AssetRegistry->>Creator: emit RegisteredAsset(to, assetId)
+```
+
+**Notes**
+- `AssetReferendum` uses `QuorumUpgradeable` (Pending → Waiting → Active/Blocked).
+- Registration reverts if the referendum status is not Active.
+
+#### 8.2 Policy Audit & Authorization
+```mermaid
+sequenceDiagram
+ participant PolicyDev
+ participant PolicyAudit
+ participant GovernanceCouncil
+ participant Holder
+ participant RightsPolicyAuthorizer
+
+ PolicyDev->>PolicyAudit: submit(policy)
+ PolicyAudit->>PolicyAudit: _register(policy) (Waiting state)
+ PolicyAudit->>GovernanceCouncil: emit PolicySubmitted(policy)
+ GovernanceCouncil->>PolicyAudit: approve(policy)
+ PolicyAudit-->>PolicyDev: status = Active
+ Holder->>RightsPolicyAuthorizer: authorizePolicy(policy, data)
+ RightsPolicyAuthorizer->>PolicyAudit: isApproved(policy)
+ PolicyAudit-->>RightsPolicyAuthorizer: true
+ RightsPolicyAuthorizer->>RightsPolicyAuthorizer: record authorization
+ RightsPolicyAuthorizer-->>Holder: emit RightsGranted(policy)
+```
+
+**Notes**
+- Policy audit follows the same quorum FSM as asset approval.
+- Authorization attempts fail if audit status is not Active or if ownership checks fail.
+
+#### 8.3 Rights Policy Management (Subscription Example)
+```mermaid
+sequenceDiagram
+ participant Holder
+ participant RightsPolicyAuthorizer
+ participant SubscriptionPolicy
+ participant RightsPolicyManager
+ participant AgreementManager
+ participant AgreementSettler
+ participant AttestationProvider
+
+ Holder->>RightsPolicyAuthorizer: authorizePolicy(SubscriptionPolicy, planData)
+ RightsPolicyAuthorizer->>SubscriptionPolicy: setup(holder, planData)
+ RightsPolicyAuthorizer-->>Holder: emit RightsGranted
+
+ Holder->>AgreementManager: createAgreement(amount, currency, manager, parties, payload)
+ AgreementManager-->>Holder: proof
+
+ Holder->>RightsPolicyManager: registerPolicy(proof, holder, SubscriptionPolicy)
+ RightsPolicyManager->>AgreementSettler: settleAgreement(proof, holder)
+ AgreementSettler->>AgreementManager: getAgreement(proof)
+ AgreementSettler->>SubscriptionPolicy: enforce(holder, agreement)
+ SubscriptionPolicy->>AttestationProvider: attest(parties, expireAt, data)
+ AttestationProvider-->>SubscriptionPolicy: attestationIds
+ SubscriptionPolicy->>SubscriptionPolicy: _setAttestation(account, context, id)
+ SubscriptionPolicy-->>RightsPolicyManager: attestationIds
+ RightsPolicyManager-->>Holder: emit Registered(account, proof, attestationId, policy)
+```
+
+**Notes**
+- Policies derived from `PolicyBase` call `_commit` to generate attestations aligning parties and plan metadata.
+- Registration is idempotent due to `EnumerableSet` storage per account.
+
+#### 8.4 Content Access Authorization (Delivery Nodes)
+```mermaid
+sequenceDiagram
+ participant DeliveryNode
+ participant RightsPolicyManager
+ participant SubscriptionPolicy
+ participant AttestationProvider
+
+ DeliveryNode->>RightsPolicyManager: getActivePolicy(account, criteria)
+ RightsPolicyManager->>RightsPolicyManager: getPolicies(account)
+ RightsPolicyManager->>SubscriptionPolicy: isActivePolicy(account, policy, criteria)
+ SubscriptionPolicy->>SubscriptionPolicy: isRegisteredPolicy(account)
+ SubscriptionPolicy->>AttestationProvider: verify(attestationId, account)
+ AttestationProvider-->>SubscriptionPolicy: valid/invalid
+ SubscriptionPolicy-->>RightsPolicyManager: true/false
+ RightsPolicyManager-->>DeliveryNode: (found?, policyAddress)
+```
+
+**Notes**
+- Delivery nodes act as content gateways; they query policies to decide whether to serve protected content.
+- `criteria` encapsulates context (assetId, holder, plan tier, expiry) for policy evaluation.
+
+#### 8.5 Financial Escrow & Settlement
+```mermaid
+sequenceDiagram
+ participant Holder
+ participant AgreementManager
+ participant Tollgate
+ participant LedgerVault
+ participant RightsPolicyManager
+ participant AgreementSettler
+ participant Treasury
+
+ Holder->>AgreementManager: createAgreement(amount, currency, arbiter, parties, payload)
+ AgreementManager->>Tollgate: getFees(arbiter, currency)
+ Tollgate-->>AgreementManager: feeScheme, feeAmount
+ AgreementManager->>LedgerVault: deposit(holder, totalToLock, currency)
+ LedgerVault-->>AgreementManager: receipt
+ AgreementManager->>AgreementManager: store agreement & emit AgreementCreated
+ AgreementManager-->>Holder: proof (agreementId)
+
+ RightsPolicyManager->>AgreementSettler: settleAgreement(proof, holder)
+ AgreementSettler->>AgreementManager: getAgreement(proof)
+ AgreementSettler->>LedgerVault: claim(protocolTake, currency)
+ AgreementSettler->>Treasury: forward fees
+ AgreementSettler->>LedgerVault: release(counterparty amounts)
+ LedgerVault-->>AgreementSettler: settlement confirmation
+ AgreementSettler-->>RightsPolicyManager: attestationIds, agreement
+ AgreementSettler->>AgreementSettler: emit AgreementSettled
+```
+
+**Notes**
+- `totalToLock = amount + penalization` enforcing honest participation; protocol take = fees + penalization.
+- Treasury handles protocol fees while counterparties receive releases from `LedgerVault`.
+- Settlement artifacts (attestation IDs, events) feed into rights verification and compliance analytics.
+
+### 9. Audit Checklist
+
+1. **AccessManager Configuration**
+ - Verify role assignments, target function mappings, pause controls.
+2. **Governance FSM**
+ - Inspect `QuorumUpgradeable` state transitions for assets/policies.
+3. **Policy & Rights Flow**
+ - Test authorization guard, duplicate policy registration, revocations.
+4. **Financial Settlement**
+ - Validate fee calculations, penalties, ledger updates through settlement and quit flows.
+5. **Economics Modules**
+ - Review fee schedule management in `Tollgate`, treasury distribution logic, and permissioning.
+6. **Attestation Providers**
+ - Evaluate deployed `IAttestationProvider` contracts for data integrity and replay protection.
+7. **Upgrade & Pause Controls**
+ - Confirm `_authorizeUpgrade` restrictions and governance oversight; ensure pausable functions behave as expected.
+
+---
+
+### 10. Tooling & Development Environment
+
+| Category | Tools / Frameworks | Notes |
+| --- | --- | --- |
+| Smart Contract Development | Foundry (forge/anvil), Solidity ^0.8.26 | Deterministic builds, fuzzing, invariants. |
+| Libraries | OpenZeppelin Upgradeable suite, custom Synapse core libraries (`FinancialOps`, `FeesOps`, `QuorumUpgradeable`). | UUPS proxies, access control, quorum FSMs. |
+| Testing | Forge standard library (`forge-std`), fuzz/invariant tests under `test/`. | Tests cover unit, fuzz, and integration flows. |
+| Deployment | CREATE3 factory scripts (`script/deployment/*.s.sol`), custom orchestrations. | Deterministic addresses via CREATE3 salts. |
+| Security | Slither (config in `slither.config.json`), manual audits. | Recommended for static analysis and coverage checks. |
+| Attestation Infra | Ethereum Attestation Service (EAS) (planned), `IAttestationProvider` adapters. | Pluggable provider for policy attestations. |
+
+### 11. Peripheral Implementations
+
+- Subscription policy (`SubscriptionPolicy`) built atop `PolicyBase`: https://github.com/Synaps3Protocol/protocol-periphery-v1/blob/main/contracts/policies/SubscriptionPolicy.sol
+- EAS attestation provider adapter: https://github.com/Synaps3Protocol/protocol-periphery-v1/blob/main/contracts/attestation/Eas.sol
+
+### 12. Deployment Runbooks
+
+| Stage | Scripts (order) | Purpose |
+| --- | --- | --- |
+| Deployment | `deployment/04_Deploy_Economics_Tollgate` → … → `17_Deploy_RightsManager_PolicyManager` | Provision economics, finance, custody, assets, policies, rights modules via CREATE3. |
+| Orchestration | `orchestration/01_Orchestrate_ProtocolHydration` → `03_Orchestrate_ProtocolRightsCustodian` | Hydrate protocol with roles, economic parameters, and custodial network. |
+| Upgrade | `upgrades/01_Upgrade_Economics_Tollgate` → … → `17_Upgrade_Rights_RightsPolicyManager` | Apply UUPS upgrades in deterministic order. |
+
+**Pause / Unpause Runbook**
+1. `SEC_ROLE` is the designated role to pause affected modules (using exposed `pause()` on `AccessControlledUpgradeable`).
+2. Execute pause transactions on required modules (e.g., `RightsPolicyManager.pause()`, `AgreementManager.pause()`, `LedgerVault.pause()` as required).
+3. Broadcast incident report; disable user-facing services.
+4. Remediation: deploy fixes or configuration changes under audit.
+5. Submit proposal to unpause; `SEC_ROLE` executes `unpause()` in reverse order once mitigation is confirmed.
+6. Document event, update post-mortem, and notify stakeholders.
+
+*Run scripts with `make deploy script=` for canonical ordering.*
+
+### 13. Deployment Addresses (Amoy Testnet)
+
+| Contract | Address |
+| --- | --- |
+| AccessManager | `0x8120a8e0688be6b2c0bb469f871e5e7023ca85eb` |
+| AssetReferendum | `0x4edf864dc5e7ef1a15b472954e994f4f95e4d1ab` |
+| AssetRegistry | `0xb439928f5dd092e010c802228d1191e64431eebf` |
+| AssetSafe | `0x4aeb687f491f91234ff6c25170db06fde505014c` |
+| RightsPolicyAuthorizer | `0x64c03e378f29d7a39a9cf2c509c4332336e44d5b` |
+| RightsPolicyManager | `0xac21c4a4ab26c295856365541dab1b4c8873d109` |
+| PolicyAudit | `0xe9ff2342903c5c3975cdae572a937626cbf3d9ed` |
+| AgreementManager | `0x7281685b064d6ffbf77df3e5442f65462e945b0e` |
+| AgreementSettler | `0x20ee3ad9ed569832b451cf796190349a9d6d673e` |
+| LedgerVault | `0x5855f2c385d526e93802123d5cb54465c4e3871c` |
+| Treasury | `0x2bef3819db8181e8d86eb1b2fb3e5999b2ebb8d1` |
+| Tollgate | `0xbae832ee0bd9c212212f7f0190cc3b8ca8586ed3` |
+| RightAssetCustodian | `0xbd67e67756415576002f67856833f9636c24e199` |
+| CustodianFactory | `0x2b03c944c50e373124d4f4fa117037215acf084c` |
+| CustodianReferendum | `0x31f32d9066257805ccccbfe46cfc2455fddd902c` |
+| DefaultCustodian | `0x5b903c9598409a6857056fc8a397f74716a5c71d` |
+| MMC Token | `0x3c7deb6feaeb7a82bde580fb73d8d69c401b219e` |
+| SubscriptionPolicy | `0x3393520c79e540c963afae6619c2b51e6fb04f61` |
+| EAS Attestation Provider | `0xdb33bda43befada78d36cbc4362ac6f240084e7b` |
+
+**Attestation Provider (example)**: Ethereum Attestation Service (EAS) can serve as the concrete `IAttestationProvider` implementation.
+
+*Note*: `SubscriptionPolicy` and the EAS adapter operate in the peripheral/integration layer, interfacing with the coordination layer via `PolicyBase` APIs.
+
+
+### 14. References & Notes
+
+- **Libraries & Standards**: OpenZeppelin Upgradeable suite, custom `FinancialOps`, `FeesOps`, `QuorumUpgradeable`, `ERC721StatefulUpgradeable`.
+- **Future Standards (planned)**: SEP-001/002 (asset metadata), SEP-004 (license metadata), EAS attestation registry.
+- **Event Catalog**:
+ - Assets: `RegisteredAsset`, `RevokedAsset`, `AssetEnabled`, `AssetDisabled`.
+ - Policies: `PolicySubmitted`, `PolicyApproved`, `PolicyRevoked`, `AttestedAgreement`, `AgreementCommitted`.
+ - Rights: `RightsGranted`, `RightsRevoked`, `Registered`.
+ - Finance/Economics: `AgreementCreated`, `AgreementSettled`, `FeesSet` (Tollgate), treasury distributions.
+
+---
+
+
+Prepared for Synapse Protocol auditors to evaluate module architecture, invariants, inter-module dependencies, and governance controls. For comprehensive coverage, integrate attestation provider audits and upcoming economics/custody extensions as they are deployed.
diff --git a/foundry.toml b/foundry.toml
index 178f1ac..80ce2d3 100644
--- a/foundry.toml
+++ b/foundry.toml
@@ -28,6 +28,13 @@ verbosity = 4
[rpc_endpoints]
polygon-amoy = "${AMOY_RPC_URL}"
+arbitrum-sepolia = "${ARBITRUM_RPC_URL}"
[etherscan]
polygon-amoy = { key = "${AMOY_API_KEY}", url = "https://www.oklink.com/api/explorer/v1/contract/verify/async/api/polygonAmoy" }
+arbitrum-sepolia = { key = "${ETHERSCAN_API_KEY}", url = "https://api.arbiscan.io/api" }
+
+[invariant]
+runs = 1000
+depth = 25
+fail_on_revert = true
diff --git a/lcov.info b/lcov.info
index 84c8407..a24dcaa 100644
--- a/lcov.info
+++ b/lcov.info
@@ -1,156 +1,76 @@
TN:
SF:contracts/access/AccessManager.sol
-DA:17,76
+DA:17,89
FN:17,AccessManager.initialize
-FNDA:76,AccessManager.initialize
+FNDA:89,AccessManager.initialize
DA:18,0
-DA:19,76
-DA:68,76
-DA:69,76
-DA:70,76
-DA:71,76
-DA:78,0
-FN:78,AccessManager._authorizeUpgrade
+DA:19,89
+DA:134,89
+DA:135,89
+DA:138,89
+DA:140,89
+DA:141,89
+DA:142,89
+DA:147,0
+FN:147,AccessManager._authorizeUpgrade
FNDA:0,AccessManager._authorizeUpgrade
-DA:79,0
-DA:81,0
-BRDA:81,0,0,-
-BRDA:81,0,1,-
+DA:148,0
+DA:150,0
+BRDA:150,0,0,-
+BRDA:150,0,1,-
FNF:2
FNH:1
-LF:10
-LH:6
-BRF:2
-BRH:0
-end_of_record
-TN:
-SF:contracts/assets/AssetOwnership.sol
-DA:69,5
-FN:69,AssetOwnership.onlyApprovedAsset
-FNDA:5,AssetOwnership.onlyApprovedAsset
-DA:70,5
-BRDA:70,0,0,-
-DA:71,0
-DA:78,0
-FN:78,AssetOwnership.onlyOwner
-FNDA:0,AssetOwnership.onlyOwner
-DA:79,0
-BRDA:79,1,0,-
-DA:80,0
-DA:87,5
-FN:87,AssetOwnership.constructor
-FNDA:5,AssetOwnership.constructor
-DA:90,0
-DA:92,5
-DA:97,5
-FN:97,AssetOwnership.initialize
-FNDA:5,AssetOwnership.initialize
-DA:98,0
-DA:99,0
-DA:100,0
-DA:101,5
-DA:102,5
-DA:108,0
-FN:108,AssetOwnership.supportsInterface
-FNDA:0,AssetOwnership.supportsInterface
-DA:111,0
-DA:128,5
-FN:128,AssetOwnership.register
-FNDA:5,AssetOwnership.register
-DA:129,5
-DA:130,5
-DA:131,5
-DA:137,0
-FN:137,AssetOwnership.revoke
-FNDA:0,AssetOwnership.revoke
-DA:138,0
-DA:139,0
-DA:140,0
-DA:146,0
-FN:146,AssetOwnership.transfer
-FNDA:0,AssetOwnership.transfer
-DA:147,0
-DA:148,0
-DA:155,0
-FN:155,AssetOwnership.switchState
-FNDA:0,AssetOwnership.switchState
-DA:156,0
-DA:158,0
-DA:160,0
-DA:164,5
-FN:164,AssetOwnership._update
-FNDA:5,AssetOwnership._update
-DA:169,5
-DA:173,0
-FN:173,AssetOwnership._increaseBalance
-FNDA:0,AssetOwnership._increaseBalance
-DA:177,0
-DA:182,0
-FN:182,AssetOwnership._authorizeUpgrade
-FNDA:0,AssetOwnership._authorizeUpgrade
-DA:185,5
-FN:185,AssetOwnership._enableAsset
-FNDA:5,AssetOwnership._enableAsset
-DA:186,5
-DA:187,5
-DA:191,0
-FN:191,AssetOwnership._disableAsset
-FNDA:0,AssetOwnership._disableAsset
-DA:192,0
-DA:193,0
-FNF:14
-FNH:6
-LF:43
-LH:16
+LF:12
+LH:8
BRF:2
BRH:0
end_of_record
TN:
SF:contracts/assets/AssetReferendum.sol
-DA:58,13
+DA:58,31
FN:58,AssetReferendum.constructor
-FNDA:13,AssetReferendum.constructor
+FNDA:31,AssetReferendum.constructor
DA:59,0
-DA:63,13
+DA:63,31
FN:63,AssetReferendum.initialize
-FNDA:13,AssetReferendum.initialize
+FNDA:31,AssetReferendum.initialize
DA:64,0
DA:65,0
-DA:66,13
-DA:72,13
+DA:66,31
+DA:72,1304
FN:72,AssetReferendum.submit
-FNDA:13,AssetReferendum.submit
-DA:74,13
-DA:75,13
-BRDA:75,0,0,-
-DA:76,13
-DA:77,13
-DA:99,2
+FNDA:1304,AssetReferendum.submit
+DA:74,1304
+DA:75,1304
+BRDA:75,0,0,1
+DA:76,1303
+DA:77,1303
+DA:99,258
FN:99,AssetReferendum.revoke
-FNDA:2,AssetReferendum.revoke
-DA:100,2
-DA:101,2
-DA:106,2
+FNDA:258,AssetReferendum.revoke
+DA:100,258
+DA:101,258
+DA:106,258
FN:106,AssetReferendum.reject
-FNDA:2,AssetReferendum.reject
-DA:107,2
-DA:108,2
-DA:113,9
+FNDA:258,AssetReferendum.reject
+DA:107,258
+DA:108,258
+DA:113,1042
FN:113,AssetReferendum.approve
-FNDA:9,AssetReferendum.approve
-DA:114,9
-DA:115,9
-DA:121,9
+FNDA:1042,AssetReferendum.approve
+DA:114,1042
+DA:115,1042
+DA:121,1301
FN:121,AssetReferendum.isApproved
-FNDA:9,AssetReferendum.isApproved
-DA:122,9
-DA:123,9
-DA:124,9
-DA:126,9
-DA:131,13
+FNDA:1301,AssetReferendum.isApproved
+DA:122,1301
+DA:123,1301
+DA:124,1301
+DA:126,1301
+DA:131,2073
FN:131,AssetReferendum.isActive
-FNDA:13,AssetReferendum.isActive
-DA:132,13
+FNDA:2073,AssetReferendum.isActive
+DA:132,2073
DA:138,0
FN:138,AssetReferendum._authorizeUpgrade
FNDA:0,AssetReferendum._authorizeUpgrade
@@ -159,47 +79,130 @@ FNH:8
LF:28
LH:24
BRF:1
-BRH:0
+BRH:1
+end_of_record
+TN:
+SF:contracts/assets/AssetRegistry.sol
+DA:69,528
+FN:69,AssetRegistry.onlyApprovedAsset
+FNDA:528,AssetRegistry.onlyApprovedAsset
+DA:70,528
+BRDA:70,0,0,1
+DA:71,1
+DA:78,3
+FN:78,AssetRegistry.onlyOwner
+FNDA:3,AssetRegistry.onlyOwner
+DA:79,3
+BRDA:79,1,0,1
+DA:80,1
+DA:87,18
+FN:87,AssetRegistry.constructor
+FNDA:18,AssetRegistry.constructor
+DA:90,0
+DA:92,18
+DA:97,18
+FN:97,AssetRegistry.initialize
+FNDA:18,AssetRegistry.initialize
+DA:98,0
+DA:99,0
+DA:100,0
+DA:101,18
+DA:102,18
+DA:108,0
+FN:108,AssetRegistry.supportsInterface
+FNDA:0,AssetRegistry.supportsInterface
+DA:111,0
+DA:127,527
+FN:127,AssetRegistry.register
+FNDA:527,AssetRegistry.register
+DA:128,527
+DA:129,526
+DA:130,527
+DA:136,1
+FN:136,AssetRegistry.revoke
+FNDA:1,AssetRegistry.revoke
+DA:137,1
+DA:139,1
+DA:140,1
+DA:141,1
+DA:147,1
+FN:147,AssetRegistry.transfer
+FNDA:1,AssetRegistry.transfer
+DA:148,1
+DA:149,1
+DA:156,2
+FN:156,AssetRegistry.switchState
+FNDA:2,AssetRegistry.switchState
+DA:157,2
+DA:159,2
+DA:161,2
+DA:165,529
+FN:165,AssetRegistry._update
+FNDA:529,AssetRegistry._update
+DA:170,529
+DA:174,0
+FN:174,AssetRegistry._increaseBalance
+FNDA:0,AssetRegistry._increaseBalance
+DA:178,0
+DA:183,0
+FN:183,AssetRegistry._authorizeUpgrade
+FNDA:0,AssetRegistry._authorizeUpgrade
+DA:186,527
+FN:186,AssetRegistry._enableAsset
+FNDA:527,AssetRegistry._enableAsset
+DA:187,527
+DA:188,527
+DA:192,2
+FN:192,AssetRegistry._disableAsset
+FNDA:2,AssetRegistry._disableAsset
+DA:193,2
+DA:194,2
+FNF:14
+FNH:11
+LF:44
+LH:35
+BRF:2
+BRH:2
end_of_record
TN:
SF:contracts/assets/AssetSafe.sol
-DA:40,5
+DA:40,519
FN:40,AssetSafe.onlyHolder
-FNDA:5,AssetSafe.onlyHolder
-DA:41,5
-BRDA:41,0,0,1
-DA:42,1
-DA:48,5
+FNDA:519,AssetSafe.onlyHolder
+DA:41,519
+BRDA:41,0,0,257
+DA:42,257
+DA:48,9
FN:48,AssetSafe.constructor
-FNDA:5,AssetSafe.constructor
+FNDA:9,AssetSafe.constructor
DA:49,0
-DA:50,5
-DA:54,5
+DA:50,9
+DA:54,9
FN:54,AssetSafe.initialize
-FNDA:5,AssetSafe.initialize
+FNDA:9,AssetSafe.initialize
DA:55,0
-DA:56,5
-DA:62,1
+DA:56,9
+DA:62,258
FN:62,AssetSafe.getType
-FNDA:1,AssetSafe.getType
-DA:63,1
-DA:71,2
+FNDA:258,AssetSafe.getType
+DA:63,258
+DA:71,261
FN:71,AssetSafe.getContent
-FNDA:2,AssetSafe.getContent
-DA:72,2
-DA:80,4
+FNDA:261,AssetSafe.getContent
+DA:72,261
+DA:80,262
FN:80,AssetSafe.setContent
-FNDA:4,AssetSafe.setContent
-DA:81,4
-DA:82,4
-DA:83,4
+FNDA:262,AssetSafe.setContent
+DA:81,262
+DA:82,262
+DA:83,262
DA:88,0
FN:88,AssetSafe._authorizeUpgrade
FNDA:0,AssetSafe._authorizeUpgrade
-DA:95,6
+DA:95,523
FN:95,AssetSafe._computeComposedKey
-FNDA:6,AssetSafe._computeComposedKey
-DA:96,6
+FNDA:523,AssetSafe._computeComposedKey
+DA:96,523
FNF:8
FNH:7
LF:20
@@ -208,120 +211,143 @@ BRF:1
BRH:1
end_of_record
TN:
-SF:contracts/core/libraries/ArrayOps.sol
-DA:16,0
-FN:16,ArrayOps.slice
-FNDA:0,ArrayOps.slice
-DA:17,0
-BRDA:17,0,0,-
-DA:18,0
-DA:20,0
+SF:contracts/core/libraries/CriteriaOps.sol
+DA:13,0
+FN:13,CriteriaOps.encode
+FNDA:0,CriteriaOps.encode
+DA:14,0
DA:21,0
-FNF:1
+FN:21,CriteriaOps.encode
+FNDA:0,CriteriaOps.encode
+DA:22,0
+DA:29,0
+FN:29,CriteriaOps.encode
+FNDA:0,CriteriaOps.encode
+DA:30,0
+DA:37,0
+FN:37,CriteriaOps.decode
+FNDA:0,CriteriaOps.decode
+DA:38,0
+FNF:4
FNH:0
-LF:5
+LF:8
LH:0
-BRF:1
+BRF:0
BRH:0
end_of_record
TN:
SF:contracts/core/libraries/FeesOps.sol
-DA:12,3
+DA:12,37
FN:12,FeesOps.isBasePoint
-FNDA:3,FeesOps.isBasePoint
-DA:14,3
-DA:19,2
+FNDA:37,FeesOps.isBasePoint
+DA:14,37
+DA:19,6
FN:19,FeesOps.isNominal
-FNDA:2,FeesOps.isNominal
-DA:21,2
-DA:27,0
+FNDA:6,FeesOps.isNominal
+DA:21,6
+DA:27,519
FN:27,FeesOps.perOf
-FNDA:0,FeesOps.perOf
-DA:31,0
+FNDA:519,FeesOps.perOf
+DA:31,519
BRDA:31,0,0,-
BRDA:31,0,1,-
-DA:32,0
-DA:37,0
+DA:32,519
+DA:37,3
FN:37,FeesOps.calcBps
-FNDA:0,FeesOps.calcBps
-DA:38,0
+FNDA:3,FeesOps.calcBps
+DA:38,3
FNF:4
-FNH:2
+FNH:4
LF:9
-LH:4
+LH:9
BRF:2
BRH:0
end_of_record
TN:
SF:contracts/core/libraries/FinancialOps.sol
-DA:24,0
+DA:24,3
FN:24,FinancialOps._nativeTransfer
-FNDA:0,FinancialOps._nativeTransfer
-DA:25,0
-DA:26,0
-BRDA:26,0,0,-
-DA:33,4
-FN:33,FinancialOps._erc20Transfer
-FNDA:4,FinancialOps._erc20Transfer
-DA:34,4
-DA:41,0
-FN:41,FinancialOps._nativeDeposit
-FNDA:0,FinancialOps._nativeDeposit
-DA:42,0
-BRDA:42,1,0,-
-DA:44,0
-DA:53,39
-FN:53,FinancialOps._erc20Deposit
-FNDA:39,FinancialOps._erc20Deposit
-DA:54,39
-BRDA:54,2,0,1
-DA:58,38
-DA:59,0
-DA:68,0
-FN:68,FinancialOps.increaseAllowance
-FNDA:0,FinancialOps.increaseAllowance
+FNDA:3,FinancialOps._nativeTransfer
+DA:27,3
+DA:28,3
+BRDA:28,0,0,-
+DA:29,0
+DA:37,9
+FN:37,FinancialOps._erc20Transfer
+FNDA:9,FinancialOps._erc20Transfer
+DA:38,9
+DA:45,7
+FN:45,FinancialOps._nativeDeposit
+FNDA:7,FinancialOps._nativeDeposit
+DA:46,7
+BRDA:46,1,0,1
+DA:47,1
+DA:51,0
+DA:60,57
+FN:60,FinancialOps._erc20Deposit
+FNDA:57,FinancialOps._erc20Deposit
+DA:61,57
+BRDA:61,2,0,3
+DA:62,3
+DA:68,54
DA:69,0
-BRDA:69,3,0,-
-DA:70,0
-DA:79,39
-FN:79,FinancialOps.allowance
-FNDA:39,FinancialOps.allowance
-DA:80,39
-DA:81,39
-DA:90,39
-FN:90,FinancialOps.safeDeposit
-FNDA:39,FinancialOps.safeDeposit
-DA:91,39
-BRDA:91,5,0,-
-DA:92,39
-DA:93,39
-DA:99,19
-FN:99,FinancialOps.balanceOf
-FNDA:19,FinancialOps.balanceOf
-DA:100,19
-DA:101,19
-DA:109,4
-FN:109,FinancialOps.transfer
-FNDA:4,FinancialOps.transfer
-DA:110,4
-BRDA:110,8,0,-
-DA:111,4
-BRDA:111,9,0,-
-DA:112,4
-DA:113,4
+DA:78,3
+FN:78,FinancialOps.increaseAllowance
+FNDA:3,FinancialOps.increaseAllowance
+DA:79,3
+BRDA:79,3,0,2
+DA:80,2
+DA:83,1
+DA:92,59
+FN:92,FinancialOps.allowance
+FNDA:59,FinancialOps.allowance
+DA:93,59
+BRDA:93,4,0,1
+DA:94,1
+DA:97,58
+DA:106,66
+FN:106,FinancialOps.safeDeposit
+FNDA:66,FinancialOps.safeDeposit
+DA:107,66
+BRDA:107,5,0,2
+DA:108,2
+DA:111,64
+BRDA:111,6,0,7
+DA:112,7
+DA:115,57
+DA:121,17
+FN:121,FinancialOps.balanceOf
+FNDA:17,FinancialOps.balanceOf
+DA:122,17
+BRDA:122,7,0,4
+DA:123,4
+DA:126,13
+DA:134,13
+FN:134,FinancialOps.transfer
+FNDA:13,FinancialOps.transfer
+DA:135,13
+BRDA:135,8,0,1
+DA:136,1
+DA:139,12
+BRDA:139,9,0,-
+DA:140,0
+DA:143,12
+BRDA:143,10,0,3
+DA:144,3
+DA:147,9
FNF:9
-FNH:6
-LF:30
-LH:20
-BRF:7
-BRH:1
+FNH:9
+LF:41
+LH:37
+BRF:11
+BRH:9
end_of_record
TN:
SF:contracts/core/libraries/LoopOps.sol
-DA:15,9
+DA:15,1003
FN:15,LoopOps.uncheckedInc
-FNDA:9,LoopOps.uncheckedInc
-DA:17,9
+FNDA:1003,LoopOps.uncheckedInc
+DA:17,1003
DA:26,0
FN:26,LoopOps.uncheckedDec
FNDA:0,LoopOps.uncheckedDec
@@ -348,83 +374,83 @@ BRH:0
end_of_record
TN:
SF:contracts/core/libraries/RollingOps.sol
-DA:36,6
+DA:36,5
FN:36,RollingOps.configure
-FNDA:6,RollingOps.configure
-DA:37,6
+FNDA:5,RollingOps.configure
+DA:37,5
BRDA:37,0,0,1
-DA:38,5
-DA:42,33
+DA:38,4
+DA:42,13
FN:42,RollingOps.roll
-FNDA:33,RollingOps.roll
-DA:43,33
-DA:47,6
+FNDA:13,RollingOps.roll
+DA:43,13
+DA:47,3
FN:47,RollingOps.contains
-FNDA:6,RollingOps.contains
-DA:48,6
-DA:52,2
+FNDA:3,RollingOps.contains
+DA:48,3
+DA:52,4
FN:52,RollingOps.length
-FNDA:2,RollingOps.length
-DA:53,2
-DA:57,3
+FNDA:4,RollingOps.length
+DA:53,4
+DA:57,2
FN:57,RollingOps.window
-FNDA:3,RollingOps.window
-DA:58,3
-DA:62,6
+FNDA:2,RollingOps.window
+DA:58,2
+DA:62,8
FN:62,RollingOps.at
-FNDA:6,RollingOps.at
-DA:63,6
-DA:67,3
+FNDA:8,RollingOps.at
+DA:63,8
+DA:67,1
FN:67,RollingOps.values
-FNDA:3,RollingOps.values
-DA:68,3
-DA:69,3
+FNDA:1,RollingOps.values
+DA:68,1
+DA:69,1
DA:72,0
-DA:75,3
-DA:84,33
+DA:75,1
+DA:84,13
FN:84,RollingOps._roll
-FNDA:33,RollingOps._roll
-DA:87,33
-BRDA:87,1,0,9
-DA:88,9
-DA:91,33
-DA:95,33
+FNDA:13,RollingOps._roll
+DA:87,13
+BRDA:87,1,0,2
+DA:88,2
+DA:91,13
+DA:95,13
FN:95,RollingOps._rollin
-FNDA:33,RollingOps._rollin
-DA:96,33
-DA:97,33
-DA:101,9
+FNDA:13,RollingOps._rollin
+DA:96,13
+DA:97,13
+DA:101,2
FN:101,RollingOps._rollout
-FNDA:9,RollingOps._rollout
-DA:103,9
-DA:104,9
-DA:106,9
-DA:110,20
-DA:111,20
-DA:114,20
-DA:119,9
-DA:123,6
+FNDA:2,RollingOps._rollout
+DA:103,2
+DA:104,2
+DA:106,2
+DA:110,3
+DA:111,3
+DA:114,3
+DA:119,2
+DA:123,3
FN:123,RollingOps._contains
-FNDA:6,RollingOps._contains
-DA:124,6
-DA:128,74
+FNDA:3,RollingOps._contains
+DA:124,3
+DA:128,38
FN:128,RollingOps._length
-FNDA:74,RollingOps._length
-DA:129,74
-DA:133,6
+FNDA:38,RollingOps._length
+DA:129,38
+DA:133,8
FN:133,RollingOps._at
-FNDA:6,RollingOps._at
-DA:134,6
+FNDA:8,RollingOps._at
+DA:134,8
BRDA:134,2,0,1
-DA:135,5
-DA:139,65
+DA:135,7
+DA:139,20
FN:139,RollingOps._window
-FNDA:65,RollingOps._window
-DA:140,65
-DA:144,3
+FNDA:20,RollingOps._window
+DA:140,20
+DA:144,1
FN:144,RollingOps._values
-FNDA:3,RollingOps._values
-DA:145,3
+FNDA:1,RollingOps._values
+DA:145,1
FNF:15
FNH:15
LF:44
@@ -434,196 +460,203 @@ BRH:3
end_of_record
TN:
SF:contracts/core/primitives/upgradeable/AccessControlledUpgradeable.sol
-DA:28,0
-FN:28,AccessControlledUpgradeable.onlyAdmin
-FNDA:0,AccessControlledUpgradeable.onlyAdmin
-DA:33,0
-BRDA:33,0,0,-
-DA:34,0
-DA:44,286
-FN:44,AccessControlledUpgradeable.__AccessControlled_init
-FNDA:286,AccessControlledUpgradeable.__AccessControlled_init
-DA:45,286
-DA:46,286
-DA:52,286
-FN:52,AccessControlledUpgradeable.__AccessControlled_init_unchained
-FNDA:286,AccessControlledUpgradeable.__AccessControlled_init_unchained
-DA:53,286
-BRDA:53,1,0,-
-DA:54,0
-DA:57,286
-DA:58,286
-DA:65,9
-FN:65,AccessControlledUpgradeable._hasRole
-FNDA:9,AccessControlledUpgradeable._hasRole
-DA:66,9
-DA:67,9
-DA:68,9
-DA:69,9
-DA:73,295
-FN:73,AccessControlledUpgradeable._getAccessControlStorage
-FNDA:295,AccessControlledUpgradeable._getAccessControlStorage
-DA:75,0
-FNF:5
-FNH:4
-LF:18
-LH:13
+DA:29,247
+FN:29,AccessControlledUpgradeable.onlyAdmin
+FNDA:247,AccessControlledUpgradeable.onlyAdmin
+DA:34,247
+BRDA:34,0,0,3
+DA:35,3
+DA:43,2
+FN:43,AccessControlledUpgradeable.pause
+FNDA:2,AccessControlledUpgradeable.pause
+DA:44,0
+DA:50,2
+FN:50,AccessControlledUpgradeable.unpause
+FNDA:2,AccessControlledUpgradeable.unpause
+DA:51,0
+DA:59,317
+FN:59,AccessControlledUpgradeable.__AccessControlled_init
+FNDA:317,AccessControlledUpgradeable.__AccessControlled_init
+DA:60,317
+DA:61,317
+DA:67,317
+FN:67,AccessControlledUpgradeable.__AccessControlled_init_unchained
+FNDA:317,AccessControlledUpgradeable.__AccessControlled_init_unchained
+DA:68,317
+BRDA:68,1,0,1
+DA:69,1
+DA:72,316
+DA:73,316
+DA:80,2636
+FN:80,AccessControlledUpgradeable._hasRole
+FNDA:2636,AccessControlledUpgradeable._hasRole
+DA:81,2636
+DA:82,2636
+DA:83,2636
+DA:84,2636
+DA:88,2952
+FN:88,AccessControlledUpgradeable._getAccessControlStorage
+FNDA:2952,AccessControlledUpgradeable._getAccessControlStorage
+DA:90,0
+FNF:7
+FNH:7
+LF:22
+LH:19
BRF:2
-BRH:0
+BRH:2
end_of_record
TN:
SF:contracts/core/primitives/upgradeable/AllowanceOperatorUpgradeable.sol
-DA:32,41
+DA:32,63
FN:32,AllowanceOperatorUpgradeable.__AllowanceOperator_init
-FNDA:41,AllowanceOperatorUpgradeable.__AllowanceOperator_init
+FNDA:63,AllowanceOperatorUpgradeable.__AllowanceOperator_init
DA:33,0
DA:39,0
FN:39,AllowanceOperatorUpgradeable.__AllowanceOperator_init_unchained
FNDA:0,AllowanceOperatorUpgradeable.__AllowanceOperator_init_unchained
-DA:46,0
+DA:46,13
FN:46,AllowanceOperatorUpgradeable.getApprovedAmount
-FNDA:0,AllowanceOperatorUpgradeable.getApprovedAmount
-DA:47,0
-DA:48,0
-DA:49,0
-DA:56,0
-FN:56,AllowanceOperatorUpgradeable.approve
-FNDA:0,AllowanceOperatorUpgradeable.approve
-DA:61,0
-BRDA:61,0,0,-
-DA:62,0
-DA:63,0
+FNDA:13,AllowanceOperatorUpgradeable.getApprovedAmount
+DA:47,13
+DA:48,13
+DA:49,13
+DA:56,8
+FN:56,AllowanceOperatorUpgradeable._approve
+FNDA:8,AllowanceOperatorUpgradeable._approve
+DA:61,8
+BRDA:61,0,0,1
+DA:62,7
+DA:63,7
DA:64,0
-DA:71,0
-FN:71,AllowanceOperatorUpgradeable.revoke
-FNDA:0,AllowanceOperatorUpgradeable.revoke
-DA:76,0
-BRDA:76,1,0,-
-DA:77,0
-DA:78,0
+DA:71,3
+FN:71,AllowanceOperatorUpgradeable._revoke
+FNDA:3,AllowanceOperatorUpgradeable._revoke
+DA:76,3
+BRDA:76,1,0,1
+DA:77,2
+DA:78,2
DA:79,0
-DA:86,0
-FN:86,AllowanceOperatorUpgradeable.collect
-FNDA:0,AllowanceOperatorUpgradeable.collect
-DA:91,0
-BRDA:91,2,0,-
-DA:92,0
-BRDA:92,3,0,-
-DA:94,0
-DA:95,0
-DA:96,0
-DA:97,0
+DA:86,5
+FN:86,AllowanceOperatorUpgradeable._collect
+FNDA:5,AllowanceOperatorUpgradeable._collect
+DA:91,5
+BRDA:91,2,0,1
+DA:92,4
+BRDA:92,3,0,1
+DA:94,3
+DA:95,3
+DA:96,3
+DA:97,3
DA:98,0
-DA:107,0
+DA:107,5
FN:107,AllowanceOperatorUpgradeable._subApprovedAmount
-FNDA:0,AllowanceOperatorUpgradeable._subApprovedAmount
-DA:108,0
-DA:109,0
-DA:110,0
-DA:119,0
+FNDA:5,AllowanceOperatorUpgradeable._subApprovedAmount
+DA:108,5
+DA:109,5
+DA:110,5
+DA:119,7
FN:119,AllowanceOperatorUpgradeable._sumApprovedAmount
-FNDA:0,AllowanceOperatorUpgradeable._sumApprovedAmount
-DA:120,0
-DA:121,0
-DA:122,0
-DA:130,0
+FNDA:7,AllowanceOperatorUpgradeable._sumApprovedAmount
+DA:120,7
+DA:121,7
+DA:122,7
+DA:130,25
FN:130,AllowanceOperatorUpgradeable._computeComposedKey
-FNDA:0,AllowanceOperatorUpgradeable._computeComposedKey
-DA:131,0
-DA:136,0
+FNDA:25,AllowanceOperatorUpgradeable._computeComposedKey
+DA:131,25
+DA:136,25
FN:136,AllowanceOperatorUpgradeable._getAllowanceOperatorStorage
-FNDA:0,AllowanceOperatorUpgradeable._getAllowanceOperatorStorage
+FNDA:25,AllowanceOperatorUpgradeable._getAllowanceOperatorStorage
DA:138,0
FNF:10
-FNH:1
+FNH:9
LF:37
-LH:1
+LH:31
BRF:4
-BRH:0
+BRH:4
end_of_record
TN:
SF:contracts/core/primitives/upgradeable/BalanceOperatorUpgradeable.sol
-DA:37,82
-FN:37,BalanceOperatorUpgradeable.__BalanceOperator_init
-FNDA:82,BalanceOperatorUpgradeable.__BalanceOperator_init
-DA:38,0
-DA:39,0
-DA:44,0
-FN:44,BalanceOperatorUpgradeable.__BalanceOperator_init_unchained
+DA:31,81
+FN:31,BalanceOperatorUpgradeable.__BalanceOperator_init
+FNDA:81,BalanceOperatorUpgradeable.__BalanceOperator_init
+DA:32,0
+DA:37,0
+FN:37,BalanceOperatorUpgradeable.__BalanceOperator_init_unchained
FNDA:0,BalanceOperatorUpgradeable.__BalanceOperator_init_unchained
-DA:49,3
-FN:49,BalanceOperatorUpgradeable.getBalance
+DA:42,3
+FN:42,BalanceOperatorUpgradeable.getBalance
FNDA:3,BalanceOperatorUpgradeable.getBalance
-DA:50,3
-DA:57,39
-FN:57,BalanceOperatorUpgradeable.deposit
-FNDA:39,BalanceOperatorUpgradeable.deposit
-DA:62,39
-DA:63,38
-DA:64,39
-DA:65,0
-DA:72,3
-FN:72,BalanceOperatorUpgradeable.withdraw
-FNDA:3,BalanceOperatorUpgradeable.withdraw
-DA:77,3
-BRDA:77,0,0,1
-DA:78,2
-DA:79,2
-DA:80,2
-DA:81,0
-DA:88,4
-FN:88,BalanceOperatorUpgradeable.transfer
-FNDA:4,BalanceOperatorUpgradeable.transfer
-DA:93,4
-BRDA:93,1,0,1
-DA:94,3
-BRDA:94,2,0,1
-DA:96,2
-DA:97,2
-DA:98,2
-DA:99,0
-DA:104,0
-FN:104,BalanceOperatorUpgradeable._getBalanceOperatorStorage
+DA:43,3
+DA:50,50
+FN:50,BalanceOperatorUpgradeable._deposit
+FNDA:50,BalanceOperatorUpgradeable._deposit
+DA:55,50
+DA:56,48
+DA:57,50
+DA:58,0
+DA:65,8
+FN:65,BalanceOperatorUpgradeable._withdraw
+FNDA:8,BalanceOperatorUpgradeable._withdraw
+DA:70,8
+BRDA:70,0,0,2
+DA:71,6
+DA:72,6
+DA:73,6
+DA:74,0
+DA:81,505
+FN:81,BalanceOperatorUpgradeable._transfer
+FNDA:505,BalanceOperatorUpgradeable._transfer
+DA:86,505
+BRDA:86,1,0,2
+DA:87,503
+BRDA:87,2,0,-
+DA:89,503
+DA:90,503
+DA:91,503
+DA:92,0
+DA:97,0
+FN:97,BalanceOperatorUpgradeable._getBalanceOperatorStorage
FNDA:0,BalanceOperatorUpgradeable._getBalanceOperatorStorage
-DA:106,0
+DA:99,0
FNF:7
FNH:5
-LF:26
+LF:25
LH:18
BRF:3
-BRH:3
+BRH:2
end_of_record
TN:
SF:contracts/core/primitives/upgradeable/ERC721StatefulUpgradeable.sol
-DA:26,5
+DA:26,18
FN:26,ERC721StatefulUpgradeable.__ERC721Stateful_init
-FNDA:5,ERC721StatefulUpgradeable.__ERC721Stateful_init
+FNDA:18,ERC721StatefulUpgradeable.__ERC721Stateful_init
DA:31,0
FN:31,ERC721StatefulUpgradeable.__ERC721Stateful_init_unchained
FNDA:0,ERC721StatefulUpgradeable.__ERC721Stateful_init_unchained
-DA:35,5
+DA:35,527
FN:35,ERC721StatefulUpgradeable._activate
-FNDA:5,ERC721StatefulUpgradeable._activate
-DA:36,5
-DA:37,5
-DA:42,0
+FNDA:527,ERC721StatefulUpgradeable._activate
+DA:36,527
+DA:37,527
+DA:42,2
FN:42,ERC721StatefulUpgradeable._deactivate
-FNDA:0,ERC721StatefulUpgradeable._deactivate
-DA:43,0
-DA:44,0
-DA:50,0
+FNDA:2,ERC721StatefulUpgradeable._deactivate
+DA:43,2
+DA:44,2
+DA:50,6
FN:50,ERC721StatefulUpgradeable.isActive
-FNDA:0,ERC721StatefulUpgradeable.isActive
-DA:51,0
-DA:52,0
-DA:56,5
+FNDA:6,ERC721StatefulUpgradeable.isActive
+DA:51,6
+DA:52,6
+DA:56,535
FN:56,ERC721StatefulUpgradeable._getERC721StateStorage
-FNDA:5,ERC721StatefulUpgradeable._getERC721StateStorage
+FNDA:535,ERC721StatefulUpgradeable._getERC721StateStorage
DA:58,0
FNF:6
-FNH:3
+FNH:5
LF:13
-LH:5
+LH:11
BRF:0
BRH:0
end_of_record
@@ -640,24 +673,24 @@ FN:49,FeesCollectorUpgradeable.getTreasuryAddress
FNDA:0,FeesCollectorUpgradeable.getTreasuryAddress
DA:50,0
DA:51,0
-DA:58,41
+DA:58,26
FN:58,FeesCollectorUpgradeable.__FeesCollector_init
-FNDA:41,FeesCollectorUpgradeable.__FeesCollector_init
-DA:59,41
-DA:66,41
+FNDA:26,FeesCollectorUpgradeable.__FeesCollector_init
+DA:59,26
+DA:66,26
FN:66,FeesCollectorUpgradeable.__FeesCollector_init_unchained
-FNDA:41,FeesCollectorUpgradeable.__FeesCollector_init_unchained
-DA:67,41
-DA:73,41
+FNDA:26,FeesCollectorUpgradeable.__FeesCollector_init_unchained
+DA:67,26
+DA:73,26
FN:73,FeesCollectorUpgradeable._setTreasuryAddress
-FNDA:41,FeesCollectorUpgradeable._setTreasuryAddress
-DA:74,41
+FNDA:26,FeesCollectorUpgradeable._setTreasuryAddress
+DA:74,26
BRDA:74,1,0,-
-DA:75,41
-DA:76,41
-DA:82,41
+DA:75,26
+DA:76,26
+DA:82,26
FN:82,FeesCollectorUpgradeable._getFeesCollectorStorage
-FNDA:41,FeesCollectorUpgradeable._getFeesCollectorStorage
+FNDA:26,FeesCollectorUpgradeable._getFeesCollectorStorage
DA:84,0
FNF:6
FNH:4
@@ -668,40 +701,40 @@ BRH:0
end_of_record
TN:
SF:contracts/core/primitives/upgradeable/LedgerUpgradeable.sol
-DA:28,5
+DA:28,21
FN:28,LedgerUpgradeable.onlyValidOperation
-FNDA:5,LedgerUpgradeable.onlyValidOperation
-DA:29,5
+FNDA:21,LedgerUpgradeable.onlyValidOperation
+DA:29,21
BRDA:29,0,0,2
-DA:36,45
+DA:36,1087
FN:36,LedgerUpgradeable.getLedgerBalance
-FNDA:45,LedgerUpgradeable.getLedgerBalance
-DA:37,45
-DA:38,45
-DA:45,123
+FNDA:1087,LedgerUpgradeable.getLedgerBalance
+DA:37,1087
+DA:38,1087
+DA:45,213
FN:45,LedgerUpgradeable.__Ledger_init
-FNDA:123,LedgerUpgradeable.__Ledger_init
+FNDA:213,LedgerUpgradeable.__Ledger_init
DA:50,0
FN:50,LedgerUpgradeable.__Ledger_init_unchained
FNDA:0,LedgerUpgradeable.__Ledger_init_unchained
-DA:57,1
+DA:57,7
FN:57,LedgerUpgradeable._setLedgerEntry
-FNDA:1,LedgerUpgradeable._setLedgerEntry
-DA:58,1
-DA:59,1
-DA:67,76
+FNDA:7,LedgerUpgradeable._setLedgerEntry
+DA:58,7
+DA:59,7
+DA:67,1078
FN:67,LedgerUpgradeable._sumLedgerEntry
-FNDA:76,LedgerUpgradeable._sumLedgerEntry
-DA:68,76
-DA:69,76
-DA:77,38
+FNDA:1078,LedgerUpgradeable._sumLedgerEntry
+DA:68,1078
+DA:69,1078
+DA:77,1035
FN:77,LedgerUpgradeable._subLedgerEntry
-FNDA:38,LedgerUpgradeable._subLedgerEntry
-DA:78,38
-DA:79,38
-DA:84,160
+FNDA:1035,LedgerUpgradeable._subLedgerEntry
+DA:78,1035
+DA:79,1035
+DA:84,3207
FN:84,LedgerUpgradeable._getLedgerStorage
-FNDA:160,LedgerUpgradeable._getLedgerStorage
+FNDA:3207,LedgerUpgradeable._getLedgerStorage
DA:86,0
FNF:8
FNH:7
@@ -711,63 +744,125 @@ BRF:1
BRH:1
end_of_record
TN:
+SF:contracts/core/primitives/upgradeable/LockOperatorUpgradeable.sol
+DA:28,63
+FN:28,LockOperatorUpgradeable.__LockOperator_init
+FNDA:63,LockOperatorUpgradeable.__LockOperator_init
+DA:29,0
+DA:35,0
+FN:35,LockOperatorUpgradeable.__LockOperator_init_unchained
+FNDA:0,LockOperatorUpgradeable.__LockOperator_init_unchained
+DA:44,523
+FN:44,LockOperatorUpgradeable._lock
+FNDA:523,LockOperatorUpgradeable._lock
+DA:49,523
+BRDA:49,0,0,3
+DA:50,520
+DA:51,520
+DA:52,520
+DA:53,0
+DA:60,7
+FN:60,LockOperatorUpgradeable._release
+FNDA:7,LockOperatorUpgradeable._release
+DA:65,7
+BRDA:65,1,0,1
+DA:66,6
+DA:67,6
+DA:68,6
+DA:69,0
+DA:78,506
+FN:78,LockOperatorUpgradeable._claim
+FNDA:506,LockOperatorUpgradeable._claim
+DA:83,506
+BRDA:83,2,0,1
+DA:84,505
+DA:85,505
+DA:86,505
+DA:87,0
+DA:95,511
+FN:95,LockOperatorUpgradeable._subLockedAmount
+FNDA:511,LockOperatorUpgradeable._subLockedAmount
+DA:96,511
+DA:97,511
+DA:105,520
+FN:105,LockOperatorUpgradeable._sumLockedAmount
+FNDA:520,LockOperatorUpgradeable._sumLockedAmount
+DA:106,520
+DA:107,520
+DA:115,513
+FN:115,LockOperatorUpgradeable._getLockedAmount
+FNDA:513,LockOperatorUpgradeable._getLockedAmount
+DA:116,513
+DA:117,513
+DA:122,1544
+FN:122,LockOperatorUpgradeable._getLockOperatorStorage
+FNDA:1544,LockOperatorUpgradeable._getLockOperatorStorage
+DA:124,0
+FNF:9
+FNH:8
+LF:32
+LH:26
+BRF:3
+BRH:3
+end_of_record
+TN:
SF:contracts/core/primitives/upgradeable/QuorumUpgradeable.sol
-DA:48,54
+DA:48,81
FN:48,QuorumUpgradeable.__Quorum_init
-FNDA:54,QuorumUpgradeable.__Quorum_init
+FNDA:81,QuorumUpgradeable.__Quorum_init
DA:53,0
FN:53,QuorumUpgradeable.__Quorum_init_unchained
FNDA:0,QuorumUpgradeable.__Quorum_init_unchained
-DA:57,159
+DA:57,8823
FN:57,QuorumUpgradeable._status
-FNDA:159,QuorumUpgradeable._status
-DA:58,159
-DA:59,159
-DA:65,8
+FNDA:8823,QuorumUpgradeable._status
+DA:58,8823
+DA:59,8823
+DA:65,501
FN:65,QuorumUpgradeable._revoke
-FNDA:8,QuorumUpgradeable._revoke
-DA:66,8
-DA:67,8
-BRDA:67,0,0,1
-DA:68,7
-DA:74,4
+FNDA:501,QuorumUpgradeable._revoke
+DA:66,501
+DA:67,501
+BRDA:67,0,0,2
+DA:68,499
+DA:74,260
FN:74,QuorumUpgradeable._block
-FNDA:4,QuorumUpgradeable._block
-DA:75,4
-DA:76,4
-BRDA:76,1,0,-
-DA:77,4
-DA:82,41
+FNDA:260,QuorumUpgradeable._block
+DA:75,260
+DA:76,260
+BRDA:76,1,0,1
+DA:77,259
+DA:82,2129
FN:82,QuorumUpgradeable._approve
-FNDA:41,QuorumUpgradeable._approve
-DA:83,41
-DA:84,41
-BRDA:84,2,0,1
-DA:85,40
-DA:90,3
+FNDA:2129,QuorumUpgradeable._approve
+DA:83,2129
+DA:84,2129
+BRDA:84,2,0,40
+DA:85,2089
+DA:90,2
FN:90,QuorumUpgradeable._quit
-FNDA:3,QuorumUpgradeable._quit
-DA:91,3
-DA:92,3
-BRDA:92,3,0,2
+FNDA:2,QuorumUpgradeable._quit
+DA:91,2
+DA:92,2
+BRDA:92,3,0,1
DA:93,1
-DA:98,54
+DA:98,2398
FN:98,QuorumUpgradeable._register
-FNDA:54,QuorumUpgradeable._register
-DA:99,54
-DA:100,54
-BRDA:100,4,0,1
-DA:101,53
-DA:105,269
+FNDA:2398,QuorumUpgradeable._register
+DA:99,2398
+DA:100,2398
+BRDA:100,4,0,39
+DA:101,2359
+DA:105,14113
FN:105,QuorumUpgradeable._getRegistryStorage
-FNDA:269,QuorumUpgradeable._getRegistryStorage
+FNDA:14113,QuorumUpgradeable._getRegistryStorage
DA:107,0
FNF:9
FNH:8
LF:27
LH:25
BRF:5
-BRH:4
+BRH:5
end_of_record
TN:
SF:contracts/custody/CustodianFactory.sol
@@ -775,184 +870,159 @@ DA:50,0
FN:50,CustodianFactory.getCreator
FNDA:0,CustodianFactory.getCreator
DA:51,0
-DA:58,34
+DA:58,0
FN:58,CustodianFactory.isRegistered
-FNDA:34,CustodianFactory.isRegistered
-DA:59,34
-DA:69,48
+FNDA:0,CustodianFactory.isRegistered
+DA:59,0
+DA:69,0
FN:69,CustodianFactory.create
-FNDA:48,CustodianFactory.create
-DA:70,48
-DA:71,48
-DA:72,48
-DA:74,48
+FNDA:0,CustodianFactory.create
+DA:70,0
+DA:71,0
+DA:72,0
+DA:74,0
DA:75,0
-DA:81,48
+DA:81,0
FN:81,CustodianFactory._registerEndpoint
-FNDA:48,CustodianFactory._registerEndpoint
-DA:82,48
-DA:83,48
+FNDA:0,CustodianFactory._registerEndpoint
+DA:82,0
+DA:83,0
BRDA:83,0,0,-
-DA:84,48
+DA:84,0
DA:85,0
-DA:91,48
+DA:91,0
FN:91,CustodianFactory._deployCustodian
-FNDA:48,CustodianFactory._deployCustodian
-DA:92,48
-DA:93,48
-DA:99,48
+FNDA:0,CustodianFactory._deployCustodian
+DA:92,0
+DA:93,0
+DA:99,0
FN:99,CustodianFactory._registerManager
-FNDA:48,CustodianFactory._registerManager
-DA:100,48
+FNDA:0,CustodianFactory._registerManager
+DA:100,0
FNF:6
-FNH:5
+FNH:0
LF:20
-LH:16
+LH:0
BRF:1
BRH:0
end_of_record
TN:
SF:contracts/custody/CustodianImpl.sol
-DA:49,48
+DA:49,0
FN:49,CustodianImpl.initialize
-FNDA:48,CustodianImpl.initialize
-DA:50,48
+FNDA:0,CustodianImpl.initialize
+DA:50,0
BRDA:50,0,0,-
-DA:51,0
-DA:52,48
-DA:53,48
-DA:58,1
-FN:58,CustodianImpl.supportsInterface
-FNDA:1,CustodianImpl.supportsInterface
-DA:59,1
-DA:63,1
-FN:63,CustodianImpl.getManager
-FNDA:1,CustodianImpl.getManager
-DA:64,1
-DA:70,2
-FN:70,CustodianImpl.getEndpoint
-FNDA:2,CustodianImpl.getEndpoint
-DA:71,2
-DA:77,1
+DA:52,0
+DA:53,0
+DA:54,0
+DA:59,0
+FN:59,CustodianImpl.supportsInterface
+FNDA:0,CustodianImpl.supportsInterface
+DA:60,0
+DA:64,0
+FN:64,CustodianImpl.getManager
+FNDA:0,CustodianImpl.getManager
+DA:65,0
+DA:70,0
+FN:70,CustodianImpl.getEndpoint
+FNDA:0,CustodianImpl.getEndpoint
+DA:71,0
+DA:77,0
FN:77,CustodianImpl.setEndpoint
-FNDA:1,CustodianImpl.setEndpoint
-DA:78,1
+FNDA:0,CustodianImpl.setEndpoint
+DA:78,0
BRDA:78,1,0,-
-DA:79,1
-DA:80,1
-DA:81,1
-DA:90,3
+DA:79,0
+DA:80,0
+DA:81,0
+DA:90,0
FN:90,CustodianImpl.withdraw
-FNDA:3,CustodianImpl.withdraw
-DA:91,3
-BRDA:91,2,0,1
-DA:92,2
-DA:93,2
+FNDA:0,CustodianImpl.withdraw
+DA:91,0
+BRDA:91,2,0,-
+DA:92,0
+DA:93,0
DA:94,0
-DA:102,12
+DA:102,0
FN:102,CustodianImpl.getBalance
-FNDA:12,CustodianImpl.getBalance
-DA:103,12
+FNDA:0,CustodianImpl.getBalance
+DA:103,0
FNF:7
-FNH:7
+FNH:0
LF:23
-LH:21
+LH:0
BRF:3
-BRH:1
+BRH:0
end_of_record
TN:
SF:contracts/custody/CustodianReferendum.sol
-DA:71,34
-FN:71,CustodianReferendum.onlyValidCustodian
-FNDA:34,CustodianReferendum.onlyValidCustodian
-DA:73,34
-BRDA:73,0,0,1
-DA:74,1
-DA:80,41
-FN:80,CustodianReferendum.constructor
-FNDA:41,CustodianReferendum.constructor
-DA:83,0
-DA:84,41
-DA:85,41
-DA:89,41
-FN:89,CustodianReferendum.initialize
-FNDA:41,CustodianReferendum.initialize
-DA:90,0
-DA:91,0
-DA:92,41
-DA:94,41
-DA:100,35
-FN:100,CustodianReferendum.isFeeSchemeSupported
-FNDA:35,CustodianReferendum.isFeeSchemeSupported
-DA:102,35
-DA:106,3
-FN:106,CustodianReferendum.getExpirationPeriod
-FNDA:3,CustodianReferendum.getExpirationPeriod
-DA:107,3
-DA:112,1
-FN:112,CustodianReferendum.getEnrollmentDeadline
-FNDA:1,CustodianReferendum.getEnrollmentDeadline
-DA:113,1
-DA:117,2
-FN:117,CustodianReferendum.getEnrollmentCount
-FNDA:2,CustodianReferendum.getEnrollmentCount
-DA:118,2
-DA:124,27
-FN:124,CustodianReferendum.isActive
-FNDA:27,CustodianReferendum.isActive
-DA:139,27
-DA:140,27
-DA:146,1
-FN:146,CustodianReferendum.isWaiting
-FNDA:1,CustodianReferendum.isWaiting
-DA:147,1
-DA:153,1
-FN:153,CustodianReferendum.isBlocked
-FNDA:1,CustodianReferendum.isBlocked
-DA:154,1
-DA:160,33
-FN:160,CustodianReferendum.register
-FNDA:33,CustodianReferendum.register
-DA:173,33
-DA:174,32
-BRDA:174,1,0,-
-DA:175,0
-DA:179,32
-DA:182,32
-DA:183,32
-DA:188,29
-FN:188,CustodianReferendum.approve
-FNDA:29,CustodianReferendum.approve
-DA:189,29
-DA:190,29
-DA:191,29
-DA:196,4
-FN:196,CustodianReferendum.revoke
-FNDA:4,CustodianReferendum.revoke
-DA:197,4
-DA:198,4
-DA:199,4
-DA:204,2
-FN:204,CustodianReferendum.setExpirationPeriod
-FNDA:2,CustodianReferendum.setExpirationPeriod
-DA:205,2
-DA:206,2
-DA:212,0
-FN:212,CustodianReferendum._authorizeUpgrade
+DA:50,0
+FN:50,CustodianReferendum.onlyValidCustodian
+FNDA:0,CustodianReferendum.onlyValidCustodian
+DA:52,0
+BRDA:52,0,0,-
+DA:53,0
+DA:59,11
+FN:59,CustodianReferendum.constructor
+FNDA:11,CustodianReferendum.constructor
+DA:62,0
+DA:63,11
+DA:67,11
+FN:67,CustodianReferendum.initialize
+FNDA:11,CustodianReferendum.initialize
+DA:68,0
+DA:69,0
+DA:70,11
+DA:76,0
+FN:76,CustodianReferendum.isActive
+FNDA:0,CustodianReferendum.isActive
+DA:82,0
+DA:88,0
+FN:88,CustodianReferendum.isWaiting
+FNDA:0,CustodianReferendum.isWaiting
+DA:89,0
+DA:95,0
+FN:95,CustodianReferendum.isBlocked
+FNDA:0,CustodianReferendum.isBlocked
+DA:96,0
+DA:101,0
+FN:101,CustodianReferendum.register
+FNDA:0,CustodianReferendum.register
+DA:103,0
+DA:105,0
+DA:110,0
+FN:110,CustodianReferendum.approve
+FNDA:0,CustodianReferendum.approve
+DA:111,0
+DA:112,0
+DA:113,0
+DA:118,0
+FN:118,CustodianReferendum.revoke
+FNDA:0,CustodianReferendum.revoke
+DA:119,0
+DA:120,0
+DA:121,0
+DA:125,0
+FN:125,CustodianReferendum.getEnrollmentCount
+FNDA:0,CustodianReferendum.getEnrollmentCount
+DA:126,0
+DA:132,0
+FN:132,CustodianReferendum._authorizeUpgrade
FNDA:0,CustodianReferendum._authorizeUpgrade
-FNF:15
-FNH:14
-LF:46
-LH:41
-BRF:2
-BRH:1
+FNF:11
+FNH:2
+LF:30
+LH:4
+BRF:1
+BRH:0
end_of_record
TN:
SF:contracts/economics/MMC.sol
-DA:19,63
+DA:19,39
FN:19,MMC.constructor
-FNDA:63,MMC.constructor
-DA:23,63
+FNDA:39,MMC.constructor
+DA:23,39
DA:26,0
FN:26,MMC.burn
FNDA:0,MMC.burn
@@ -961,10 +1031,10 @@ DA:31,0
FN:31,MMC.nonces
FNDA:0,MMC.nonces
DA:32,0
-DA:36,109
+DA:36,67
FN:36,MMC._update
-FNDA:109,MMC._update
-DA:37,109
+FNDA:67,MMC._update
+DA:37,67
FNF:4
FNH:2
LF:8
@@ -974,53 +1044,53 @@ BRH:0
end_of_record
TN:
SF:contracts/economics/Tollgate.sol
-DA:59,43
+DA:59,44
FN:59,Tollgate.onlyValidFeeRepresentation
-FNDA:43,Tollgate.onlyValidFeeRepresentation
-DA:60,43
+FNDA:44,Tollgate.onlyValidFeeRepresentation
+DA:60,44
BRDA:60,0,0,1
-DA:61,42
+DA:61,43
BRDA:61,1,0,1
-DA:72,44
+DA:72,46
FN:72,Tollgate.onlySupportedScheme
-FNDA:44,Tollgate.onlySupportedScheme
-DA:74,44
-BRDA:74,2,0,-
-DA:75,0
-DA:78,44
-DA:79,44
-DA:82,36
-BRDA:82,3,0,36
-DA:83,36
-DA:84,36
+FNDA:46,Tollgate.onlySupportedScheme
+DA:74,46
+BRDA:74,2,0,1
+DA:75,1
+DA:78,45
+DA:79,45
+DA:82,1
+BRDA:82,3,0,1
+DA:83,1
+DA:84,1
BRDA:84,4,0,1
-DA:92,41
+DA:92,39
FN:92,Tollgate.constructor
-FNDA:41,Tollgate.constructor
+FNDA:39,Tollgate.constructor
DA:93,0
-DA:98,41
+DA:98,39
FN:98,Tollgate.initialize
-FNDA:41,Tollgate.initialize
+FNDA:39,Tollgate.initialize
DA:99,0
-DA:100,41
-DA:107,64
+DA:100,39
+DA:107,1025
FN:107,Tollgate.isSupportedCurrency
-FNDA:64,Tollgate.isSupportedCurrency
-DA:108,64
+FNDA:1025,Tollgate.isSupportedCurrency
+DA:108,1025
DA:114,1
FN:114,Tollgate.supportedCurrencies
FNDA:1,Tollgate.supportedCurrencies
DA:115,1
-DA:122,38
+DA:122,519
FN:122,Tollgate.getFees
-FNDA:38,Tollgate.getFees
-DA:124,38
+FNDA:519,Tollgate.getFees
+DA:124,519
BRDA:124,5,0,1
DA:125,1
-DA:128,37
-DA:129,37
-DA:130,37
-DA:131,37
+DA:128,518
+DA:129,518
+DA:130,518
+DA:131,518
DA:139,41
FN:139,Tollgate.setFees
FNDA:41,Tollgate.setFees
@@ -1034,138 +1104,176 @@ DA:158,41
DA:163,0
FN:163,Tollgate._authorizeUpgrade
FNDA:0,Tollgate._authorizeUpgrade
-DA:169,102
+DA:169,1544
FN:169,Tollgate._isSchemeSupported
-FNDA:102,Tollgate._isSchemeSupported
-DA:170,102
-DA:171,102
-DA:172,102
-DA:180,180
+FNDA:1544,Tollgate._isSchemeSupported
+DA:170,1544
+DA:171,1544
+DA:172,1544
+DA:180,2103
FN:180,Tollgate._computeComposedKey
-FNDA:180,Tollgate._computeComposedKey
-DA:181,180
+FNDA:2103,Tollgate._computeComposedKey
+DA:181,2103
FNF:11
FNH:10
LF:41
-LH:37
+LH:38
BRF:7
-BRH:5
+BRH:6
end_of_record
TN:
SF:contracts/economics/Treasury.sol
-DA:35,41
-FN:35,Treasury.constructor
-FNDA:41,Treasury.constructor
-DA:38,0
-DA:41,41
-FN:41,Treasury.initialize
-FNDA:41,Treasury.initialize
-DA:42,0
-DA:43,0
-DA:44,41
-DA:59,0
-FN:59,Treasury.deposit
+DA:37,26
+FN:37,Treasury.constructor
+FNDA:26,Treasury.constructor
+DA:40,0
+DA:43,26
+FN:43,Treasury.initialize
+FNDA:26,Treasury.initialize
+DA:44,0
+DA:45,0
+DA:46,0
+DA:47,26
+DA:65,0
+FN:65,Treasury.deposit
FNDA:0,Treasury.deposit
-DA:66,0
-DA:75,0
-FN:75,Treasury.collectFees
-FNDA:0,Treasury.collectFees
+DA:70,0
+DA:80,0
+FN:80,Treasury.withdraw
+FNDA:0,Treasury.withdraw
DA:85,0
-FN:85,Treasury._authorizeUpgrade
+DA:95,0
+FN:95,Treasury.transfer
+FNDA:0,Treasury.transfer
+DA:100,0
+DA:110,0
+FN:110,Treasury.collectFees
+FNDA:0,Treasury.collectFees
+DA:115,0
+DA:116,0
+DA:117,0
+DA:118,0
+DA:124,0
+FN:124,Treasury._authorizeUpgrade
FNDA:0,Treasury._authorizeUpgrade
-FNF:5
+FNF:7
FNH:2
-LF:10
+LF:19
LH:3
BRF:0
BRH:0
end_of_record
TN:
SF:contracts/financial/AgreementManager.sol
-DA:67,32
-FN:67,AgreementManager.onlySupportedCurrency
-FNDA:32,AgreementManager.onlySupportedCurrency
-DA:68,32
-DA:69,32
-BRDA:69,0,0,-
-DA:74,41
-FN:74,AgreementManager.constructor
-FNDA:41,AgreementManager.constructor
-DA:77,0
-DA:79,41
-DA:80,41
-DA:84,41
-FN:84,AgreementManager.initialize
-FNDA:41,AgreementManager.initialize
-DA:85,0
-DA:86,41
-DA:95,32
-FN:95,AgreementManager.createAgreement
-FNDA:32,AgreementManager.createAgreement
-DA:103,32
-DA:104,32
-DA:107,32
-DA:108,32
-DA:109,0
-DA:114,33
-FN:114,AgreementManager.getAgreement
-FNDA:33,AgreementManager.getAgreement
-DA:115,33
-DA:124,32
-FN:124,AgreementManager.previewAgreement
-FNDA:32,AgreementManager.previewAgreement
-DA:131,32
-BRDA:131,1,0,-
-DA:132,0
-DA:151,32
-DA:154,32
-DA:169,0
-FN:169,AgreementManager._authorizeUpgrade
+DA:70,513
+FN:70,AgreementManager.onlySupportedCurrency
+FNDA:513,AgreementManager.onlySupportedCurrency
+DA:71,513
+DA:72,513
+BRDA:72,0,0,-
+DA:77,39
+FN:77,AgreementManager.constructor
+FNDA:39,AgreementManager.constructor
+DA:80,0
+DA:82,39
+DA:83,39
+DA:87,39
+FN:87,AgreementManager.initialize
+FNDA:39,AgreementManager.initialize
+DA:88,0
+DA:89,39
+DA:90,39
+DA:95,2
+FN:95,AgreementManager.setMaxParties
+FNDA:2,AgreementManager.setMaxParties
+DA:96,2
+BRDA:96,1,0,1
+DA:97,1
+DA:101,6
+FN:101,AgreementManager.maxParties
+FNDA:6,AgreementManager.maxParties
+DA:102,6
+DA:111,511
+FN:111,AgreementManager.createAgreement
+FNDA:511,AgreementManager.createAgreement
+DA:119,511
+DA:120,511
+DA:123,508
+DA:124,508
+DA:125,0
+DA:130,1011
+FN:130,AgreementManager.getAgreement
+FNDA:1011,AgreementManager.getAgreement
+DA:131,1011
+DA:140,513
+FN:140,AgreementManager.previewAgreement
+FNDA:513,AgreementManager.previewAgreement
+DA:159,513
+DA:163,512
+DA:164,511
+DA:168,511
+DA:184,0
+FN:184,AgreementManager._authorizeUpgrade
FNDA:0,AgreementManager._authorizeUpgrade
-DA:172,32
-FN:172,AgreementManager._createAndStoreProof
-FNDA:32,AgreementManager._createAndStoreProof
-DA:174,32
-DA:175,32
-DA:176,32
-DA:177,0
-DA:186,32
-FN:186,AgreementManager._calcFees
-FNDA:32,AgreementManager._calcFees
-DA:188,32
-DA:189,32
-DA:190,32
-DA:191,32
-BRDA:191,4,0,-
-DA:192,32
-FNF:9
-FNH:8
-LF:35
-LH:29
-BRF:3
-BRH:0
+DA:187,508
+FN:187,AgreementManager._createAndStoreProof
+FNDA:508,AgreementManager._createAndStoreProof
+DA:189,508
+DA:190,508
+DA:191,508
+DA:192,0
+DA:196,512
+FN:196,AgreementManager._calculatePenalization
+FNDA:512,AgreementManager._calculatePenalization
+DA:197,512
+DA:198,4
+DA:199,4
+DA:200,4
+DA:208,4
+FN:208,AgreementManager._penaltyBps
+FNDA:4,AgreementManager._penaltyBps
+DA:209,4
+DA:213,4
+DA:217,4
+BRDA:217,4,0,1
+DA:218,1
+DA:228,513
+FN:228,AgreementManager._calcFees
+FNDA:513,AgreementManager._calcFees
+DA:230,513
+DA:231,513
+DA:232,2
+DA:233,1
+BRDA:233,7,0,1
+DA:234,0
+FNF:13
+FNH:12
+LF:51
+LH:45
+BRF:4
+BRH:3
end_of_record
TN:
SF:contracts/financial/AgreementSettler.sol
-DA:85,0
+DA:85,4
FN:85,AgreementSettler.onlyValidAgreement
-FNDA:0,AgreementSettler.onlyValidAgreement
-DA:86,0
-BRDA:86,0,0,-
-DA:87,0
-DA:93,41
+FNDA:4,AgreementSettler.onlyValidAgreement
+DA:86,4
+BRDA:86,0,0,2
+DA:87,2
+DA:93,26
FN:93,AgreementSettler.constructor
-FNDA:41,AgreementSettler.constructor
+FNDA:26,AgreementSettler.constructor
DA:96,0
-DA:97,41
-DA:98,41
-DA:99,41
-DA:103,41
+DA:97,26
+DA:98,26
+DA:99,26
+DA:103,26
FN:103,AgreementSettler.initialize
-FNDA:41,AgreementSettler.initialize
+FNDA:26,AgreementSettler.initialize
DA:104,0
-DA:105,41
-DA:106,41
+DA:105,26
+DA:106,26
DA:114,0
FN:114,AgreementSettler.disburse
FNDA:0,AgreementSettler.disburse
@@ -1174,149 +1282,117 @@ DA:117,0
DA:119,0
DA:120,0
DA:121,0
-DA:139,0
-FN:139,AgreementSettler.quitAgreement
-FNDA:0,AgreementSettler.quitAgreement
-DA:140,0
-DA:141,0
-BRDA:141,2,0,-
-DA:155,0
-DA:156,0
-DA:157,0
-DA:158,0
-DA:160,0
-DA:162,0
-DA:164,0
-BRDA:164,3,0,-
-DA:166,0
-DA:167,0
-DA:173,33
-FN:173,AgreementSettler.settleAgreement
-FNDA:33,AgreementSettler.settleAgreement
-DA:178,33
-DA:179,33
-BRDA:179,4,0,1
-DA:181,32
-DA:182,32
-DA:183,32
-DA:184,32
-DA:185,32
-DA:190,32
-DA:193,32
-DA:195,32
-BRDA:195,5,0,-
-DA:196,32
-DA:197,0
-DA:203,0
-FN:203,AgreementSettler._authorizeUpgrade
+DA:138,2
+FN:138,AgreementSettler.quitAgreement
+FNDA:2,AgreementSettler.quitAgreement
+DA:139,2
+DA:140,2
+BRDA:140,2,0,1
+DA:154,1
+DA:155,1
+DA:156,1
+DA:164,1
+DA:165,1
+DA:166,1
+DA:168,1
+DA:170,1
+DA:172,1
+BRDA:172,3,0,1
+DA:174,1
+DA:175,0
+DA:201,501
+FN:201,AgreementSettler.settleAgreement
+FNDA:501,AgreementSettler.settleAgreement
+DA:206,501
+DA:207,501
+BRDA:207,4,0,2
+DA:209,499
+DA:210,499
+DA:211,499
+DA:212,499
+DA:213,499
+DA:215,499
+DA:216,499
+DA:217,499
+DA:222,499
+DA:226,499
+DA:228,499
+BRDA:228,5,0,499
+DA:229,499
+DA:230,0
+DA:236,0
+FN:236,AgreementSettler._authorizeUpgrade
FNDA:0,AgreementSettler._authorizeUpgrade
-DA:207,32
-FN:207,AgreementSettler._setProofAsSettled
-FNDA:32,AgreementSettler._setProofAsSettled
-DA:208,32
+DA:240,500
+FN:240,AgreementSettler._setProofAsSettled
+FNDA:500,AgreementSettler._setProofAsSettled
+DA:241,500
FNF:8
-FNH:4
-LF:46
-LH:21
+FNH:6
+LF:51
+LH:40
BRF:5
-BRH:1
+BRH:5
end_of_record
TN:
SF:contracts/financial/LedgerVault.sol
-DA:65,41
-FN:65,LedgerVault.constructor
-FNDA:41,LedgerVault.constructor
-DA:68,0
-DA:71,41
-FN:71,LedgerVault.initialize
-FNDA:41,LedgerVault.initialize
-DA:72,0
-DA:73,0
-DA:74,0
-DA:75,41
-DA:85,32
-FN:85,LedgerVault.lock
-FNDA:32,LedgerVault.lock
-DA:90,32
-BRDA:90,0,0,-
-DA:91,32
-DA:92,32
-DA:93,32
-DA:94,0
+DA:31,55
+FN:31,LedgerVault.constructor
+FNDA:55,LedgerVault.constructor
+DA:34,0
+DA:37,55
+FN:37,LedgerVault.initialize
+FNDA:55,LedgerVault.initialize
+DA:38,0
+DA:39,0
+DA:40,0
+DA:41,0
+DA:42,0
+DA:43,0
+DA:44,55
+DA:54,516
+FN:54,LedgerVault.lock
+FNDA:516,LedgerVault.lock
+DA:59,516
+DA:66,4
+FN:66,LedgerVault.release
+FNDA:4,LedgerVault.release
+DA:71,4
+DA:80,503
+FN:80,LedgerVault.claim
+FNDA:503,LedgerVault.claim
+DA:85,503
+DA:92,0
+FN:92,LedgerVault.approve
+FNDA:0,LedgerVault.approve
+DA:93,0
+DA:100,0
+FN:100,LedgerVault.revoke
+FNDA:0,LedgerVault.revoke
DA:101,0
-FN:101,LedgerVault.release
-FNDA:0,LedgerVault.release
-DA:106,0
-BRDA:106,1,0,-
-DA:107,0
DA:108,0
+FN:108,LedgerVault.collect
+FNDA:0,LedgerVault.collect
DA:109,0
-DA:110,0
-DA:119,32
-FN:119,LedgerVault.claim
-FNDA:32,LedgerVault.claim
-DA:124,32
-BRDA:124,2,0,-
-DA:125,32
-DA:126,32
-DA:127,32
-DA:128,0
-DA:136,32
-FN:136,LedgerVault._subLockedAmount
-FNDA:32,LedgerVault._subLockedAmount
-DA:137,32
-DA:145,32
-FN:145,LedgerVault._sumLockedAmount
-FNDA:32,LedgerVault._sumLockedAmount
-DA:146,32
-DA:154,32
-FN:154,LedgerVault._getLockedAmount
-FNDA:32,LedgerVault._getLockedAmount
-DA:155,32
-DA:161,0
-FN:161,LedgerVault._authorizeUpgrade
+DA:116,43
+FN:116,LedgerVault.deposit
+FNDA:43,LedgerVault.deposit
+DA:121,43
+DA:128,4
+FN:128,LedgerVault.withdraw
+FNDA:4,LedgerVault.withdraw
+DA:133,4
+DA:140,502
+FN:140,LedgerVault.transfer
+FNDA:502,LedgerVault.transfer
+DA:141,502
+DA:147,0
+FN:147,LedgerVault._authorizeUpgrade
FNDA:0,LedgerVault._authorizeUpgrade
-FNF:9
-FNH:7
-LF:32
-LH:19
-BRF:3
-BRH:0
-end_of_record
-TN:
-SF:contracts/governance/Governance.sol
-DA:56,0
-FN:56,Governance.state
-FNDA:0,Governance.state
-DA:57,0
-DA:62,0
-FN:62,Governance.proposalNeedsQueuing
-FNDA:0,Governance.proposalNeedsQueuing
-DA:65,0
-DA:77,0
-FN:77,Governance._queueOperations
-FNDA:0,Governance._queueOperations
-DA:84,0
-DA:95,0
-FN:95,Governance._executeOperations
-FNDA:0,Governance._executeOperations
-DA:102,0
-DA:113,0
-FN:113,Governance._cancel
-FNDA:0,Governance._cancel
-DA:119,0
-DA:126,0
-FN:126,Governance._executor
-FNDA:0,Governance._executor
-DA:127,0
-DA:134,0
-FN:134,Governance.proposalThreshold
-FNDA:0,Governance.proposalThreshold
-DA:135,0
-FNF:7
-FNH:0
-LF:14
-LH:0
+FNF:12
+FNH:8
+LF:29
+LH:15
BRF:0
BRH:0
end_of_record
@@ -1356,34 +1432,34 @@ FNDA:0,HookRegistry.initialize
DA:68,0
DA:69,0
DA:70,0
-DA:78,0
-FN:78,HookRegistry.submit
+DA:77,0
+FN:77,HookRegistry.submit
FNDA:0,HookRegistry.submit
+DA:78,0
DA:79,0
DA:80,0
-DA:81,0
-DA:87,0
-FN:87,HookRegistry.approve
+DA:86,0
+FN:86,HookRegistry.approve
FNDA:0,HookRegistry.approve
+DA:87,0
DA:88,0
-DA:89,0
-DA:95,0
-FN:95,HookRegistry.reject
+DA:94,0
+FN:94,HookRegistry.reject
FNDA:0,HookRegistry.reject
+DA:95,0
DA:96,0
-DA:97,0
-DA:103,0
-FN:103,HookRegistry.lookup
+DA:102,0
+FN:102,HookRegistry.lookup
FNDA:0,HookRegistry.lookup
-DA:104,0
-DA:110,0
-FN:110,HookRegistry.isActive
+DA:103,0
+DA:109,0
+FN:109,HookRegistry.isActive
FNDA:0,HookRegistry.isActive
+DA:110,0
DA:111,0
DA:112,0
-DA:113,0
-DA:119,0
-FN:119,HookRegistry._authorizeUpgrade
+DA:118,0
+FN:118,HookRegistry._authorizeUpgrade
FNDA:0,HookRegistry._authorizeUpgrade
FNF:9
FNH:0
@@ -1394,449 +1470,471 @@ BRH:0
end_of_record
TN:
SF:contracts/policies/PolicyAudit.sol
-DA:44,0
+DA:44,1089
FN:44,PolicyAudit.onlyValidPolicy
-FNDA:0,PolicyAudit.onlyValidPolicy
-DA:45,0
-BRDA:45,0,0,-
-DA:46,0
-DA:53,0
+FNDA:1089,PolicyAudit.onlyValidPolicy
+DA:45,1089
+BRDA:45,0,0,1
+DA:46,1
+DA:53,28
FN:53,PolicyAudit.constructor
-FNDA:0,PolicyAudit.constructor
+FNDA:28,PolicyAudit.constructor
DA:54,0
-DA:59,0
+DA:59,28
FN:59,PolicyAudit.initialize
-FNDA:0,PolicyAudit.initialize
+FNDA:28,PolicyAudit.initialize
DA:60,0
DA:61,0
-DA:62,0
-DA:70,0
+DA:62,28
+DA:70,1088
FN:70,PolicyAudit.submit
-FNDA:0,PolicyAudit.submit
-DA:71,0
-DA:72,0
-DA:78,0
+FNDA:1088,PolicyAudit.submit
+DA:71,1088
+DA:72,1088
+DA:78,1084
FN:78,PolicyAudit.approve
-FNDA:0,PolicyAudit.approve
-DA:79,0
-DA:80,0
-DA:86,0
+FNDA:1084,PolicyAudit.approve
+DA:79,1084
+DA:80,1084
+DA:86,241
FN:86,PolicyAudit.reject
-FNDA:0,PolicyAudit.reject
-DA:87,0
-DA:88,0
-DA:93,0
-FN:93,PolicyAudit.isAudited
-FNDA:0,PolicyAudit.isAudited
-DA:94,0
-DA:100,0
-FN:100,PolicyAudit._authorizeUpgrade
+FNDA:241,PolicyAudit.reject
+DA:87,241
+DA:88,241
+DA:93,1445
+FN:93,PolicyAudit.isApproved
+FNDA:1445,PolicyAudit.isApproved
+DA:94,1445
+DA:99,5
+FN:99,PolicyAudit.isRejected
+FNDA:5,PolicyAudit.isRejected
+DA:100,5
+DA:105,4
+FN:105,PolicyAudit.isPending
+FNDA:4,PolicyAudit.isPending
+DA:106,4
+DA:112,0
+FN:112,PolicyAudit._authorizeUpgrade
FNDA:0,PolicyAudit._authorizeUpgrade
-FNF:8
-FNH:0
-LF:21
-LH:0
+FNF:10
+FNH:9
+LF:25
+LH:21
BRF:1
-BRH:0
+BRH:1
end_of_record
TN:
SF:contracts/policies/PolicyBase.sol
-DA:72,0
+DA:72,2
FN:72,PolicyBase.onlyPolicyManager
-FNDA:0,PolicyBase.onlyPolicyManager
-DA:73,0
-BRDA:73,0,0,-
-DA:74,0
-DA:80,0
-FN:80,PolicyBase.onlyPolicyAuthorizer
-FNDA:0,PolicyBase.onlyPolicyAuthorizer
-DA:81,0
-BRDA:81,1,0,-
-DA:82,0
-DA:87,0
+FNDA:2,PolicyBase.onlyPolicyManager
+DA:73,2
+BRDA:73,0,0,1
+DA:74,1
+DA:80,2
+FN:80,PolicyBase.onlyPolicyAuthorizer
+FNDA:2,PolicyBase.onlyPolicyAuthorizer
+DA:81,2
+BRDA:81,1,0,1
+DA:82,1
+DA:87,1051
FN:87,PolicyBase.constructor
-FNDA:0,PolicyBase.constructor
-DA:93,0
-DA:94,0
-DA:95,0
-DA:96,0
-DA:101,0
-FN:101,PolicyBase.getAttestationProvider
-FNDA:0,PolicyBase.getAttestationProvider
-DA:102,0
-DA:108,0
-FN:108,PolicyBase.supportsInterface
-FNDA:0,PolicyBase.supportsInterface
-DA:109,0
-DA:116,0
-FN:116,PolicyBase.getLicense
-FNDA:0,PolicyBase.getLicense
-DA:118,0
-DA:119,0
-DA:124,0
-FN:124,PolicyBase._getHolder
-FNDA:0,PolicyBase._getHolder
-DA:125,0
-DA:132,0
-FN:132,PolicyBase._commit
-FNDA:0,PolicyBase._commit
-DA:137,0
-DA:138,0
-DA:139,0
-DA:140,0
-DA:148,0
-FN:148,PolicyBase._setAttestation
-FNDA:0,PolicyBase._setAttestation
-DA:150,0
-DA:151,0
-DA:152,0
-DA:160,0
-FN:160,PolicyBase._computeComposedKey
-FNDA:0,PolicyBase._computeComposedKey
-DA:167,0
+FNDA:1051,PolicyBase.constructor
+DA:88,1051
+DA:89,1051
+DA:90,1051
+DA:91,1051
+DA:96,1
+FN:96,PolicyBase.getAttestationProvider
+FNDA:1,PolicyBase.getAttestationProvider
+DA:97,1
+DA:103,3236
+FN:103,PolicyBase.supportsInterface
+FNDA:3236,PolicyBase.supportsInterface
+DA:104,3236
+DA:111,1
+FN:111,PolicyBase.getLicense
+FNDA:1,PolicyBase.getLicense
+DA:113,1
+DA:114,1
+DA:119,1
+FN:119,PolicyBase._getHolder
+FNDA:1,PolicyBase._getHolder
+DA:120,1
+DA:127,495
+FN:127,PolicyBase._commit
+FNDA:495,PolicyBase._commit
+DA:132,495
+DA:133,495
+DA:134,495
+DA:135,495
+DA:137,495
+DA:138,495
+DA:141,495
+DA:142,495
+DA:144,495
+DA:145,0
+DA:153,496
+FN:153,PolicyBase._setAttestation
+FNDA:496,PolicyBase._setAttestation
+DA:155,496
+DA:156,496
+DA:157,496
+DA:165,497
+FN:165,PolicyBase._computeComposedKey
+FNDA:497,PolicyBase._computeComposedKey
+DA:172,497
FNF:10
-FNH:0
-LF:31
-LH:0
+FNH:10
+LF:37
+LH:36
BRF:2
-BRH:0
+BRH:2
end_of_record
TN:
SF:contracts/rights/RightsAssetCustodian.sol
-DA:74,18
+DA:74,0
FN:74,RightsAssetCustodian.onlyActiveCustodian
-FNDA:18,RightsAssetCustodian.onlyActiveCustodian
-DA:75,18
+FNDA:0,RightsAssetCustodian.onlyActiveCustodian
+DA:75,0
BRDA:75,0,0,-
DA:76,0
-DA:82,19
+DA:82,0
FN:82,RightsAssetCustodian.onlyAvailableRedundancy
-FNDA:19,RightsAssetCustodian.onlyAvailableRedundancy
-DA:84,19
-DA:85,19
-BRDA:85,1,0,1
-DA:86,1
-DA:92,17
+FNDA:0,RightsAssetCustodian.onlyAvailableRedundancy
+DA:84,0
+DA:85,0
+BRDA:85,1,0,-
+DA:86,0
+DA:92,0
FN:92,RightsAssetCustodian.constructor
-FNDA:17,RightsAssetCustodian.constructor
+FNDA:0,RightsAssetCustodian.constructor
DA:95,0
-DA:97,17
-DA:101,17
+DA:97,0
+DA:101,0
FN:101,RightsAssetCustodian.initialize
-FNDA:17,RightsAssetCustodian.initialize
+FNDA:0,RightsAssetCustodian.initialize
DA:102,0
-DA:103,17
-DA:108,17
-DA:115,1
+DA:103,0
+DA:108,0
+DA:115,0
FN:115,RightsAssetCustodian.setMaxAllowedRedundancy
-FNDA:1,RightsAssetCustodian.setMaxAllowedRedundancy
-DA:116,1
-DA:120,1
+FNDA:0,RightsAssetCustodian.setMaxAllowedRedundancy
+DA:116,0
+DA:120,0
FN:120,RightsAssetCustodian.getMaxAllowedRedundancy
-FNDA:1,RightsAssetCustodian.getMaxAllowedRedundancy
-DA:121,1
-DA:126,4
+FNDA:0,RightsAssetCustodian.getMaxAllowedRedundancy
+DA:121,0
+DA:126,0
FN:126,RightsAssetCustodian.revokeCustody
-FNDA:4,RightsAssetCustodian.revokeCustody
-DA:128,4
-DA:129,4
-BRDA:129,2,0,1
-DA:130,3
-DA:131,3
-DA:138,18
+FNDA:0,RightsAssetCustodian.revokeCustody
+DA:128,0
+DA:129,0
+BRDA:129,2,0,-
+DA:130,0
+DA:131,0
+DA:138,0
FN:138,RightsAssetCustodian.grantCustody
-FNDA:18,RightsAssetCustodian.grantCustody
-DA:144,18
-DA:145,18
-BRDA:145,3,0,1
-DA:148,17
-DA:149,17
-DA:150,17
-DA:156,4
+FNDA:0,RightsAssetCustodian.grantCustody
+DA:144,0
+DA:145,0
+BRDA:145,3,0,-
+DA:148,0
+DA:149,0
+DA:150,0
+DA:156,0
FN:156,RightsAssetCustodian.setPriority
-FNDA:4,RightsAssetCustodian.setPriority
-DA:157,4
-DA:166,2
+FNDA:0,RightsAssetCustodian.setPriority
+DA:157,0
+DA:166,0
FN:166,RightsAssetCustodian.getPriority
-FNDA:2,RightsAssetCustodian.getPriority
-DA:167,2
-DA:175,6
+FNDA:0,RightsAssetCustodian.getPriority
+DA:167,0
+DA:175,0
FN:175,RightsAssetCustodian.getDemand
-FNDA:6,RightsAssetCustodian.getDemand
-DA:176,6
-DA:185,4
+FNDA:0,RightsAssetCustodian.getDemand
+DA:176,0
+DA:185,0
FN:185,RightsAssetCustodian.getWeight
-FNDA:4,RightsAssetCustodian.getWeight
-DA:186,4
-DA:192,7
+FNDA:0,RightsAssetCustodian.getWeight
+DA:186,0
+DA:192,0
FN:192,RightsAssetCustodian.isCustodian
-FNDA:7,RightsAssetCustodian.isCustodian
-DA:193,7
-DA:202,1
+FNDA:0,RightsAssetCustodian.isCustodian
+DA:193,0
+DA:202,0
FN:202,RightsAssetCustodian.getCustodian
-FNDA:1,RightsAssetCustodian.getCustodian
-DA:203,1
-DA:204,1
-DA:209,1
-DA:210,1
+FNDA:0,RightsAssetCustodian.getCustodian
+DA:203,0
+DA:204,0
+DA:209,0
+DA:210,0
DA:211,0
-DA:213,1
-DA:214,1
-DA:222,2
-DA:229,2
-DA:230,2
-BRDA:230,5,0,1
-DA:231,1
-DA:232,1
-DA:237,1
+DA:213,0
+DA:214,0
+DA:222,0
+DA:229,0
+DA:230,0
+BRDA:230,5,0,-
+DA:231,0
+DA:232,0
+DA:237,0
DA:245,0
FN:245,RightsAssetCustodian._authorizeUpgrade
FNDA:0,RightsAssetCustodian._authorizeUpgrade
-DA:262,7
+DA:262,0
FN:262,RightsAssetCustodian._calcWeight
-FNDA:7,RightsAssetCustodian._calcWeight
-DA:263,7
-DA:264,7
-DA:265,7
-DA:270,7
-DA:280,1
+FNDA:0,RightsAssetCustodian._calcWeight
+DA:263,0
+DA:264,0
+DA:265,0
+DA:270,0
+DA:280,0
FN:280,RightsAssetCustodian._calcWeights
-FNDA:1,RightsAssetCustodian._calcWeights
-DA:286,1
-DA:287,1
-DA:288,3
-DA:291,3
-DA:292,3
-DA:299,1
+FNDA:0,RightsAssetCustodian._calcWeights
+DA:286,0
+DA:287,0
+DA:288,0
+DA:291,0
+DA:292,0
+DA:299,0
FN:299,RightsAssetCustodian._getCustodians
-FNDA:1,RightsAssetCustodian._getCustodians
-DA:300,1
-DA:301,1
-DA:302,1
-DA:303,1
-DA:305,1
-DA:306,3
-DA:307,3
-DA:310,3
+FNDA:0,RightsAssetCustodian._getCustodians
+DA:300,0
+DA:301,0
+DA:302,0
+DA:303,0
+DA:305,0
+DA:306,0
+DA:307,0
+DA:310,0
DA:323,0
DA:326,0
-DA:332,17
+DA:332,0
FN:332,RightsAssetCustodian._incrementDemand
-FNDA:17,RightsAssetCustodian._incrementDemand
-DA:333,17
-DA:334,17
-DA:340,3
+FNDA:0,RightsAssetCustodian._incrementDemand
+DA:333,0
+DA:334,0
+DA:340,0
FN:340,RightsAssetCustodian._decrementDemand
-FNDA:3,RightsAssetCustodian._decrementDemand
-DA:341,3
-BRDA:341,7,0,3
-DA:342,3
-DA:345,3
-DA:351,13
+FNDA:0,RightsAssetCustodian._decrementDemand
+DA:341,0
+BRDA:341,7,0,-
+DA:342,0
+DA:345,0
+DA:351,0
FN:351,RightsAssetCustodian._getDemand
-FNDA:13,RightsAssetCustodian._getDemand
-DA:352,13
-DA:360,21
+FNDA:0,RightsAssetCustodian._getDemand
+DA:352,0
+DA:360,0
FN:360,RightsAssetCustodian._setPriority
-FNDA:21,RightsAssetCustodian._setPriority
-DA:361,21
-BRDA:361,8,0,1
-DA:362,20
-DA:363,20
-DA:372,9
+FNDA:0,RightsAssetCustodian._setPriority
+DA:361,0
+BRDA:361,8,0,-
+DA:362,0
+DA:363,0
+DA:372,0
FN:372,RightsAssetCustodian._getPriority
-FNDA:9,RightsAssetCustodian._getPriority
-DA:373,9
-DA:384,1
+FNDA:0,RightsAssetCustodian._getPriority
+DA:373,0
+DA:384,0
FN:384,RightsAssetCustodian._calcRandomness
-FNDA:1,RightsAssetCustodian._calcRandomness
-DA:390,1
-DA:391,1
-DA:392,1
-DA:398,26
+FNDA:0,RightsAssetCustodian._calcRandomness
+DA:390,0
+DA:391,0
+DA:392,0
+DA:398,0
FN:398,RightsAssetCustodian._isValidActiveCustodian
-FNDA:26,RightsAssetCustodian._isValidActiveCustodian
-DA:399,26
-DA:406,29
+FNDA:0,RightsAssetCustodian._isValidActiveCustodian
+DA:399,0
+DA:406,0
FN:406,RightsAssetCustodian._computeComposedKey
-FNDA:29,RightsAssetCustodian._computeComposedKey
-DA:407,29
+FNDA:0,RightsAssetCustodian._computeComposedKey
+DA:407,0
FNF:26
-FNH:25
+FNH:0
LF:99
-LH:92
+LH:0
BRF:7
-BRH:6
+BRH:0
end_of_record
TN:
SF:contracts/rights/RightsPolicyAuthorizer.sol
-DA:71,0
-FN:71,RightsPolicyAuthorizer.onlyAuditedPolicies
-FNDA:0,RightsPolicyAuthorizer.onlyAuditedPolicies
-DA:73,0
-BRDA:73,0,0,-
-DA:78,0
-FN:78,RightsPolicyAuthorizer.constructor
-FNDA:0,RightsPolicyAuthorizer.constructor
-DA:81,0
-DA:83,0
+DA:69,634
+FN:69,RightsPolicyAuthorizer.onlyAuditedPolicies
+FNDA:634,RightsPolicyAuthorizer.onlyAuditedPolicies
+DA:71,634
+BRDA:71,0,0,1
+DA:76,19
+FN:76,RightsPolicyAuthorizer.constructor
+FNDA:19,RightsPolicyAuthorizer.constructor
+DA:79,0
+DA:81,19
+DA:85,19
+FN:85,RightsPolicyAuthorizer.initialize
+FNDA:19,RightsPolicyAuthorizer.initialize
+DA:86,0
DA:87,0
-FN:87,RightsPolicyAuthorizer.initialize
-FNDA:0,RightsPolicyAuthorizer.initialize
-DA:88,0
-DA:89,0
-DA:90,0
-DA:96,0
-FN:96,RightsPolicyAuthorizer.authorizePolicy
-FNDA:0,RightsPolicyAuthorizer.authorizePolicy
-DA:98,0
-DA:99,0
-BRDA:99,1,0,-
-DA:100,0
-DA:101,0
-DA:106,0
-FN:106,RightsPolicyAuthorizer.revokePolicy
-FNDA:0,RightsPolicyAuthorizer.revokePolicy
-DA:108,0
-DA:109,0
-BRDA:109,2,0,-
-DA:110,0
-DA:116,0
-FN:116,RightsPolicyAuthorizer.isPolicyAuthorized
-FNDA:0,RightsPolicyAuthorizer.isPolicyAuthorized
-DA:117,0
-DA:124,0
-FN:124,RightsPolicyAuthorizer.getAuthorizedPolicies
-FNDA:0,RightsPolicyAuthorizer.getAuthorizedPolicies
-DA:131,0
-DA:132,0
-DA:133,0
-DA:134,0
-DA:136,0
-DA:137,0
-DA:138,0
-DA:142,0
-DA:155,0
-DA:157,0
-DA:163,0
-FN:163,RightsPolicyAuthorizer._authorizeUpgrade
+DA:88,19
+DA:94,632
+FN:94,RightsPolicyAuthorizer.authorizePolicy
+FNDA:632,RightsPolicyAuthorizer.authorizePolicy
+DA:96,632
+DA:97,632
+BRDA:97,1,0,2
+DA:99,630
+DA:100,630
+BRDA:100,2,0,1
+DA:102,629
+DA:107,94
+FN:107,RightsPolicyAuthorizer.revokePolicy
+FNDA:94,RightsPolicyAuthorizer.revokePolicy
+DA:109,94
+DA:110,94
+BRDA:110,3,0,1
+DA:111,93
+DA:117,503
+FN:117,RightsPolicyAuthorizer.isPolicyAuthorized
+FNDA:503,RightsPolicyAuthorizer.isPolicyAuthorized
+DA:118,503
+DA:125,259
+FN:125,RightsPolicyAuthorizer.getAuthorizedPolicies
+FNDA:259,RightsPolicyAuthorizer.getAuthorizedPolicies
+DA:132,259
+DA:133,259
+DA:134,259
+DA:135,259
+DA:137,259
+DA:138,270
+DA:139,232
+DA:143,232
+DA:156,0
+DA:158,0
+DA:164,0
+FN:164,RightsPolicyAuthorizer._authorizeUpgrade
FNDA:0,RightsPolicyAuthorizer._authorizeUpgrade
-DA:169,0
-FN:169,RightsPolicyAuthorizer._isValidPolicy
-FNDA:0,RightsPolicyAuthorizer._isValidPolicy
-DA:170,0
+DA:170,1402
+FN:170,RightsPolicyAuthorizer._isValidPolicy
+FNDA:1402,RightsPolicyAuthorizer._isValidPolicy
+DA:171,1402
FNF:9
-FNH:0
-LF:34
-LH:0
-BRF:3
-BRH:0
+FNH:8
+LF:35
+LH:29
+BRF:4
+BRH:4
end_of_record
TN:
SF:contracts/rights/RightsPolicyManager.sol
-DA:77,0
-FN:77,RightsPolicyManager.onlyAuthorizedPolicy
-FNDA:0,RightsPolicyManager.onlyAuthorizedPolicy
-DA:78,0
-DA:79,0
-BRDA:79,0,0,-
-DA:84,0
-FN:84,RightsPolicyManager.constructor
-FNDA:0,RightsPolicyManager.constructor
-DA:87,0
-DA:88,0
-DA:89,0
+DA:75,498
+FN:75,RightsPolicyManager.onlyAuthorizedPolicy
+FNDA:498,RightsPolicyManager.onlyAuthorizedPolicy
+DA:76,498
+BRDA:76,0,0,-
+BRDA:76,0,1,-
+DA:77,498
+DA:78,498
+BRDA:78,1,0,1
+DA:83,9
+FN:83,RightsPolicyManager.constructor
+FNDA:9,RightsPolicyManager.constructor
+DA:86,0
+DA:87,9
+DA:88,9
+DA:92,9
+FN:92,RightsPolicyManager.initialize
+FNDA:9,RightsPolicyManager.initialize
DA:93,0
-FN:93,RightsPolicyManager.initialize
-FNDA:0,RightsPolicyManager.initialize
DA:94,0
-DA:95,0
-DA:96,0
-DA:104,0
-FN:104,RightsPolicyManager.registerPolicy
-FNDA:0,RightsPolicyManager.registerPolicy
-DA:110,0
-DA:111,0
-DA:116,0
-DA:117,0
-BRDA:117,1,0,-
-DA:119,0
-DA:120,0
-DA:121,0
-DA:127,0
-FN:127,RightsPolicyManager.getActivePolicy
-FNDA:0,RightsPolicyManager.getActivePolicy
-DA:128,0
-DA:129,0
-DA:132,0
-DA:134,0
-BRDA:134,2,0,-
-DA:135,0
-DA:139,0
-DA:148,0
-FN:148,RightsPolicyManager.getActivePolicies
-FNDA:0,RightsPolicyManager.getActivePolicies
-DA:149,0
-DA:150,0
-DA:151,0
-DA:152,0
-DA:155,0
-DA:156,0
-DA:157,0
-DA:161,0
-DA:174,0
-DA:176,0
-DA:181,0
-FN:181,RightsPolicyManager.getPolicies
-FNDA:0,RightsPolicyManager.getPolicies
-DA:188,0
-DA:195,0
-FN:195,RightsPolicyManager.isActivePolicy
-FNDA:0,RightsPolicyManager.isActivePolicy
-DA:196,0
-DA:197,0
-DA:204,0
-FN:204,RightsPolicyManager.isRegisteredPolicy
-FNDA:0,RightsPolicyManager.isRegisteredPolicy
-DA:205,0
-DA:211,0
-FN:211,RightsPolicyManager._authorizeUpgrade
+DA:95,9
+DA:103,497
+FN:103,RightsPolicyManager.registerPolicy
+FNDA:497,RightsPolicyManager.registerPolicy
+DA:109,497
+DA:110,496
+DA:111,496
+BRDA:111,2,0,1
+DA:112,1
+DA:119,495
+DA:120,495
+BRDA:120,3,0,1
+DA:122,494
+DA:123,494
+DA:124,0
+DA:130,1
+FN:130,RightsPolicyManager.getActivePolicy
+FNDA:1,RightsPolicyManager.getActivePolicy
+DA:131,1
+DA:132,1
+DA:135,1
+DA:137,2
+BRDA:137,4,0,1
+DA:138,1
+DA:142,0
+DA:151,1
+FN:151,RightsPolicyManager.getActivePolicies
+FNDA:1,RightsPolicyManager.getActivePolicies
+DA:152,1
+DA:153,1
+DA:154,1
+DA:155,1
+DA:158,1
+DA:159,3
+DA:160,2
+DA:164,2
+DA:177,0
+DA:179,0
+DA:184,260
+FN:184,RightsPolicyManager.getPolicies
+FNDA:260,RightsPolicyManager.getPolicies
+DA:191,260
+DA:199,7
+FN:199,RightsPolicyManager.isActivePolicy
+FNDA:7,RightsPolicyManager.isActivePolicy
+DA:200,7
+DA:201,6
+DA:208,7
+FN:208,RightsPolicyManager.isRegisteredPolicy
+FNDA:7,RightsPolicyManager.isRegisteredPolicy
+DA:209,7
+DA:215,0
+FN:215,RightsPolicyManager._authorizeUpgrade
FNDA:0,RightsPolicyManager._authorizeUpgrade
-DA:218,0
-FN:218,RightsPolicyManager._verifyPolicyAccess
-FNDA:0,RightsPolicyManager._verifyPolicyAccess
-DA:219,0
-DA:220,0
-DA:221,0
-DA:222,0
-DA:232,0
-FN:232,RightsPolicyManager._registerBatchPolicies
-FNDA:0,RightsPolicyManager._registerBatchPolicies
-DA:238,0
-DA:240,0
-DA:241,0
-DA:242,0
-DA:251,0
-FN:251,RightsPolicyManager._registerPolicy
-FNDA:0,RightsPolicyManager._registerPolicy
-DA:252,0
+DA:222,6
+FN:222,RightsPolicyManager._verifyPolicyAccess
+FNDA:6,RightsPolicyManager._verifyPolicyAccess
+DA:223,6
+DA:224,6
+DA:225,6
+DA:226,5
+DA:236,494
+FN:236,RightsPolicyManager._registerBatchPolicies
+FNDA:494,RightsPolicyManager._registerBatchPolicies
+DA:242,494
+DA:244,494
+DA:245,495
+DA:246,495
+DA:255,495
+FN:255,RightsPolicyManager._registerPolicy
+FNDA:495,RightsPolicyManager._registerPolicy
+DA:256,495
FNF:13
-FNH:0
-LF:57
-LH:0
-BRF:3
-BRH:0
+FNH:12
+LF:60
+LH:52
+BRF:6
+BRH:4
end_of_record
TN:
SF:script/create3/CREATE3Factory.sol
-DA:10,476
+DA:10,433
FN:10,CREATE3Factory.deploy
-FNDA:476,CREATE3Factory.deploy
-DA:12,476
-DA:16,1076
+FNDA:433,CREATE3Factory.deploy
+DA:12,433
+DA:16,901
FN:16,CREATE3Factory.getDeployed
-FNDA:1076,CREATE3Factory.getDeployed
-DA:18,1076
+FNDA:901,CREATE3Factory.getDeployed
+DA:18,901
FNF:2
FNH:2
LF:4
@@ -1846,47 +1944,47 @@ BRH:0
end_of_record
TN:
SF:script/deployment/00_Deploy_Base.s.sol
-DA:14,1552
+DA:14,1334
FN:14,DeployBase.getCreate3FactoryAddress
-FNDA:1552,DeployBase.getCreate3FactoryAddress
-DA:15,1552
-DA:18,552
+FNDA:1334,DeployBase.getCreate3FactoryAddress
+DA:15,1334
+DA:18,522
FN:18,DeployBase.getAdminPK
-FNDA:552,DeployBase.getAdminPK
-DA:19,552
-DA:23,1628
+FNDA:522,DeployBase.getAdminPK
+DA:19,522
+DA:23,1423
FN:23,DeployBase.getSalt
-FNDA:1628,DeployBase.getSalt
-DA:24,1628
-DA:27,1076
+FNDA:1423,DeployBase.getSalt
+DA:24,1423
+DA:27,901
FN:27,DeployBase.computeCreate3Address
-FNDA:1076,DeployBase.computeCreate3Address
-DA:32,1076
-DA:43,1076
-DA:46,114
+FNDA:901,DeployBase.computeCreate3Address
+DA:32,901
+DA:43,901
+DA:46,50
FN:46,DeployBase.deploy
-FNDA:114,DeployBase.deploy
-DA:48,114
-DA:49,114
-DA:52,362
+FNDA:50,DeployBase.deploy
+DA:48,50
+DA:49,50
+DA:52,383
FN:52,DeployBase.deployUUPS
-FNDA:362,DeployBase.deployUUPS
-DA:58,362
-DA:62,362
-DA:63,362
-DA:65,362
-DA:85,362
-DA:89,476
+FNDA:383,DeployBase.deployUUPS
+DA:58,383
+DA:62,383
+DA:63,383
+DA:65,383
+DA:85,383
+DA:89,414
FN:89,DeployBase._checkExpectedAddress
-FNDA:476,DeployBase._checkExpectedAddress
-DA:90,476
-DA:91,476
+FNDA:414,DeployBase._checkExpectedAddress
+DA:90,414
+DA:91,414
BRDA:91,0,0,-
BRDA:91,0,1,-
-DA:94,552
+DA:94,503
FN:94,DeployBase._logAddress
-FNDA:552,DeployBase._logAddress
-DA:95,552
+FNDA:503,DeployBase._logAddress
+DA:95,503
DA:96,0
DA:97,0
FNF:8
@@ -1898,18 +1996,18 @@ BRH:0
end_of_record
TN:
SF:script/deployment/01_Deploy_Base_Create3.s.sol
-DA:8,76
+DA:8,89
FN:8,DeployCreate3Factory.run
-FNDA:76,DeployCreate3Factory.run
-DA:10,76
-DA:11,76
-DA:12,76
+FNDA:89,DeployCreate3Factory.run
+DA:10,89
+DA:11,89
+DA:12,89
DA:15,0
DA:16,0
BRDA:16,0,0,-
DA:17,0
-DA:21,76
-DA:22,76
+DA:21,89
+DA:22,89
DA:23,0
FNF:1
FNH:1
@@ -1920,18 +2018,18 @@ BRH:0
end_of_record
TN:
SF:script/deployment/02_Deploy_Access_AccessManager.s.sol
-DA:9,76
+DA:9,89
FN:9,DeployAccessManager.run
-FNDA:76,DeployAccessManager.run
-DA:10,76
-DA:11,76
-DA:13,76
-DA:14,76
-DA:15,76
-DA:16,76
-DA:17,76
-DA:19,76
-DA:20,76
+FNDA:89,DeployAccessManager.run
+DA:10,89
+DA:11,89
+DA:13,89
+DA:14,89
+DA:15,89
+DA:16,89
+DA:17,89
+DA:19,89
+DA:20,89
DA:21,0
FNF:1
FNH:1
@@ -1942,18 +2040,18 @@ BRH:0
end_of_record
TN:
SF:script/deployment/03_Deploy_Economics_Token.s.sol
-DA:8,63
+DA:8,39
FN:8,DeployToken.run
-FNDA:63,DeployToken.run
-DA:9,63
-DA:10,63
-DA:12,63
-DA:13,63
-DA:15,63
-DA:16,63
-DA:17,63
-DA:19,63
-DA:20,63
+FNDA:39,DeployToken.run
+DA:9,39
+DA:10,39
+DA:12,39
+DA:13,39
+DA:15,39
+DA:16,39
+DA:17,39
+DA:19,39
+DA:20,39
DA:21,0
FNF:1
FNH:1
@@ -1964,17 +2062,17 @@ BRH:0
end_of_record
TN:
SF:script/deployment/04_Deploy_Economics_Tollgate.s.sol
-DA:10,41
+DA:10,39
FN:10,DeployTollgate.run
-FNDA:41,DeployTollgate.run
-DA:11,41
-DA:12,41
-DA:13,41
-DA:14,41
-DA:15,41
-DA:16,41
-DA:18,41
-DA:19,41
+FNDA:39,DeployTollgate.run
+DA:11,39
+DA:12,39
+DA:13,39
+DA:14,39
+DA:15,39
+DA:16,39
+DA:18,39
+DA:19,39
DA:20,0
FNF:1
FNH:1
@@ -1985,17 +2083,17 @@ BRH:0
end_of_record
TN:
SF:script/deployment/05_Deploy_Economics_Treasury.s.sol
-DA:10,41
+DA:10,26
FN:10,DeployTreasury.run
-FNDA:41,DeployTreasury.run
-DA:11,41
-DA:12,41
-DA:13,41
-DA:14,41
-DA:15,41
-DA:16,41
-DA:18,41
-DA:19,41
+FNDA:26,DeployTreasury.run
+DA:11,26
+DA:12,26
+DA:13,26
+DA:14,26
+DA:15,26
+DA:16,26
+DA:18,26
+DA:19,26
DA:20,0
FNF:1
FNH:1
@@ -2006,17 +2104,17 @@ BRH:0
end_of_record
TN:
SF:script/deployment/06_Deploy_Financial_LedgerVault.s.sol
-DA:8,41
+DA:8,39
FN:8,DeployLedgerVault.run
-FNDA:41,DeployLedgerVault.run
-DA:10,41
-DA:11,41
-DA:12,41
-DA:13,41
-DA:14,41
-DA:15,41
-DA:17,41
-DA:18,41
+FNDA:39,DeployLedgerVault.run
+DA:10,39
+DA:11,39
+DA:12,39
+DA:13,39
+DA:14,39
+DA:15,39
+DA:17,39
+DA:18,39
DA:19,0
FNF:1
FNH:1
@@ -2027,19 +2125,19 @@ BRH:0
end_of_record
TN:
SF:script/deployment/07_Deploy_Financial_AgreementManager.s.sol
-DA:8,41
+DA:8,39
FN:8,DeployAgreementManager.run
-FNDA:41,DeployAgreementManager.run
-DA:9,41
-DA:10,41
-DA:11,41
-DA:12,41
-DA:13,41
-DA:14,41
-DA:15,41
-DA:16,41
-DA:18,41
-DA:19,41
+FNDA:39,DeployAgreementManager.run
+DA:9,39
+DA:10,39
+DA:11,39
+DA:12,39
+DA:13,39
+DA:14,39
+DA:15,39
+DA:16,39
+DA:18,39
+DA:19,39
DA:20,0
FNF:1
FNH:1
@@ -2050,20 +2148,20 @@ BRH:0
end_of_record
TN:
SF:script/deployment/08_Deploy_Financial_AgreementSettler.s.sol
-DA:8,41
+DA:8,26
FN:8,DeployAgreementSettler.run
-FNDA:41,DeployAgreementSettler.run
-DA:10,41
-DA:11,41
-DA:12,41
-DA:13,41
-DA:14,41
-DA:15,41
-DA:16,41
-DA:17,41
-DA:18,41
-DA:20,41
-DA:21,41
+FNDA:26,DeployAgreementSettler.run
+DA:10,26
+DA:11,26
+DA:12,26
+DA:13,26
+DA:14,26
+DA:15,26
+DA:16,26
+DA:17,26
+DA:18,26
+DA:20,26
+DA:21,26
DA:22,0
FNF:1
FNH:1
@@ -2074,17 +2172,17 @@ BRH:0
end_of_record
TN:
SF:script/deployment/09_Deploy_Custody_CustodianFactory.s.sol
-DA:9,51
+DA:9,11
FN:9,DeployCustodianFactory.run
-FNDA:51,DeployCustodianFactory.run
-DA:11,51
-DA:12,51
-DA:13,51
-DA:14,51
-DA:15,51
-DA:16,51
-DA:18,51
-DA:19,51
+FNDA:11,DeployCustodianFactory.run
+DA:11,11
+DA:12,11
+DA:13,11
+DA:14,11
+DA:15,11
+DA:16,11
+DA:18,11
+DA:19,11
DA:20,0
FNF:1
FNH:1
@@ -2095,40 +2193,39 @@ BRH:0
end_of_record
TN:
SF:script/deployment/10_Deploy_Custody_CustodianReferendum.s.sol
-DA:9,41
+DA:9,11
FN:9,DeployCustodianReferendum.run
-FNDA:41,DeployCustodianReferendum.run
-DA:10,41
-DA:11,41
-DA:12,41
-DA:13,41
-DA:14,41
-DA:15,41
-DA:16,41
-DA:17,41
-DA:19,41
-DA:20,41
-DA:21,0
+FNDA:11,DeployCustodianReferendum.run
+DA:10,11
+DA:11,11
+DA:12,11
+DA:13,11
+DA:14,11
+DA:15,11
+DA:16,11
+DA:18,11
+DA:19,11
+DA:20,0
FNF:1
FNH:1
-LF:12
-LH:11
+LF:11
+LH:10
BRF:0
BRH:0
end_of_record
TN:
SF:script/deployment/11_Deploy_Assets_AssetReferendum.s.sol
-DA:8,13
+DA:8,31
FN:8,DeployAssetReferendum.run
-FNDA:13,DeployAssetReferendum.run
-DA:9,13
-DA:10,13
-DA:11,13
-DA:12,13
-DA:13,13
-DA:14,13
-DA:16,13
-DA:17,13
+FNDA:31,DeployAssetReferendum.run
+DA:9,31
+DA:10,31
+DA:11,31
+DA:12,31
+DA:13,31
+DA:14,31
+DA:16,31
+DA:17,31
DA:18,0
FNF:1
FNH:1
@@ -2138,19 +2235,19 @@ BRF:0
BRH:0
end_of_record
TN:
-SF:script/deployment/12_Deploy_Assets_AssetOwnership.s.sol
-DA:8,5
-FN:8,DeployAssetOwnership.run
-FNDA:5,DeployAssetOwnership.run
-DA:10,5
-DA:11,5
-DA:12,5
-DA:13,5
-DA:14,5
-DA:15,5
-DA:16,5
-DA:18,5
-DA:19,5
+SF:script/deployment/12_Deploy_Assets_AssetRegistry.s.sol
+DA:8,18
+FN:8,DeployAssetRegistry.run
+FNDA:18,DeployAssetRegistry.run
+DA:10,18
+DA:11,18
+DA:12,18
+DA:13,18
+DA:14,18
+DA:15,18
+DA:16,18
+DA:18,18
+DA:19,18
DA:20,0
FNF:1
FNH:1
@@ -2161,18 +2258,18 @@ BRH:0
end_of_record
TN:
SF:script/deployment/13_Deploy_Assets_AssetSafe.s.sol
-DA:8,5
+DA:8,9
FN:8,DeployAssetSafe.run
-FNDA:5,DeployAssetSafe.run
-DA:10,5
-DA:11,5
-DA:12,5
-DA:13,5
-DA:14,5
-DA:15,5
-DA:16,5
-DA:18,5
-DA:19,5
+FNDA:9,DeployAssetSafe.run
+DA:10,9
+DA:11,9
+DA:12,9
+DA:13,9
+DA:14,9
+DA:15,9
+DA:16,9
+DA:18,9
+DA:19,9
DA:20,0
FNF:1
FNH:1
@@ -2183,44 +2280,44 @@ BRH:0
end_of_record
TN:
SF:script/deployment/14_Deploy_Policies_PolicyAudit.s.sol
-DA:10,0
+DA:10,28
FN:10,DeployPolicyAudit.run
-FNDA:0,DeployPolicyAudit.run
-DA:11,0
-DA:12,0
-DA:13,0
-DA:14,0
-DA:15,0
-DA:16,0
-DA:18,0
-DA:19,0
+FNDA:28,DeployPolicyAudit.run
+DA:11,28
+DA:12,28
+DA:13,28
+DA:14,28
+DA:15,28
+DA:16,28
+DA:18,28
+DA:19,28
DA:20,0
FNF:1
-FNH:0
+FNH:1
LF:10
-LH:0
+LH:9
BRF:0
BRH:0
end_of_record
TN:
SF:script/deployment/15_Deploy_RightsManager_AssetCustodian.s.sol
-DA:8,17
+DA:8,0
FN:8,DeployRightsAssetCustodian.run
-FNDA:17,DeployRightsAssetCustodian.run
-DA:10,17
-DA:11,17
-DA:12,17
-DA:13,17
-DA:14,17
-DA:15,17
-DA:16,17
-DA:18,17
-DA:19,17
+FNDA:0,DeployRightsAssetCustodian.run
+DA:10,0
+DA:11,0
+DA:12,0
+DA:13,0
+DA:14,0
+DA:15,0
+DA:16,0
+DA:18,0
+DA:19,0
DA:20,0
FNF:1
-FNH:1
+FNH:0
LF:11
-LH:10
+LH:0
BRF:0
BRH:0
end_of_record
@@ -2248,33 +2345,50 @@ BRH:0
end_of_record
TN:
SF:script/deployment/17_Deploy_RightsManager_PolicyManager.s.sol
-DA:8,0
+DA:8,9
FN:8,DeployRightsPolicyManager.run
-FNDA:0,DeployRightsPolicyManager.run
-DA:10,0
-DA:11,0
-DA:12,0
-DA:13,0
-DA:14,0
-DA:15,0
-DA:16,0
-DA:17,0
-DA:19,0
-DA:20,0
+FNDA:9,DeployRightsPolicyManager.run
+DA:10,9
+DA:11,9
+DA:12,9
+DA:13,9
+DA:14,9
+DA:15,9
+DA:16,9
+DA:17,9
+DA:19,9
+DA:20,9
DA:21,0
FNF:1
-FNH:0
+FNH:1
LF:12
-LH:0
+LH:11
+BRF:0
+BRH:0
+end_of_record
+TN:
+SF:script/deployment/18_Deploy_RightsManager_PolicyAuthorizer.s.sol
+DA:10,19
+FN:10,DeployRightsPolicyAuthorizer.run
+FNDA:19,DeployRightsPolicyAuthorizer.run
+DA:11,19
+DA:13,19
+DA:14,19
+DA:15,19
+DA:17,19
+DA:18,19
+FNF:1
+FNH:1
+LF:7
+LH:7
BRF:0
BRH:0
end_of_record
TN:
SF:script/orchestration/01_Orchestrate_ProtocolHydration.s.sol
-DA:19,0
-FN:19,OrchestrateProtocolHydration.run
-FNDA:0,OrchestrateProtocolHydration.run
DA:20,0
+FN:20,OrchestrateProtocolHydration.run
+FNDA:0,OrchestrateProtocolHydration.run
DA:21,0
DA:22,0
DA:23,0
@@ -2285,45 +2399,46 @@ DA:27,0
DA:28,0
DA:29,0
DA:30,0
+DA:31,0
DA:32,0
-DA:36,0
-DA:37,0
-DA:40,0
-DA:43,0
+DA:34,0
+DA:38,0
+DA:39,0
DA:44,0
-DA:45,0
DA:46,0
-DA:48,0
-DA:49,0
+DA:47,0
DA:50,0
DA:51,0
+DA:52,0
+DA:53,0
DA:54,0
+DA:55,0
DA:56,0
-DA:57,0
+DA:59,0
+DA:60,0
DA:61,0
DA:62,0
-DA:63,0
DA:64,0
+DA:65,0
DA:67,0
DA:68,0
DA:69,0
DA:71,0
-DA:73,0
-DA:74,0
-DA:76,0
+DA:72,0
DA:77,0
-DA:79,0
-BRDA:79,0,0,-
-BRDA:79,0,1,-
+DA:78,0
DA:80,0
-BRDA:80,1,0,-
-BRDA:80,1,1,-
DA:82,0
+DA:84,0
+DA:85,0
+BRDA:85,0,0,-
+BRDA:85,0,1,-
+DA:86,0
FNF:1
FNH:0
-LF:42
+LF:44
LH:0
-BRF:4
+BRF:2
BRH:0
end_of_record
TN:
@@ -2337,32 +2452,27 @@ DA:16,0
DA:17,0
DA:18,0
DA:19,0
-DA:20,0
-DA:22,0
+DA:21,0
+DA:23,0
DA:24,0
-DA:25,0
+DA:26,0
DA:27,0
-DA:28,0
+DA:29,0
+BRDA:29,0,0,-
+BRDA:29,0,1,-
DA:30,0
-BRDA:30,0,0,-
-BRDA:30,0,1,-
-DA:31,0
-BRDA:31,1,0,-
-BRDA:31,1,1,-
+BRDA:30,1,0,-
+BRDA:30,1,1,-
+DA:33,0
DA:34,0
DA:35,0
-DA:36,0
+DA:37,0
DA:38,0
DA:39,0
-DA:40,0
-DA:42,0
-DA:50,0
-DA:51,0
-DA:52,0
-DA:54,0
+DA:41,0
FNF:1
FNH:0
-LF:26
+LF:21
LH:0
BRF:4
BRH:0
@@ -2502,13 +2612,12 @@ DA:10,0
DA:11,0
DA:12,0
DA:13,0
-DA:14,0
+DA:17,0
DA:18,0
DA:19,0
-DA:20,0
FNF:1
FNH:0
-LF:9
+LF:8
LH:0
BRF:0
BRH:0
@@ -2532,10 +2641,10 @@ BRF:0
BRH:0
end_of_record
TN:
-SF:script/upgrades/12_Upgrade_Assets_AssetOwnership.s.sol
+SF:script/upgrades/12_Upgrade_Assets_AssetRegistry.s.sol
DA:9,0
-FN:9,UpgradeAssetOwnership.run
-FNDA:0,UpgradeAssetOwnership.run
+FN:9,UpgradeAssetRegistry.run
+FNDA:0,UpgradeAssetRegistry.run
DA:10,0
DA:11,0
DA:12,0
@@ -2609,145 +2718,181 @@ BRH:0
end_of_record
TN:
SF:test/BaseTest.t.sol
-DA:58,76
-FN:58,BaseTest.initialize
-FNDA:76,BaseTest.initialize
-DA:60,76
-DA:61,76
-DA:62,76
-DA:64,0
-DA:65,0
-DA:71,76
-FN:71,BaseTest.deployCreate3Factory
-FNDA:76,BaseTest.deployCreate3Factory
-DA:73,76
-DA:74,76
-DA:76,76
-DA:77,76
-DA:78,76
-DA:82,106
-FN:82,BaseTest.deployToken
-FNDA:106,BaseTest.deployToken
-DA:84,106
-DA:85,106
-DA:89,76
-FN:89,BaseTest.deployAccessManager
-FNDA:76,BaseTest.deployAccessManager
-DA:91,76
-DA:92,76
-DA:94,76
-DA:96,76
-DA:97,76
-DA:101,67
-FN:101,BaseTest.deployTollgate
-FNDA:67,BaseTest.deployTollgate
-DA:103,0
-DA:105,67
-DA:106,67
-DA:107,67
-DA:109,41
-DA:113,116
-FN:113,BaseTest.deployLedgerVault
-FNDA:116,BaseTest.deployLedgerVault
-DA:115,116
-DA:116,116
-DA:117,116
-DA:119,116
-DA:121,116
-DA:122,116
-DA:123,116
-DA:127,58
-FN:127,BaseTest.deployTreasury
-FNDA:58,BaseTest.deployTreasury
-DA:129,58
-DA:130,58
-DA:131,58
-DA:132,41
-DA:135,58
-FN:135,BaseTest.deployAgreementManager
-FNDA:58,BaseTest.deployAgreementManager
-DA:136,0
-DA:137,58
-DA:139,58
-DA:140,58
-DA:142,58
-DA:145,58
-FN:145,BaseTest.deployAgreementSettler
-FNDA:58,BaseTest.deployAgreementSettler
-DA:146,0
-DA:147,58
-DA:148,0
-DA:150,58
-DA:151,58
-DA:153,58
-DA:157,13
-FN:157,BaseTest.deployAssetReferendum
-FNDA:13,BaseTest.deployAssetReferendum
-DA:159,13
-DA:160,13
-DA:161,13
-DA:162,13
-DA:165,5
-FN:165,BaseTest.deployAssetOwnership
-FNDA:5,BaseTest.deployAssetOwnership
-DA:166,0
-DA:168,5
-DA:169,5
-DA:172,5
-FN:172,BaseTest.deployAssetSafe
-FNDA:5,BaseTest.deployAssetSafe
-DA:173,0
-DA:175,5
-DA:176,5
-DA:177,5
-DA:178,5
-DA:182,100
-FN:182,BaseTest.deployCustodianFactory
-FNDA:100,BaseTest.deployCustodianFactory
-DA:183,100
-DA:184,100
-DA:187,58
-FN:187,BaseTest.deployCustodianReferendum
-FNDA:58,BaseTest.deployCustodianReferendum
-DA:188,0
+DA:66,89
+FN:66,BaseTest.initialize
+FNDA:89,BaseTest.initialize
+DA:68,89
+DA:69,89
+DA:70,89
+DA:71,89
+DA:72,89
+DA:74,0
+DA:75,0
+DA:81,89
+FN:81,BaseTest.deployCreate3Factory
+FNDA:89,BaseTest.deployCreate3Factory
+DA:83,89
+DA:84,89
+DA:86,89
+DA:87,89
+DA:88,89
+DA:92,50
+FN:92,BaseTest.deployToken
+FNDA:50,BaseTest.deployToken
+DA:94,50
+DA:95,50
+DA:99,89
+FN:99,BaseTest.deployAccessManager
+FNDA:89,BaseTest.deployAccessManager
+DA:101,89
+DA:102,89
+DA:104,89
+DA:106,89
+DA:107,89
+DA:109,89
+DA:111,89
+DA:112,89
+DA:113,89
+DA:117,50
+FN:117,BaseTest.deployTollgate
+FNDA:50,BaseTest.deployTollgate
+DA:119,0
+DA:121,50
+DA:122,50
+DA:123,50
+DA:125,39
+DA:129,65
+FN:129,BaseTest.deployLedgerVault
+FNDA:65,BaseTest.deployLedgerVault
+DA:131,65
+DA:132,65
+DA:133,65
+DA:135,65
+DA:137,65
+DA:138,65
+DA:139,65
+DA:143,26
+FN:143,BaseTest.deployTreasury
+FNDA:26,BaseTest.deployTreasury
+DA:145,26
+DA:146,26
+DA:147,26
+DA:148,26
+DA:151,39
+FN:151,BaseTest.deployAgreementManager
+FNDA:39,BaseTest.deployAgreementManager
+DA:152,0
+DA:153,39
+DA:155,39
+DA:156,39
+DA:158,39
+DA:161,26
+FN:161,BaseTest.deployAgreementSettler
+FNDA:26,BaseTest.deployAgreementSettler
+DA:162,0
+DA:163,26
+DA:164,0
+DA:166,26
+DA:167,26
+DA:169,26
+DA:173,31
+FN:173,BaseTest.deployAssetReferendum
+FNDA:31,BaseTest.deployAssetReferendum
+DA:175,31
+DA:176,31
+DA:177,31
+DA:178,31
+DA:181,18
+FN:181,BaseTest.deployAssetRegistry
+FNDA:18,BaseTest.deployAssetRegistry
+DA:182,0
+DA:184,18
+DA:185,18
+DA:188,9
+FN:188,BaseTest.deployAssetSafe
+FNDA:9,BaseTest.deployAssetSafe
DA:189,0
-DA:192,58
-DA:193,58
-DA:194,58
-DA:196,41
-DA:200,17
-FN:200,BaseTest.deployRightsAssetCustodian
-FNDA:17,BaseTest.deployRightsAssetCustodian
+DA:190,9
+DA:191,9
+DA:195,11
+FN:195,BaseTest.deployCustodianFactory
+FNDA:11,BaseTest.deployCustodianFactory
+DA:196,11
+DA:197,11
+DA:200,11
+FN:200,BaseTest.deployCustodianReferendum
+FNDA:11,BaseTest.deployCustodianReferendum
DA:201,0
-DA:203,17
-DA:204,17
-DA:207,201
-FN:207,BaseTest._setGovPermissions
-FNDA:201,BaseTest._setGovPermissions
-DA:208,201
-DA:209,201
-DA:211,201
-DA:212,201
-DA:215,116
-FN:215,BaseTest._assignOpRole
-FNDA:116,BaseTest._assignOpRole
-DA:216,116
-DA:217,116
-DA:218,116
-DA:219,116
-FNF:17
-FNH:17
-LF:92
-LH:81
+DA:202,0
+DA:205,11
+DA:206,11
+DA:207,11
+DA:209,11
+DA:213,0
+FN:213,BaseTest.deployRightsAssetCustodian
+FNDA:0,BaseTest.deployRightsAssetCustodian
+DA:214,0
+DA:216,0
+DA:217,0
+DA:220,28
+FN:220,BaseTest.deployPolicyAudit
+FNDA:28,BaseTest.deployPolicyAudit
+DA:221,28
+DA:222,28
+DA:225,19
+FN:225,BaseTest.deployRightsPolicyAuthorizer
+FNDA:19,BaseTest.deployRightsPolicyAuthorizer
+DA:226,0
+DA:228,19
+DA:229,19
+DA:234,9
+FN:234,BaseTest.deployRightsPolicyManager
+FNDA:9,BaseTest.deployRightsPolicyManager
+DA:235,0
+DA:236,0
+DA:238,9
+DA:239,9
+DA:242,31
+FN:242,BaseTest._setContentCouncilPermissions
+FNDA:31,BaseTest._setContentCouncilPermissions
+DA:243,31
+DA:244,31
+DA:246,31
+DA:247,31
+DA:250,11
+FN:250,BaseTest._setNodesCouncilPermissions
+FNDA:11,BaseTest._setNodesCouncilPermissions
+DA:251,11
+DA:252,11
+DA:254,11
+DA:255,11
+DA:258,76
+FN:258,BaseTest._setGovPermissions
+FNDA:76,BaseTest._setGovPermissions
+DA:259,76
+DA:260,76
+DA:262,76
+DA:263,76
+DA:266,65
+FN:266,BaseTest._assignOpRole
+FNDA:65,BaseTest._assignOpRole
+DA:267,65
+DA:268,65
+DA:269,65
+DA:270,65
+FNF:22
+FNH:21
+LF:118
+LH:101
BRF:0
BRH:0
end_of_record
TN:
SF:test/economics/Tollgate.t.sol
-DA:19,1
-FN:19,TargetD.isFeeSchemeSupported
-FNDA:1,TargetD.isFeeSchemeSupported
DA:21,1
+FN:21,TargetD.isFeeSchemeSupported
+FNDA:1,TargetD.isFeeSchemeSupported
+DA:23,1
FNF:1
FNH:1
LF:2
@@ -2756,35 +2901,143 @@ BRF:0
BRH:0
end_of_record
TN:
+SF:test/finance/AgreementSettler.t.sol
+DA:28,6
+FN:28,SettlerMockArbiter.constructor
+FNDA:6,SettlerMockArbiter.constructor
+DA:29,6
+DA:32,1
+FN:32,SettlerMockArbiter.execute
+FNDA:1,SettlerMockArbiter.execute
+DA:33,1
+FNF:2
+FNH:2
+LF:4
+LH:4
+BRF:0
+BRH:0
+end_of_record
+TN:
+SF:test/finance/LedgerVault.t.sol
+DA:22,48
+FN:22,MockToken.mint
+FNDA:48,MockToken.mint
+DA:23,48
+DA:31,5
+FN:31,LedgerVaultHarness.lockedBalance
+FNDA:5,LedgerVaultHarness.lockedBalance
+DA:32,5
+DA:33,5
+DA:35,0
+DA:36,0
+DA:37,0
+DA:39,0
+DA:40,0
+DA:41,0
+DA:43,0
+FNF:2
+FNH:2
+LF:12
+LH:5
+BRF:0
+BRH:0
+end_of_record
+TN:
+SF:test/libraries/FeesOps.t.sol
+DA:10,3
+FN:10,FeesOpsHarness.isBasePoint
+FNDA:3,FeesOpsHarness.isBasePoint
+DA:11,3
+DA:14,3
+FN:14,FeesOpsHarness.isNominal
+FNDA:3,FeesOpsHarness.isNominal
+DA:15,3
+DA:18,4
+FN:18,FeesOpsHarness.perOf
+FNDA:4,FeesOpsHarness.perOf
+DA:19,4
+DA:22,2
+FN:22,FeesOpsHarness.calcBps
+FNDA:2,FeesOpsHarness.calcBps
+DA:23,2
+FNF:4
+FNH:4
+LF:8
+LH:8
+BRF:0
+BRH:0
+end_of_record
+TN:
+SF:test/libraries/FinancialOps.t.sol
+DA:13,36
+FN:13,TestToken.mint
+FNDA:36,TestToken.mint
+DA:14,36
+DA:21,8
+FN:21,FinancialOpsHarness.depositNative
+FNDA:8,FinancialOpsHarness.depositNative
+DA:22,8
+DA:25,8
+FN:25,FinancialOpsHarness.depositToken
+FNDA:8,FinancialOpsHarness.depositToken
+DA:26,8
+DA:29,7
+FN:29,FinancialOpsHarness.transferFunds
+FNDA:7,FinancialOpsHarness.transferFunds
+DA:30,7
+DA:33,3
+FN:33,FinancialOpsHarness.increaseTokenAllowance
+FNDA:3,FinancialOpsHarness.increaseTokenAllowance
+DA:34,3
+DA:37,1
+FN:37,FinancialOpsHarness.queryAllowance
+FNDA:1,FinancialOpsHarness.queryAllowance
+DA:38,1
+DA:41,1
+FN:41,FinancialOpsHarness.queryNativeAllowance
+FNDA:1,FinancialOpsHarness.queryNativeAllowance
+DA:42,1
+DA:45,2
+FN:45,FinancialOpsHarness.queryBalance
+FNDA:2,FinancialOpsHarness.queryBalance
+DA:46,2
+FNF:8
+FNH:8
+LF:16
+LH:16
+BRF:0
+BRH:0
+end_of_record
+TN:
SF:test/libraries/RollingOps.t.sol
-DA:17,6
-FN:17,RollingOpsWrapper.configureWindow
-FNDA:6,RollingOpsWrapper.configureWindow
-DA:18,6
+DA:13,5
+FN:13,RollingOpsHarness.configure
+FNDA:5,RollingOpsHarness.configure
+DA:14,5
+DA:17,13
+FN:17,RollingOpsHarness.roll
+FNDA:13,RollingOpsHarness.roll
+DA:18,13
+DA:21,3
+FN:21,RollingOpsHarness.contains
+FNDA:3,RollingOpsHarness.contains
DA:22,3
-FN:22,RollingOpsWrapper.getWindow
-FNDA:3,RollingOpsWrapper.getWindow
-DA:23,3
-DA:28,33
-FN:28,RollingOpsWrapper.add
-FNDA:33,RollingOpsWrapper.add
-DA:29,33
-DA:35,6
-FN:35,RollingOpsWrapper.exists
-FNDA:6,RollingOpsWrapper.exists
-DA:36,6
-DA:41,2
-FN:41,RollingOpsWrapper.getLength
-FNDA:2,RollingOpsWrapper.getLength
-DA:42,2
-DA:48,6
-FN:48,RollingOpsWrapper.getAt
-FNDA:6,RollingOpsWrapper.getAt
-DA:49,6
-DA:54,3
-FN:54,RollingOpsWrapper.getAll
-FNDA:3,RollingOpsWrapper.getAll
-DA:55,3
+DA:25,4
+FN:25,RollingOpsHarness.length
+FNDA:4,RollingOpsHarness.length
+DA:26,4
+DA:29,2
+FN:29,RollingOpsHarness.window
+FNDA:2,RollingOpsHarness.window
+DA:30,2
+DA:33,8
+FN:33,RollingOpsHarness.at
+FNDA:8,RollingOpsHarness.at
+DA:34,8
+DA:37,1
+FN:37,RollingOpsHarness.values
+FNDA:1,RollingOpsHarness.values
+DA:38,1
FNF:7
FNH:7
LF:14
@@ -2793,84 +3046,626 @@ BRF:0
BRH:0
end_of_record
TN:
-SF:test/primitives/Quorum.t.sol
-DA:12,7
-FN:12,QuorumWrapper.status
-FNDA:7,QuorumWrapper.status
-DA:13,7
-DA:16,2
-FN:16,QuorumWrapper.revoke
-FNDA:2,QuorumWrapper.revoke
-DA:17,2
-DA:20,3
-FN:20,QuorumWrapper.approve
-FNDA:3,QuorumWrapper.approve
-DA:21,3
+SF:test/policies/PolicyAudit.t.sol
+DA:15,0
+FN:15,MockPolicy.setup
+FNDA:0,MockPolicy.setup
+DA:17,0
+FN:17,MockPolicy.enforce
+FNDA:0,MockPolicy.enforce
+DA:18,0
+DA:21,0
+FN:21,MockPolicy.isAccessAllowed
+FNDA:0,MockPolicy.isAccessAllowed
+DA:22,0
+DA:25,0
+FN:25,MockPolicy.getLicense
+FNDA:0,MockPolicy.getLicense
+DA:26,0
+DA:29,0
+FN:29,MockPolicy.resolveTerms
+FNDA:0,MockPolicy.resolveTerms
+DA:30,0
+DA:33,0
+FN:33,MockPolicy.getAttestationProvider
+FNDA:0,MockPolicy.getAttestationProvider
+DA:34,0
+DA:37,0
+FN:37,MockPolicy.name
+FNDA:0,MockPolicy.name
+DA:38,0
+DA:41,0
+FN:41,MockPolicy.description
+FNDA:0,MockPolicy.description
+DA:42,0
+DA:45,30
+FN:45,MockPolicy.supportsInterface
+FNDA:30,MockPolicy.supportsInterface
+DA:46,30
+FNF:9
+FNH:1
+LF:17
+LH:2
+BRF:0
+BRH:0
+end_of_record
+TN:
+SF:test/policies/PolicyBase.t.sol
+DA:17,0
+FN:17,RightsPolicyManagerVerifiableMock.getPolicies
+FNDA:0,RightsPolicyManagerVerifiableMock.getPolicies
+DA:18,0
+DA:21,0
+FN:21,RightsPolicyManagerVerifiableMock.getActivePolicy
+FNDA:0,RightsPolicyManagerVerifiableMock.getActivePolicy
+DA:22,0
+DA:25,0
+FN:25,RightsPolicyManagerVerifiableMock.getActivePolicies
+FNDA:0,RightsPolicyManagerVerifiableMock.getActivePolicies
+DA:26,0
+DA:29,0
+FN:29,RightsPolicyManagerVerifiableMock.isActivePolicy
+FNDA:0,RightsPolicyManagerVerifiableMock.isActivePolicy
+DA:30,0
+DA:33,0
+FN:33,RightsPolicyManagerVerifiableMock.isRegisteredPolicy
+FNDA:0,RightsPolicyManagerVerifiableMock.isRegisteredPolicy
+DA:34,0
+DA:39,0
+FN:39,RightsPolicyAuthorizerVerifiableMock.getAuthorizedPolicies
+FNDA:0,RightsPolicyAuthorizerVerifiableMock.getAuthorizedPolicies
+DA:40,0
+DA:43,0
+FN:43,RightsPolicyAuthorizerVerifiableMock.isPolicyAuthorized
+FNDA:0,RightsPolicyAuthorizerVerifiableMock.isPolicyAuthorized
+DA:44,0
+DA:56,0
+FN:56,AttestationProviderMock.getName
+FNDA:0,AttestationProviderMock.getName
+DA:57,0
+DA:60,0
+FN:60,AttestationProviderMock.getAddress
+FNDA:0,AttestationProviderMock.getAddress
+DA:61,0
+DA:64,1
+FN:64,AttestationProviderMock.attest
+FNDA:1,AttestationProviderMock.attest
+DA:69,1
+DA:70,1
+DA:71,1
+DA:72,2
+DA:75,1
+DA:76,1
+DA:77,1
+DA:79,1
+DA:80,1
+DA:81,2
+DA:82,2
+DA:83,2
+DA:85,1
+BRDA:85,0,0,-
+DA:86,0
+DA:90,1
+FN:90,AttestationProviderMock.lastRecipientsLength
+FNDA:1,AttestationProviderMock.lastRecipientsLength
+DA:91,1
+DA:94,2
+FN:94,AttestationProviderMock.lastRecipientAt
+FNDA:2,AttestationProviderMock.lastRecipientAt
+DA:95,2
+DA:98,0
+FN:98,AttestationProviderMock.verify
+FNDA:0,AttestationProviderMock.verify
+DA:99,0
+DA:106,1
+FN:106,AssetRegistryMock.register
+FNDA:1,AssetRegistryMock.register
+DA:107,1
+DA:110,0
+FN:110,AssetRegistryMock.revoke
+FNDA:0,AssetRegistryMock.revoke
+DA:111,0
+DA:114,0
+FN:114,AssetRegistryMock.transfer
+FNDA:0,AssetRegistryMock.transfer
+DA:115,0
+DA:127,1
+FN:127,PolicyBaseHarness.managerPing
+FNDA:1,PolicyBaseHarness.managerPing
+DA:128,1
+DA:131,1
+FN:131,PolicyBaseHarness.authorizerPing
+FNDA:1,PolicyBaseHarness.authorizerPing
+DA:132,1
+DA:135,1
+FN:135,PolicyBaseHarness.exposeCommit
+FNDA:1,PolicyBaseHarness.exposeCommit
+DA:140,1
+DA:143,1
+FN:143,PolicyBaseHarness.exposeSetAttestation
+FNDA:1,PolicyBaseHarness.exposeSetAttestation
+DA:144,1
+DA:147,1
+FN:147,PolicyBaseHarness.exposeHolder
+FNDA:1,PolicyBaseHarness.exposeHolder
+DA:148,1
+DA:151,0
+FN:151,PolicyBaseHarness.setup
+FNDA:0,PolicyBaseHarness.setup
+DA:153,0
+FN:153,PolicyBaseHarness.enforce
+FNDA:0,PolicyBaseHarness.enforce
+DA:157,0
+DA:160,0
+FN:160,PolicyBaseHarness.isAccessAllowed
+FNDA:0,PolicyBaseHarness.isAccessAllowed
+DA:161,0
+DA:164,0
+FN:164,PolicyBaseHarness.resolveTerms
+FNDA:0,PolicyBaseHarness.resolveTerms
+DA:166,0
+FN:166,PolicyBaseHarness.name
+FNDA:0,PolicyBaseHarness.name
+DA:167,0
+DA:170,0
+FN:170,PolicyBaseHarness.description
+FNDA:0,PolicyBaseHarness.description
+DA:171,0
+FNF:27
+FNH:9
+LF:65
+LH:30
+BRF:1
+BRH:0
+end_of_record
+TN:
+SF:test/primitives/AccessControlledUpgradeable.t.sol
+DA:15,7
+FN:15,AccessControlledHarness.initialize
+FNDA:7,AccessControlledHarness.initialize
+DA:16,7
+DA:19,1
+FN:19,AccessControlledHarness.adminAction
+FNDA:1,AccessControlledHarness.adminAction
+DA:20,1
+DA:21,1
+DA:24,2
+FN:24,AccessControlledHarness.opsAction
+FNDA:2,AccessControlledHarness.opsAction
+DA:25,2
+DA:26,2
+DA:29,2
+FN:29,AccessControlledHarness.isPaused
+FNDA:2,AccessControlledHarness.isPaused
+DA:30,2
+DA:33,3
+FN:33,AccessControlledHarness.hasRoleView
+FNDA:3,AccessControlledHarness.hasRoleView
+DA:34,3
+FNF:5
+FNH:5
+LF:12
+LH:12
+BRF:0
+BRH:0
+end_of_record
+TN:
+SF:test/primitives/AllowanceOperatorUpgradeable.t.sol
+DA:12,8
+FN:12,AllowanceOperatorHarness.initialize
+FNDA:8,AllowanceOperatorHarness.initialize
+DA:13,0
+DA:16,3
+FN:16,AllowanceOperatorHarness.boostLedger
+FNDA:3,AllowanceOperatorHarness.boostLedger
+DA:17,3
+DA:20,10
+FN:20,AllowanceOperatorHarness.approve
+FNDA:10,AllowanceOperatorHarness.approve
+DA:21,10
DA:24,3
-FN:24,QuorumWrapper.quit
-FNDA:3,QuorumWrapper.quit
+FN:24,AllowanceOperatorHarness.revoke
+FNDA:3,AllowanceOperatorHarness.revoke
DA:25,3
-DA:28,9
-FN:28,QuorumWrapper.register
-FNDA:9,QuorumWrapper.register
-DA:29,9
-DA:32,2
-FN:32,QuorumWrapper.reject
-FNDA:2,QuorumWrapper.reject
-DA:33,2
+DA:28,5
+FN:28,AllowanceOperatorHarness.collect
+FNDA:5,AllowanceOperatorHarness.collect
+DA:29,5
+DA:32,5
+FN:32,AllowanceOperatorHarness.allowance
+FNDA:5,AllowanceOperatorHarness.allowance
+DA:33,5
FNF:6
FNH:6
LF:12
-LH:12
+LH:11
BRF:0
BRH:0
end_of_record
TN:
-SF:test/shared/CustodianShared.t.sol
-DA:14,32
-FN:14,CustodianShared.setUp
-FNDA:32,CustodianShared.setUp
-DA:15,0
-DA:16,0
-DA:19,38
-FN:19,CustodianShared.deployCustodian
-FNDA:38,CustodianShared.deployCustodian
-DA:20,38
-DA:21,38
-DA:22,38
-DA:25,33
-FN:25,CustodianShared._setFeesAsGovernor
-FNDA:33,CustodianShared._setFeesAsGovernor
-DA:26,33
-DA:27,33
-DA:28,33
-DA:31,31
-FN:31,CustodianShared._registerCustodianWithApproval
-FNDA:31,CustodianShared._registerCustodianWithApproval
-DA:34,31
-DA:36,31
-DA:37,31
-DA:39,31
-DA:41,31
-DA:42,31
-DA:45,32
-FN:45,CustodianShared._createAgreement
-FNDA:32,CustodianShared._createAgreement
-DA:46,32
-DA:47,32
-DA:49,32
-DA:57,32
-DA:60,28
-FN:60,CustodianShared._registerAndApproveCustodian
-FNDA:28,CustodianShared._registerAndApproveCustodian
-DA:62,28
-DA:64,28
-DA:65,28
-DA:67,28
+SF:test/primitives/BalanceOperatorUpgradeable.t.sol
+DA:23,0
+FN:23,MockToken.totalSupply
+FNDA:0,MockToken.totalSupply
+DA:24,0
+DA:27,8
+FN:27,MockToken.balanceOf
+FNDA:8,MockToken.balanceOf
+DA:28,8
+DA:31,3
+FN:31,MockToken.transfer
+FNDA:3,MockToken.transfer
+DA:32,3
+DA:33,3
+DA:36,7
+FN:36,MockToken.allowance
+FNDA:7,MockToken.allowance
+DA:37,7
+DA:40,6
+FN:40,MockToken.approve
+FNDA:6,MockToken.approve
+DA:41,6
+DA:42,6
+DA:43,0
+DA:46,6
+FN:46,MockToken.transferFrom
+FNDA:6,MockToken.transferFrom
+DA:47,6
+DA:48,6
+BRDA:48,0,0,-
+BRDA:48,0,1,-
+DA:49,6
+DA:50,6
+DA:51,0
+DA:54,14
+FN:54,MockToken.mint
+FNDA:14,MockToken.mint
+DA:55,14
+DA:56,14
+DA:57,14
+DA:60,9
+FN:60,MockToken._transfer
+FNDA:9,MockToken._transfer
+DA:61,9
+BRDA:61,1,0,-
+BRDA:61,1,1,-
+DA:62,9
+BRDA:62,2,0,-
+BRDA:62,2,1,-
+DA:63,9
+DA:64,9
+DA:65,9
+DA:70,7
+FN:70,BalanceOperatorHarness.deposit
+FNDA:7,BalanceOperatorHarness.deposit
+DA:71,7
+DA:74,4
+FN:74,BalanceOperatorHarness.withdraw
+FNDA:4,BalanceOperatorHarness.withdraw
+DA:75,4
+DA:78,4
+FN:78,BalanceOperatorHarness.transfer
+FNDA:4,BalanceOperatorHarness.transfer
+DA:79,4
+FNF:11
+FNH:10
+LF:35
+LH:31
+BRF:6
+BRH:0
+end_of_record
+TN:
+SF:test/primitives/LedgerUpgradeable.t.sol
+DA:9,6
+FN:9,LedgerUpgradeableHarness.initialize
+FNDA:6,LedgerUpgradeableHarness.initialize
+DA:10,0
+DA:13,7
+FN:13,LedgerUpgradeableHarness.setEntry
+FNDA:7,LedgerUpgradeableHarness.setEntry
+DA:14,7
+DA:17,3
+FN:17,LedgerUpgradeableHarness.sumEntry
+FNDA:3,LedgerUpgradeableHarness.sumEntry
+DA:18,3
+DA:21,3
+FN:21,LedgerUpgradeableHarness.subEntry
+FNDA:3,LedgerUpgradeableHarness.subEntry
+DA:22,3
+FNF:4
+FNH:4
+LF:8
+LH:7
+BRF:0
+BRH:0
+end_of_record
+TN:
+SF:test/primitives/LockOperatorUpgradeable.t.sol
+DA:16,8
+FN:16,LockOperatorHarness.initialize
+FNDA:8,LockOperatorHarness.initialize
+DA:17,0
+DA:20,7
+FN:20,LockOperatorHarness.seedLedger
+FNDA:7,LockOperatorHarness.seedLedger
+DA:21,7
+DA:24,9
+FN:24,LockOperatorHarness.lock
+FNDA:9,LockOperatorHarness.lock
+DA:25,9
+DA:28,3
+FN:28,LockOperatorHarness.release
+FNDA:3,LockOperatorHarness.release
+DA:29,3
+DA:32,3
+FN:32,LockOperatorHarness.claim
+FNDA:3,LockOperatorHarness.claim
+DA:33,3
+DA:36,4
+FN:36,LockOperatorHarness.lockedBalance
+FNDA:4,LockOperatorHarness.lockedBalance
+DA:37,4
+DA:38,4
+DA:40,0
+DA:41,0
+DA:42,0
+DA:44,0
+DA:45,0
+DA:46,0
+DA:48,0
FNF:6
FNH:6
-LF:28
-LH:26
+LF:20
+LH:12
BRF:0
BRH:0
end_of_record
+TN:
+SF:test/primitives/QuorumUpgradeable.t.sol
+DA:10,11
+FN:10,QuorumUpgradeableHarness.initialize
+FNDA:11,QuorumUpgradeableHarness.initialize
+DA:11,0
+DA:14,6
+FN:14,QuorumUpgradeableHarness.statusOf
+FNDA:6,QuorumUpgradeableHarness.statusOf
+DA:15,6
+DA:18,7
+FN:18,QuorumUpgradeableHarness.register
+FNDA:7,QuorumUpgradeableHarness.register
+DA:19,7
+DA:22,3
+FN:22,QuorumUpgradeableHarness.approve
+FNDA:3,QuorumUpgradeableHarness.approve
+DA:23,3
+DA:26,2
+FN:26,QuorumUpgradeableHarness.blockEntry
+FNDA:2,QuorumUpgradeableHarness.blockEntry
+DA:27,2
+DA:30,2
+FN:30,QuorumUpgradeableHarness.quit
+FNDA:2,QuorumUpgradeableHarness.quit
+DA:31,2
+DA:34,2
+FN:34,QuorumUpgradeableHarness.revoke
+FNDA:2,QuorumUpgradeableHarness.revoke
+DA:35,2
+FNF:7
+FNH:7
+LF:14
+LH:13
+BRF:0
+BRH:0
+end_of_record
+TN:
+SF:test/rights/RightsPolicyAuthorizer.t.sol
+DA:18,0
+FN:18,DummyAttestationProvider.getName
+FNDA:0,DummyAttestationProvider.getName
+DA:19,0
+DA:22,0
+FN:22,DummyAttestationProvider.getAddress
+FNDA:0,DummyAttestationProvider.getAddress
+DA:23,0
+DA:26,0
+FN:26,DummyAttestationProvider.attest
+FNDA:0,DummyAttestationProvider.attest
+DA:31,0
+DA:34,0
+FN:34,DummyAttestationProvider.verify
+FNDA:0,DummyAttestationProvider.verify
+DA:35,0
+DA:50,777
+FN:50,PolicyBaseAuthorizerHarness.constructor
+FNDA:777,PolicyBaseAuthorizerHarness.constructor
+DA:53,777
+DA:56,1
+FN:56,PolicyBaseAuthorizerHarness.setSetupRevert
+FNDA:1,PolicyBaseAuthorizerHarness.setSetupRevert
+DA:57,1
+DA:60,1
+FN:60,PolicyBaseAuthorizerHarness.setSetupRevertNoData
+FNDA:1,PolicyBaseAuthorizerHarness.setSetupRevertNoData
+DA:61,1
+DA:64,1
+FN:64,PolicyBaseAuthorizerHarness.setTriggerReentrancy
+FNDA:1,PolicyBaseAuthorizerHarness.setTriggerReentrancy
+DA:65,1
+DA:66,1
+DA:69,1
+FN:69,PolicyBaseAuthorizerHarness.lastHolder
+FNDA:1,PolicyBaseAuthorizerHarness.lastHolder
+DA:70,1
+DA:73,1
+FN:73,PolicyBaseAuthorizerHarness.lastInit
+FNDA:1,PolicyBaseAuthorizerHarness.lastInit
+DA:74,1
+DA:77,366
+FN:77,PolicyBaseAuthorizerHarness.setup
+FNDA:366,PolicyBaseAuthorizerHarness.setup
+DA:78,1
+BRDA:78,0,0,1
+DA:79,1
+BRDA:79,1,0,1
+BRDA:79,1,1,-
+DA:81,0
+DA:84,0
+DA:87,365
+BRDA:87,2,0,1
+DA:88,1
+DA:89,1
+DA:91,364
+DA:92,364
+DA:95,0
+FN:95,PolicyBaseAuthorizerHarness.enforce
+FNDA:0,PolicyBaseAuthorizerHarness.enforce
+DA:96,0
+DA:99,0
+FN:99,PolicyBaseAuthorizerHarness.isAccessAllowed
+FNDA:0,PolicyBaseAuthorizerHarness.isAccessAllowed
+DA:100,0
+DA:104,0
+FN:104,PolicyBaseAuthorizerHarness.resolveTerms
+FNDA:0,PolicyBaseAuthorizerHarness.resolveTerms
+DA:106,0
+FN:106,PolicyBaseAuthorizerHarness.name
+FNDA:0,PolicyBaseAuthorizerHarness.name
+DA:107,0
+DA:110,0
+FN:110,PolicyBaseAuthorizerHarness.description
+FNDA:0,PolicyBaseAuthorizerHarness.description
+DA:111,0
+FNF:16
+FNH:7
+LF:40
+LH:21
+BRF:4
+BRH:3
+end_of_record
+TN:
+SF:test/rights/RightsPolicyManager.t.sol
+DA:35,494
+FN:35,ConfigurableAttestationProvider.setNextAttestationIds
+FNDA:494,ConfigurableAttestationProvider.setNextAttestationIds
+DA:36,494
+DA:37,494
+DA:38,494
+DA:39,495
+DA:43,0
+FN:43,ConfigurableAttestationProvider.getName
+FNDA:0,ConfigurableAttestationProvider.getName
+DA:44,0
+DA:47,0
+FN:47,ConfigurableAttestationProvider.getAddress
+FNDA:0,ConfigurableAttestationProvider.getAddress
+DA:48,0
+DA:51,494
+FN:51,ConfigurableAttestationProvider.attest
+FNDA:494,ConfigurableAttestationProvider.attest
+DA:56,494
+DA:57,494
+DA:58,494
+DA:59,495
+DA:62,494
+DA:63,494
+DA:64,494
+DA:66,494
+DA:67,494
+DA:69,494
+BRDA:69,1,0,494
+BRDA:69,1,1,-
+DA:70,494
+BRDA:70,2,0,-
+BRDA:70,2,1,-
+DA:71,494
+DA:72,495
+DA:74,494
+DA:76,0
+DA:77,0
+DA:81,494
+DA:82,495
+DA:84,0
+DA:87,0
+FN:87,ConfigurableAttestationProvider.verify
+FNDA:0,ConfigurableAttestationProvider.verify
+DA:88,0
+DA:91,0
+FN:91,ConfigurableAttestationProvider.lastRecipientsLength
+FNDA:0,ConfigurableAttestationProvider.lastRecipientsLength
+DA:92,0
+DA:95,0
+FN:95,ConfigurableAttestationProvider.lastRecipientAt
+FNDA:0,ConfigurableAttestationProvider.lastRecipientAt
+DA:96,0
+DA:118,0
+FN:118,PolicyBaseManagerHarness.setSetupRevert
+FNDA:0,PolicyBaseManagerHarness.setSetupRevert
+DA:119,0
+DA:122,5
+FN:122,PolicyBaseManagerHarness.setAccessAllowed
+FNDA:5,PolicyBaseManagerHarness.setAccessAllowed
+DA:123,5
+DA:126,1
+FN:126,PolicyBaseManagerHarness.setAccessRevert
+FNDA:1,PolicyBaseManagerHarness.setAccessRevert
+DA:127,1
+DA:130,1
+FN:130,PolicyBaseManagerHarness.setEnforceRevert
+FNDA:1,PolicyBaseManagerHarness.setEnforceRevert
+DA:131,1
+DA:134,1
+FN:134,PolicyBaseManagerHarness.lastHolder
+FNDA:1,PolicyBaseManagerHarness.lastHolder
+DA:135,1
+DA:138,0
+FN:138,PolicyBaseManagerHarness.lastSetupData
+FNDA:0,PolicyBaseManagerHarness.lastSetupData
+DA:139,0
+DA:142,1
+FN:142,PolicyBaseManagerHarness.getLastAgreement
+FNDA:1,PolicyBaseManagerHarness.getLastAgreement
+DA:143,1
+DA:146,266
+FN:146,PolicyBaseManagerHarness.setup
+FNDA:266,PolicyBaseManagerHarness.setup
+DA:147,0
+BRDA:147,0,0,-
+DA:148,266
+DA:149,266
+DA:152,495
+FN:152,PolicyBaseManagerHarness.enforce
+FNDA:495,PolicyBaseManagerHarness.enforce
+DA:156,1
+BRDA:156,1,0,1
+DA:157,494
+DA:158,494
+DA:160,494
+DA:161,494
+DA:163,494
+DA:164,494
+DA:165,494
+DA:166,495
+DA:170,6
+FN:170,PolicyBaseManagerHarness.isAccessAllowed
+FNDA:6,PolicyBaseManagerHarness.isAccessAllowed
+DA:171,1
+BRDA:171,2,0,1
+DA:172,5
+DA:175,0
+FN:175,PolicyBaseManagerHarness.resolveTerms
+FNDA:0,PolicyBaseManagerHarness.resolveTerms
+DA:177,0
+FN:177,PolicyBaseManagerHarness.name
+FNDA:0,PolicyBaseManagerHarness.name
+DA:178,0
+DA:181,0
+FN:181,PolicyBaseManagerHarness.description
+FNDA:0,PolicyBaseManagerHarness.description
+DA:182,0
+FNF:20
+FNH:10
+LF:71
+LH:48
+BRF:7
+BRH:3
+end_of_record
diff --git a/packages/protocol/package.json b/packages/protocol/package.json
index 8cd6eb4..6d3f0ff 100644
--- a/packages/protocol/package.json
+++ b/packages/protocol/package.json
@@ -1,6 +1,6 @@
{
"name": "@synaps3/protocol",
- "version": "1.10.13",
+ "version": "1.10.16",
"description": "Core contracts for the Synapse Protocol",
"homepage": "https://github.com/Synaps3Protocol/protocol-core-v1#readme",
"license": "BUSL-1.1",
diff --git a/packages/types/package.json b/packages/types/package.json
index 61cf82e..24f3851 100644
--- a/packages/types/package.json
+++ b/packages/types/package.json
@@ -1,6 +1,6 @@
{
"name": "@synaps3/types",
- "version": "1.10.11",
+ "version": "1.10.12",
"description": "Essential interfaces and types for Synapse Protocol.",
"homepage": "https://github.com/Synaps3Protocol/protocol-core-v1#readme",
"license": "BUSL-1.1",
diff --git a/script/deployment/00_Deploy_Base.s.sol b/script/deployment/00_Deploy_Base.s.sol
index 20f746a..3343e38 100644
--- a/script/deployment/00_Deploy_Base.s.sol
+++ b/script/deployment/00_Deploy_Base.s.sol
@@ -15,7 +15,7 @@ abstract contract DeployBase is Script {
return vm.envAddress("CREATE3_FACTORY");
}
- function getAdminPK() public view returns (uint256) {
+ function getDeployerPK() public view returns (uint256) {
return vm.envUint("PRIVATE_KEY");
}
diff --git a/script/deployment/01_Deploy_Base_Create3.s.sol b/script/deployment/01_Deploy_Base_Create3.s.sol
index f5abc9f..d05156d 100644
--- a/script/deployment/01_Deploy_Base_Create3.s.sol
+++ b/script/deployment/01_Deploy_Base_Create3.s.sol
@@ -7,7 +7,7 @@ import { CREATE3Factory } from "script/create3/CREATE3Factory.sol";
contract DeployCreate3Factory is DeployBase {
function run() external returns (address factory) {
- vm.startBroadcast(getAdminPK());
+ vm.startBroadcast(getDeployerPK());
bytes32 salt = getSalt("SALT_CREATE3_FACTORY");
bytes memory bytecode = type(CREATE3Factory).creationCode;
diff --git a/script/deployment/02_Deploy_Access_AccessManager.s.sol b/script/deployment/02_Deploy_Access_AccessManager.s.sol
index d7b2a8a..27b9c22 100644
--- a/script/deployment/02_Deploy_Access_AccessManager.s.sol
+++ b/script/deployment/02_Deploy_Access_AccessManager.s.sol
@@ -7,7 +7,7 @@ import { AccessManager } from "contracts/access/AccessManager.sol";
contract DeployAccessManager is DeployBase {
function run() external returns (address) {
- uint256 privateKey = getAdminPK();
+ uint256 privateKey = getDeployerPK();
address publicKey = vm.addr(privateKey);
vm.startBroadcast(privateKey);
diff --git a/script/deployment/03_Deploy_Economics_Token.s.sol b/script/deployment/03_Deploy_Economics_Token.s.sol
index 90e20d4..ae9cad0 100644
--- a/script/deployment/03_Deploy_Economics_Token.s.sol
+++ b/script/deployment/03_Deploy_Economics_Token.s.sol
@@ -6,7 +6,7 @@ import { MMC } from "contracts/economics/MMC.sol";
contract DeployToken is DeployBase {
function run() external returns (address) {
- uint256 privateKey = getAdminPK();
+ uint256 privateKey = getDeployerPK();
address publicKey = vm.addr(privateKey);
vm.startBroadcast(privateKey);
diff --git a/script/deployment/04_Deploy_Economics_Tollgate.s.sol b/script/deployment/04_Deploy_Economics_Tollgate.s.sol
index a2ff4b8..d8b3d62 100644
--- a/script/deployment/04_Deploy_Economics_Tollgate.s.sol
+++ b/script/deployment/04_Deploy_Economics_Tollgate.s.sol
@@ -8,7 +8,7 @@ import { C } from "contracts/core/primitives/Constants.sol";
contract DeployTollgate is DeployBase {
function run() external returns (address) {
- vm.startBroadcast(getAdminPK());
+ vm.startBroadcast(getDeployerPK());
address impl = address(new Tollgate());
address accessManager = computeCreate3Address("SALT_ACCESS_MANAGER");
bytes memory init = abi.encodeCall(Tollgate.initialize, (accessManager));
diff --git a/script/deployment/05_Deploy_Economics_Treasury.s.sol b/script/deployment/05_Deploy_Economics_Treasury.s.sol
index 9f4311a..846d8c2 100644
--- a/script/deployment/05_Deploy_Economics_Treasury.s.sol
+++ b/script/deployment/05_Deploy_Economics_Treasury.s.sol
@@ -8,7 +8,7 @@ import { C } from "contracts/core/primitives/Constants.sol";
contract DeployTreasury is DeployBase {
function run() external returns (address) {
- vm.startBroadcast(getAdminPK());
+ vm.startBroadcast(getDeployerPK());
address impl = address(new Treasury());
address accessManager = computeCreate3Address("SALT_ACCESS_MANAGER");
bytes memory init = abi.encodeCall(Treasury.initialize, (accessManager));
diff --git a/script/deployment/06_Deploy_Financial_LedgerVault.s.sol b/script/deployment/06_Deploy_Financial_LedgerVault.s.sol
index f6bcc8c..772f6b9 100644
--- a/script/deployment/06_Deploy_Financial_LedgerVault.s.sol
+++ b/script/deployment/06_Deploy_Financial_LedgerVault.s.sol
@@ -7,7 +7,7 @@ import { LedgerVault } from "contracts/financial/LedgerVault.sol";
contract DeployLedgerVault is DeployBase {
function run() external returns (address) {
- vm.startBroadcast(getAdminPK());
+ vm.startBroadcast(getDeployerPK());
address accessManager = computeCreate3Address("SALT_ACCESS_MANAGER");
address impl = address(new LedgerVault());
bytes memory init = abi.encodeCall(LedgerVault.initialize, (accessManager));
diff --git a/script/deployment/07_Deploy_Financial_AgreementManager.s.sol b/script/deployment/07_Deploy_Financial_AgreementManager.s.sol
index 8788758..f1acdaf 100644
--- a/script/deployment/07_Deploy_Financial_AgreementManager.s.sol
+++ b/script/deployment/07_Deploy_Financial_AgreementManager.s.sol
@@ -6,7 +6,7 @@ import { AgreementManager } from "contracts/financial/AgreementManager.sol";
contract DeployAgreementManager is DeployBase {
function run() external returns (address) {
- vm.startBroadcast(getAdminPK());
+ vm.startBroadcast(getDeployerPK());
address tollgate = computeCreate3Address("SALT_TOLLGATE");
address vault = computeCreate3Address("SALT_LEDGER_VAULT");
address accessManager = computeCreate3Address("SALT_ACCESS_MANAGER");
diff --git a/script/deployment/08_Deploy_Financial_AgreementSettler.s.sol b/script/deployment/08_Deploy_Financial_AgreementSettler.s.sol
index f94a149..09e3459 100644
--- a/script/deployment/08_Deploy_Financial_AgreementSettler.s.sol
+++ b/script/deployment/08_Deploy_Financial_AgreementSettler.s.sol
@@ -7,7 +7,7 @@ import { AgreementSettler } from "contracts/financial/AgreementSettler.sol";
contract DeployAgreementSettler is DeployBase {
function run() external returns (address) {
- vm.startBroadcast(getAdminPK());
+ vm.startBroadcast(getDeployerPK());
address treasury = computeCreate3Address("SALT_TREASURY");
address vault = computeCreate3Address("SALT_LEDGER_VAULT");
address agreementManager = computeCreate3Address("SALT_AGREEMENT_MANAGER");
diff --git a/script/deployment/09_Deploy_Custody_CustodianFactory.s.sol b/script/deployment/09_Deploy_Custody_CustodianFactory.s.sol
index 7998ba8..9293afe 100644
--- a/script/deployment/09_Deploy_Custody_CustodianFactory.s.sol
+++ b/script/deployment/09_Deploy_Custody_CustodianFactory.s.sol
@@ -8,7 +8,7 @@ import { CustodianFactory } from "contracts/custody/CustodianFactory.sol";
contract DeployCustodianFactory is DeployBase {
function run() public returns (address) {
- vm.startBroadcast(getAdminPK());
+ vm.startBroadcast(getDeployerPK());
CustodianImpl imp = new CustodianImpl(); // implementation
bytes memory creationCode = type(CustodianFactory).creationCode;
bytes memory initCode = abi.encodePacked(creationCode, abi.encode(address(imp)));
diff --git a/script/deployment/10_Deploy_Custody_CustodianReferendum.s.sol b/script/deployment/10_Deploy_Custody_CustodianReferendum.s.sol
index 67d2ad4..b1d8d6d 100644
--- a/script/deployment/10_Deploy_Custody_CustodianReferendum.s.sol
+++ b/script/deployment/10_Deploy_Custody_CustodianReferendum.s.sol
@@ -7,11 +7,10 @@ import { C } from "contracts/core/primitives/Constants.sol";
contract DeployCustodianReferendum is DeployBase {
function run() external returns (address) {
- vm.startBroadcast(getAdminPK());
- address agreementSettler = computeCreate3Address("SALT_AGREEMENT_SETTLER");
+ vm.startBroadcast(getDeployerPK());
address custodianFactory = computeCreate3Address("SALT_CUSTODIAN_FACTORY");
address accessManager = computeCreate3Address("SALT_ACCESS_MANAGER");
- address impl = address(new CustodianReferendum(agreementSettler, custodianFactory));
+ address impl = address(new CustodianReferendum(custodianFactory));
bytes memory init = abi.encodeCall(CustodianReferendum.initialize, (accessManager));
address referendum = deployUUPS(impl, init, "SALT_CUSTODIAN_REFERENDUM");
vm.stopBroadcast();
diff --git a/script/deployment/11_Deploy_Assets_AssetReferendum.s.sol b/script/deployment/11_Deploy_Assets_AssetReferendum.s.sol
index 70ca6bc..81304c7 100644
--- a/script/deployment/11_Deploy_Assets_AssetReferendum.s.sol
+++ b/script/deployment/11_Deploy_Assets_AssetReferendum.s.sol
@@ -6,7 +6,7 @@ import { AssetReferendum } from "contracts/assets/AssetReferendum.sol";
contract DeployAssetReferendum is DeployBase {
function run() external returns (address) {
- vm.startBroadcast(getAdminPK());
+ vm.startBroadcast(getDeployerPK());
address impl = address(new AssetReferendum());
address accessManager = computeCreate3Address("SALT_ACCESS_MANAGER");
bytes memory init = abi.encodeCall(AssetReferendum.initialize, (accessManager));
diff --git a/script/deployment/12_Deploy_Assets_AssetOwnership.s.sol b/script/deployment/12_Deploy_Assets_AssetOwnership.s.sol
deleted file mode 100644
index 7a027fb..0000000
--- a/script/deployment/12_Deploy_Assets_AssetOwnership.s.sol
+++ /dev/null
@@ -1,22 +0,0 @@
-// SPDX-License-Identifier: BUSL-1.1
-pragma solidity 0.8.26;
-
-import { DeployBase } from "script/deployment/00_Deploy_Base.s.sol";
-import { AssetOwnership } from "contracts/assets/AssetOwnership.sol";
-
-contract DeployAssetOwnership is DeployBase {
- function run() external returns (address) {
-
- vm.startBroadcast(getAdminPK());
- address accessManager = computeCreate3Address("SALT_ACCESS_MANAGER");
- address assetReferendum = computeCreate3Address("SALT_ASSET_REFERENDUM");
- address impl = address(new AssetOwnership(assetReferendum));
- bytes memory init = abi.encodeCall(AssetOwnership.initialize, (accessManager));
- address assetOwnersip = deployUUPS(impl, init, "SALT_ASSET_OWNERSHIP");
- vm.stopBroadcast();
-
- _checkExpectedAddress(assetOwnersip, "SALT_ASSET_OWNERSHIP");
- _logAddress("ASSET_OWNERSHIP", assetOwnersip);
- return assetOwnersip;
- }
-}
diff --git a/script/deployment/12_Deploy_Assets_AssetRegistry.s.sol b/script/deployment/12_Deploy_Assets_AssetRegistry.s.sol
new file mode 100644
index 0000000..11e2579
--- /dev/null
+++ b/script/deployment/12_Deploy_Assets_AssetRegistry.s.sol
@@ -0,0 +1,22 @@
+// SPDX-License-Identifier: BUSL-1.1
+pragma solidity 0.8.26;
+
+import { DeployBase } from "script/deployment/00_Deploy_Base.s.sol";
+import { AssetRegistry } from "contracts/assets/AssetRegistry.sol";
+
+contract DeployAssetRegistry is DeployBase {
+ function run() external returns (address) {
+
+ vm.startBroadcast(getDeployerPK());
+ address accessManager = computeCreate3Address("SALT_ACCESS_MANAGER");
+ address assetReferendum = computeCreate3Address("SALT_ASSET_REFERENDUM");
+ address impl = address(new AssetRegistry(assetReferendum));
+ bytes memory init = abi.encodeCall(AssetRegistry.initialize, (accessManager));
+ address assetRegistry = deployUUPS(impl, init, "SALT_ASSET_REGISTRY");
+ vm.stopBroadcast();
+
+ _checkExpectedAddress(assetRegistry, "SALT_ASSET_REGISTRY");
+ _logAddress("ASSET_REGISTRY", assetRegistry);
+ return assetRegistry;
+ }
+}
diff --git a/script/deployment/13_Deploy_Assets_AssetSafe.s.sol b/script/deployment/13_Deploy_Assets_AssetSafe.s.sol
index 8dba3a2..07de9b7 100644
--- a/script/deployment/13_Deploy_Assets_AssetSafe.s.sol
+++ b/script/deployment/13_Deploy_Assets_AssetSafe.s.sol
@@ -7,10 +7,10 @@ import { AssetSafe } from "contracts/assets/AssetSafe.sol";
contract DeployAssetSafe is DeployBase {
function run() external returns (address) {
- vm.startBroadcast(getAdminPK());
+ vm.startBroadcast(getDeployerPK());
address accessManager = computeCreate3Address("SALT_ACCESS_MANAGER");
- address AssetOwnership = computeCreate3Address("SALT_ASSET_OWNERSHIP");
- address impl = address(new AssetSafe(AssetOwnership));
+ address assetRegistry = computeCreate3Address("SALT_ASSET_REGISTRY");
+ address impl = address(new AssetSafe(assetRegistry));
bytes memory init = abi.encodeCall(AssetSafe.initialize, (accessManager));
address assetVault = deployUUPS(impl, init, "SALT_ASSET_SAFE");
vm.stopBroadcast();
diff --git a/script/deployment/14_Deploy_Policies_PolicyAudit.s.sol b/script/deployment/14_Deploy_Policies_PolicyAudit.s.sol
index b4bf512..4ea8348 100644
--- a/script/deployment/14_Deploy_Policies_PolicyAudit.s.sol
+++ b/script/deployment/14_Deploy_Policies_PolicyAudit.s.sol
@@ -8,7 +8,7 @@ import { C } from "contracts/core/primitives/Constants.sol";
contract DeployPolicyAudit is DeployBase {
function run() external returns (address) {
- vm.startBroadcast(getAdminPK());
+ vm.startBroadcast(getDeployerPK());
address impl = address(new PolicyAudit());
address accessManager = computeCreate3Address("SALT_ACCESS_MANAGER");
bytes memory init = abi.encodeCall(PolicyAudit.initialize, (accessManager));
diff --git a/script/deployment/15_Deploy_RightsManager_AssetCustodian.s.sol b/script/deployment/15_Deploy_RightsManager_AssetCustodian.s.sol
index 53c8d79..179c033 100644
--- a/script/deployment/15_Deploy_RightsManager_AssetCustodian.s.sol
+++ b/script/deployment/15_Deploy_RightsManager_AssetCustodian.s.sol
@@ -7,7 +7,7 @@ import { RightsAssetCustodian } from "contracts/rights/RightsAssetCustodian.sol"
contract DeployRightsAssetCustodian is DeployBase {
function run() external returns (address) {
- vm.startBroadcast(getAdminPK());
+ vm.startBroadcast(getDeployerPK());
address accessManager = computeCreate3Address("SALT_ACCESS_MANAGER");
address custodianReferendum = computeCreate3Address("SALT_CUSTODIAN_REFERENDUM");
address impl = address(new RightsAssetCustodian(custodianReferendum));
diff --git a/script/deployment/16_Deploy_RightsManager_PolicyAuthorizer.s.sol b/script/deployment/16_Deploy_RightsManager_PolicyAuthorizer.s.sol
index c92216e..0a1d249 100644
--- a/script/deployment/16_Deploy_RightsManager_PolicyAuthorizer.s.sol
+++ b/script/deployment/16_Deploy_RightsManager_PolicyAuthorizer.s.sol
@@ -7,7 +7,7 @@ import { RightsPolicyAuthorizer } from "contracts/rights/RightsPolicyAuthorizer.
contract DeployRightsPolicyAuthorizer is DeployBase {
function run() external returns (address) {
- vm.startBroadcast(getAdminPK());
+ vm.startBroadcast(getDeployerPK());
address accessManager = computeCreate3Address("SALT_ACCESS_MANAGER");
address policyAudit = computeCreate3Address("SALT_POLICY_AUDIT");
address impl = address(new RightsPolicyAuthorizer(policyAudit));
diff --git a/script/deployment/17_Deploy_RightsManager_PolicyManager.s.sol b/script/deployment/17_Deploy_RightsManager_PolicyManager.s.sol
index f60d560..b99c408 100644
--- a/script/deployment/17_Deploy_RightsManager_PolicyManager.s.sol
+++ b/script/deployment/17_Deploy_RightsManager_PolicyManager.s.sol
@@ -7,7 +7,7 @@ import { RightsPolicyManager } from "contracts/rights/RightsPolicyManager.sol";
contract DeployRightsPolicyManager is DeployBase {
function run() external returns (address) {
- vm.startBroadcast(getAdminPK());
+ vm.startBroadcast(getDeployerPK());
address accessManager = computeCreate3Address("SALT_ACCESS_MANAGER");
address agreementSettler = computeCreate3Address("SALT_AGREEMENT_SETTLER");
address rightsAuthorizer = computeCreate3Address("SALT_RIGHT_POLICY_AUTHORIZER");
diff --git a/script/deployment/18_Deploy_RightsManager_PolicyAuthorizer.s.sol b/script/deployment/18_Deploy_RightsManager_PolicyAuthorizer.s.sol
new file mode 100644
index 0000000..09dec55
--- /dev/null
+++ b/script/deployment/18_Deploy_RightsManager_PolicyAuthorizer.s.sol
@@ -0,0 +1,21 @@
+// SPDX-License-Identifier: BUSL-1.1
+pragma solidity 0.8.26;
+
+import { DeployBase } from "script/deployment/00_Deploy_Base.s.sol";
+import { RightsPolicyAuthorizer } from "contracts/rights/RightsPolicyAuthorizer.sol";
+
+/// @notice Local deployment helper for test environments.
+/// @dev Mirrors the production deployment but allows injecting custom dependencies when needed.
+contract DeployRightsPolicyAuthorizer is DeployBase {
+ function run(address policyAudit, address accessManager) external returns (address) {
+ vm.startBroadcast(getDeployerPK());
+
+ address implementation = address(new RightsPolicyAuthorizer(policyAudit));
+ bytes memory initData = abi.encodeCall(RightsPolicyAuthorizer.initialize, accessManager);
+ address proxy = deployUUPS(implementation, initData, "SALT_RIGHT_POLICY_AUTHORIZER");
+
+ vm.stopBroadcast();
+ return proxy;
+ }
+}
+
diff --git a/script/orchestration/01_Orchestrate_ProtocolHydration.s.sol b/script/orchestration/01_Orchestrate_ProtocolHydration.s.sol
index 1d1cc30..aea636f 100644
--- a/script/orchestration/01_Orchestrate_ProtocolHydration.s.sol
+++ b/script/orchestration/01_Orchestrate_ProtocolHydration.s.sol
@@ -4,6 +4,7 @@ import "forge-std/Script.sol";
import { IAccessManager } from "contracts/core/interfaces/access/IAccessManager.sol";
import { ITollgate } from "contracts/core/interfaces/economics/ITollgate.sol";
+import { ILedgerVault } from "contracts/core/interfaces/financial/ILedgerVault.sol";
import { C } from "contracts/core/primitives/Constants.sol";
import { T } from "contracts/core/primitives/Types.sol";
@@ -14,14 +15,16 @@ import { getGovPermissions as AssetReferendumGovPermissions } from "script/permi
import { getModPermissions as PolicyAuditorModPermissions } from "script/permissions/Permissions_PolicyAuditor.sol";
import { getOpsPermissions as LedgerVaultOpsPermissions } from "script/permissions/Permissions_LedgerVault.sol";
import { getModPermissions as HooksModPermissions } from "script/permissions/Permissions_HookRegistry.sol";
+import { getPauserPermissions as PauserPermissions } from "script/permissions/Permissions_AccessControlled.sol";
contract OrchestrateProtocolHydration is Script {
function run() external {
- uint256 admin = vm.envUint("PRIVATE_KEY");
+ uint256 governor = vm.envUint("PRIVATE_KEY");
address tollgateAddress = vm.envAddress("TOLLGATE");
address treasuryAddress = vm.envAddress("TREASURY");
address auditorAddress = vm.envAddress("POLICY_AUDIT");
address accessManager = vm.envAddress("ACCESS_MANAGER");
+ address assetRegistry = vm.envAddress("ASSET_REGISTRY");
address assetReferendum = vm.envAddress("ASSET_REFERENDUM");
address agreementManager = vm.envAddress("AGREEMENT_MANAGER");
address agreementSettler = vm.envAddress("AGREEMENT_SETTLER");
@@ -29,56 +32,60 @@ contract OrchestrateProtocolHydration is Script {
address custodianReferendum = vm.envAddress("CUSTODIAN_REFERENDUM");
address ledgerVault = vm.envAddress("LEDGER_VAULT");
- vm.startBroadcast(admin);
+ vm.startBroadcast(governor);
// 1 set the initial governor to operate over the protocol configuration
// initially the admin will have the role of "governor"
// initially the admin will be the mod to setup policies
- address adminAddress = vm.addr(admin);
+ // address adminAddress = vm.addr(governor);
IAccessManager authority = IAccessManager(accessManager);
+
// the governor is set to admin to handle initial setup..
- // after this can be revoked and assign the governance as governor
- authority.grantRole(C.GOV_ROLE, adminAddress, 0);
+ // after this can be revoked and assign the governance timelock as governor
+ // https://docs.openzeppelin.com/contracts/5.x/access-control#role-admins-and-guardians
+ // authority.grantRole(C.GOV_ROLE, adminAddress, 0);
+
+ authority.grantRole(C.OPS_ROLE, agreementManager, 0);
+ authority.grantRole(C.OPS_ROLE, agreementSettler, 0);
// assign governance permissions
+ bytes4[] memory pauserAllowed = PauserPermissions();
+ bytes4[] memory vaultAllowed = LedgerVaultOpsPermissions();
bytes4[] memory tollgateAllowed = TollgateGovPermissions();
bytes4[] memory treasuryAllowed = TreasuryGovPermissions();
+ bytes4[] memory auditorAllowed = PolicyAuditorModPermissions();
bytes4[] memory assetReferendumAllowed = AssetReferendumGovPermissions();
bytes4[] memory custodianReferendumAllowed = CustodianReferendumGovPermissions();
+ // pausable list of contracts
+ authority.setTargetFunctionRole(assetRegistry, pauserAllowed, C.SEC_ROLE);
+ authority.setTargetFunctionRole(ledgerVault, pauserAllowed, C.SEC_ROLE);
+ authority.setTargetFunctionRole(treasuryAddress, pauserAllowed, C.SEC_ROLE);
+ authority.setTargetFunctionRole(custodianReferendum, pauserAllowed, C.SEC_ROLE);
+
authority.setTargetFunctionRole(tollgateAddress, tollgateAllowed, C.GOV_ROLE);
authority.setTargetFunctionRole(treasuryAddress, treasuryAllowed, C.GOV_ROLE);
- authority.setTargetFunctionRole(assetReferendum, assetReferendumAllowed, C.GOV_ROLE);
- authority.setTargetFunctionRole(custodianReferendum, custodianReferendumAllowed, C.GOV_ROLE);
- // assign moderation permissions
- authority.grantRole(C.MOD_ROLE, adminAddress, 0);
- // bytes4[] memory hookModAllowed = HooksModPermissions();
- bytes4[] memory auditorAllowed = PolicyAuditorModPermissions();
- authority.setTargetFunctionRole(auditorAddress, auditorAllowed, C.MOD_ROLE);
- // authority.setTargetFunctionRole(auditorAddress, hookModAllowed, C.MOD_ROLE);
+ authority.setTargetFunctionRole(treasuryAddress, treasuryAllowed, C.TREASURER_ROLE);
+ authority.setTargetFunctionRole(assetReferendum, assetReferendumAllowed, C.CONTENT_COUNCIL_ROLE);
+ authority.setTargetFunctionRole(custodianReferendum, custodianReferendumAllowed, C.CUSTODY_COUNCIL_ROLE);
- // assign operations permissions
- authority.grantRole(C.OPS_ROLE, agreementManager, 0);
- authority.grantRole(C.OPS_ROLE, agreementSettler, 0);
- bytes4[] memory vaultAllowed = LedgerVaultOpsPermissions();
authority.setTargetFunctionRole(ledgerVault, vaultAllowed, C.OPS_ROLE);
+ authority.setTargetFunctionRole(auditorAddress, auditorAllowed, C.ADMIN_ROLE);
+ // bytes4[] memory hookModAllowed = HooksModPermissions();
+ // authority.setTargetFunctionRole(auditorAddress, hookModAllowed, C.MOD_ROLE);
// 2 set mmc as the initial currency and fees
uint256 agrFee = vm.envUint("AGREEMENT_FEES"); // 5% 500 bps
- uint256 synFees = vm.envUint("CUSTODY_FEES"); // 100 MMC flat fee
address currency = vm.envAddress("MMC");
ITollgate tollgate = ITollgate(tollgateAddress);
+ ILedgerVault vault = ILedgerVault(ledgerVault);
// assign bps scheme to right policy manager + fees + mmc
+ vault.allowCurrency(currency); // the currency needs whitelisting
tollgate.setFees(T.Scheme.BPS, rightPolicyManager, agrFee, currency);
- tollgate.setFees(T.Scheme.FLAT, custodianReferendum, synFees, currency);
(uint256 feeA, ) = tollgate.getFees(rightPolicyManager, currency);
- (uint256 feeB, ) = tollgate.getFees(custodianReferendum, currency);
-
- require(feeA == agrFee);
- require(feeB == synFees);
-
+ assert(feeA == agrFee);
vm.stopBroadcast();
}
}
diff --git a/script/orchestration/02_Orchestrate_ProtocolCustodianNetwork.s.sol b/script/orchestration/02_Orchestrate_ProtocolCustodianNetwork.s.sol
index edeed37..ddc3a8f 100644
--- a/script/orchestration/02_Orchestrate_ProtocolCustodianNetwork.s.sol
+++ b/script/orchestration/02_Orchestrate_ProtocolCustodianNetwork.s.sol
@@ -11,15 +11,14 @@ import { IAgreementManager } from "contracts/core/interfaces/financial/IAgreemen
contract OrchestrateProtocolCustodianNetwork is DeployBase {
function run() external {
- uint256 admin = getAdminPK();
+ uint256 governor = getDeployerPK();
address mmc = vm.envAddress("MMC");
uint256 fees = vm.envUint("CUSTODY_FEES"); // 100 MMC flat fee
address vault = computeCreate3Address("SALT_LEDGER_VAULT");
address custodianFactory = vm.envAddress("CUSTODIAN_FACTORY");
address custodianReferendum = vm.envAddress("CUSTODIAN_REFERENDUM");
- address agreementManager = vm.envAddress("AGREEMENT_MANAGER");
- vm.startBroadcast(admin);
+ vm.startBroadcast(governor);
// approve initial custodian
address custodian = ICustodianFactory(custodianFactory).create("https://g.watchit.movie");
ICustodianReferendum referendum = ICustodianReferendum(custodianReferendum);
@@ -27,27 +26,15 @@ contract OrchestrateProtocolCustodianNetwork is DeployBase {
bytes32 got = keccak256(abi.encodePacked(ICustodian(custodian).getEndpoint()));
bytes32 expected = keccak256(abi.encodePacked("https://g.watchit.movie"));
- require(ICustodian(custodian).getManager() == vm.addr(admin));
+ require(ICustodian(custodian).getManager() == vm.addr(governor));
require(got == expected);
// deposit funds to register custodian
IERC20(mmc).approve(vault, fees);
- ILedgerVault(vault).deposit(vm.addr(admin), fees, mmc);
+ ILedgerVault(vault).deposit(vm.addr(governor), fees, mmc);
ILedgerVault(vault).approve(address(referendum), fees, mmc);
- address custody = address(custodian);
- address[] memory parties = new address[](1);
- parties[0] = custody;
-
- uint256 proof = IAgreementManager(agreementManager).createAgreement(
- fees,
- mmc,
- address(referendum),
- parties,
- ""
- );
-
- referendum.register(proof, address(custodian));
+ referendum.register(address(custodian));
referendum.approve(address(custodian));
vm.stopBroadcast();
diff --git a/script/orchestration/03_Orchestrate_ProtocolRightsCustodian.s.sol b/script/orchestration/03_Orchestrate_ProtocolRightsCustodian.s.sol
index 18bb541..e2bbb1a 100644
--- a/script/orchestration/03_Orchestrate_ProtocolRightsCustodian.s.sol
+++ b/script/orchestration/03_Orchestrate_ProtocolRightsCustodian.s.sol
@@ -6,11 +6,11 @@ import { IRightsAssetCustodian } from "contracts/core/interfaces/rights/IRightsA
contract OrchestrateRightsCustodian is Script {
function run() external {
- uint256 admin = vm.envUint("PRIVATE_KEY");
+ uint256 governor = vm.envUint("PRIVATE_KEY");
address rightsCustodian = vm.envAddress("RIGHT_ASSET_CUSTODIAN");
address defaultCustodian = vm.envAddress("DEFAULT_CUSTODIAN_ADDRESS");
- vm.startBroadcast(admin);
+ vm.startBroadcast(governor);
// approve initial custodian
IRightsAssetCustodian custodian = IRightsAssetCustodian(rightsCustodian);
custodian.grantCustody(defaultCustodian); // assign my content custody to custodian
diff --git a/script/permissions/Permissions_AccessControlled.sol b/script/permissions/Permissions_AccessControlled.sol
new file mode 100644
index 0000000..741c361
--- /dev/null
+++ b/script/permissions/Permissions_AccessControlled.sol
@@ -0,0 +1,13 @@
+// SPDX-License-Identifier: BUSL-1.1
+pragma solidity 0.8.26;
+import { AccessControlledUpgradeable } from "contracts/core/primitives/upgradeable/AccessControlledUpgradeable.sol";
+import { AssetRegistry } from "contracts/assets/AssetRegistry.sol";
+
+function getPauserPermissions() pure returns (bytes4[] memory) {
+ // AssetReferendum grant access to governance
+ bytes4[] memory accessControlled = new bytes4[](3);
+ accessControlled[0] = AccessControlledUpgradeable.pause.selector;
+ accessControlled[0] = AccessControlledUpgradeable.unpause.selector;
+ return accessControlled;
+}
+
diff --git a/script/permissions/Permissions_CustodianReferendum.sol b/script/permissions/Permissions_CustodianReferendum.sol
index ba41c74..68ceeb2 100644
--- a/script/permissions/Permissions_CustodianReferendum.sol
+++ b/script/permissions/Permissions_CustodianReferendum.sol
@@ -4,7 +4,6 @@ import { CustodianReferendum } from "contracts/custody/CustodianReferendum.sol";
function getGovPermissions() pure returns (bytes4[] memory) {
bytes4[] memory custodianReferendumAllowed = new bytes4[](3);
- custodianReferendumAllowed[0] = CustodianReferendum.setExpirationPeriod.selector;
custodianReferendumAllowed[1] = CustodianReferendum.revoke.selector;
custodianReferendumAllowed[2] = CustodianReferendum.approve.selector;
return custodianReferendumAllowed;
diff --git a/script/permissions/Permissions_LedgerVault.sol b/script/permissions/Permissions_LedgerVault.sol
index d223719..e1cc4f1 100644
--- a/script/permissions/Permissions_LedgerVault.sol
+++ b/script/permissions/Permissions_LedgerVault.sol
@@ -9,3 +9,10 @@ function getOpsPermissions() pure returns (bytes4[] memory) {
vaultAllowed[2] = LedgerVault.claim.selector;
return vaultAllowed;
}
+
+function getGovPermissions() pure returns (bytes4[] memory) {
+ bytes4[] memory vaultAllowed = new bytes4[](2);
+ vaultAllowed[0] = LedgerVault.allowCurrency.selector;
+ vaultAllowed[1] = LedgerVault.blockCurrency.selector;
+ return vaultAllowed;
+}
\ No newline at end of file
diff --git a/script/upgrades/10_Upgrade_Custody_CustodianReferendum.s.sol b/script/upgrades/10_Upgrade_Custody_CustodianReferendum.s.sol
index ad0ae96..d3b2dd2 100644
--- a/script/upgrades/10_Upgrade_Custody_CustodianReferendum.s.sol
+++ b/script/upgrades/10_Upgrade_Custody_CustodianReferendum.s.sol
@@ -8,9 +8,8 @@ import { C } from "contracts/core/primitives/Constants.sol";
contract UpgradeCustodianReferendum is UpgradeBase {
function run() external returns (address) {
vm.startBroadcast(getAdminPK());
- address agreementSettler = vm.envAddress("AGREEMENT_SETTLER");
address custodianFactory = vm.envAddress("CUSTODIAN_FACTORY");
- address impl = address(new CustodianReferendum(agreementSettler, custodianFactory));
+ address impl = address(new CustodianReferendum(custodianFactory));
address referendumProxy = vm.envAddress("CUSTODIAN_REFERENDUM");
// address accessManager = vm.envAddress("ACCESS_MANAGER");
//!IMPORTANT: This is not a safe upgrade, take any caution or 2-check needed before run this method
diff --git a/script/upgrades/12_Upgrade_Assets_AssetOwnership.s.sol b/script/upgrades/12_Upgrade_Assets_AssetRegistry.s.sol
similarity index 62%
rename from script/upgrades/12_Upgrade_Assets_AssetOwnership.s.sol
rename to script/upgrades/12_Upgrade_Assets_AssetRegistry.s.sol
index 7fd560d..bfa15e6 100644
--- a/script/upgrades/12_Upgrade_Assets_AssetOwnership.s.sol
+++ b/script/upgrades/12_Upgrade_Assets_AssetRegistry.s.sol
@@ -2,20 +2,20 @@
pragma solidity 0.8.26;
import { UpgradeBase } from "script/upgrades/00_Upgrade_Base.s.sol";
-import { AssetOwnership } from "contracts/assets/AssetOwnership.sol";
+import { AssetRegistry } from "contracts/assets/AssetRegistry.sol";
import { C } from "contracts/core/primitives/Constants.sol";
-contract UpgradeAssetOwnership is UpgradeBase {
+contract UpgradeAssetRegistry is UpgradeBase {
function run() external returns (address) {
vm.startBroadcast(getAdminPK());
address assetReferendum = vm.envAddress("ASSET_REFERENDUM");
- address impl = address(new AssetOwnership(assetReferendum));
- address assetOwnershipProxy = vm.envAddress("ASSET_OWNERSHIP");
+ address impl = address(new AssetRegistry(assetReferendum));
+ address assetRegistryProxy = vm.envAddress("ASSET_REGISTRY");
// address accessManager = vm.envAddress("ACCESS_MANAGER");
//!IMPORTANT: This is not a safe upgrade, take any caution or 2-check needed before run this method
// bytes memory init = abi.encodeCall(LedgerVaultV2.initializeV2, (accessManager));
- address assetOwnership = upgradeAndCallUUPS(assetOwnershipProxy, impl, ""); // no initialization
+ address assetRegistry = upgradeAndCallUUPS(assetRegistryProxy, impl, ""); // no initialization
vm.stopBroadcast();
- return assetOwnership;
+ return assetRegistry;
}
}
diff --git a/test/BaseTest.t.sol b/test/BaseTest.t.sol
index a6ffe61..ed56858 100644
--- a/test/BaseTest.t.sol
+++ b/test/BaseTest.t.sol
@@ -11,21 +11,26 @@ import { DeployTreasury } from "script/deployment/05_Deploy_Economics_Treasury.s
import { DeployLedgerVault } from "script/deployment/06_Deploy_Financial_LedgerVault.s.sol";
import { DeployAssetReferendum } from "script/deployment/11_Deploy_Assets_AssetReferendum.s.sol";
import { DeployAssetSafe } from "script/deployment/13_Deploy_Assets_AssetSafe.s.sol";
-import { DeployAssetOwnership } from "script/deployment/12_Deploy_Assets_AssetOwnership.s.sol";
+import { DeployAssetRegistry } from "script/deployment/12_Deploy_Assets_AssetRegistry.s.sol";
import { DeployCustodianFactory } from "script/deployment/09_Deploy_Custody_CustodianFactory.s.sol";
import { DeployCustodianReferendum } from "script/deployment/10_Deploy_Custody_CustodianReferendum.s.sol";
import { DeployAgreementManager } from "script/deployment/07_Deploy_Financial_AgreementManager.s.sol";
import { DeployAgreementSettler } from "script/deployment/08_Deploy_Financial_AgreementSettler.s.sol";
import { DeployRightsAssetCustodian } from "script/deployment/15_Deploy_RightsManager_AssetCustodian.s.sol";
+import { DeployRightsPolicyAuthorizer } from "script/deployment/18_Deploy_RightsManager_PolicyAuthorizer.s.sol";
+import { DeployRightsPolicyManager } from "script/deployment/17_Deploy_RightsManager_PolicyManager.s.sol";
+import { DeployPolicyAudit } from "script/deployment/14_Deploy_Policies_PolicyAudit.s.sol";
import { getGovPermissions as TollgateGovPermissions } from "script/permissions/Permissions_Tollgate.sol";
import { getGovPermissions as TreasuryGovPermissions } from "script/permissions/Permissions_Treasury.sol";
import { getGovPermissions as CustodianReferendumGovPermissions } from "script/permissions/Permissions_CustodianReferendum.sol";
import { getGovPermissions as AssetReferendumGovPermissions } from "script/permissions/Permissions_AssetReferendum.sol";
import { getOpsPermissions as LedgerVaultOpsPermissions } from "script/permissions/Permissions_LedgerVault.sol";
+import { getGovPermissions as LedgerVaultGovPermissions } from "script/permissions/Permissions_LedgerVault.sol";
import { IAccessManager } from "contracts/core/interfaces/access/IAccessManager.sol";
import { C } from "contracts/core/primitives/Constants.sol";
+import { ILedgerVault } from "contracts/core/interfaces/financial/ILedgerVault.sol";
import { console } from "forge-std/console.sol";
@@ -35,7 +40,10 @@ import { console } from "forge-std/console.sol";
abstract contract BaseTest is Test {
address admin;
address user;
+ address sec;
address governor;
+ address contentCouncil;
+ address nodesCouncil;
address accessManager;
address agreementManager;
@@ -43,12 +51,15 @@ abstract contract BaseTest is Test {
address assetSafe;
address assetReferendum;
- address assetOwnership;
+ address assetRegistry;
address custodianReferendum;
address custodianFactory;
address rightAssetCustodian;
+ address policyAudit;
+ address rightsPolicyAuthorizer;
+ address rightsPolicyManager;
address tollgate;
address treasury;
@@ -57,9 +68,12 @@ abstract contract BaseTest is Test {
modifier initialize() {
// setup the admin to operate in tests..
+ admin = vm.addr(1);
user = vm.addr(2);
- governor = vm.addr(1);
- admin = vm.addr(vm.envUint("PRIVATE_KEY"));
+ governor = vm.addr(vm.envUint("PRIVATE_KEY"));
+ contentCouncil = vm.addr(3);
+ nodesCouncil = vm.addr(4);
+ sec = vm.addr(5);
deployCreate3Factory();
deployAccessManager();
@@ -91,10 +105,17 @@ abstract contract BaseTest is Test {
DeployAccessManager accessManagerDeployer = new DeployAccessManager();
accessManager = accessManager == address(0) ? accessManagerDeployer.run() : accessManager;
- vm.prank(admin);
+ vm.startPrank(governor);
// add to governor the gov role
IAccessManager authority = IAccessManager(accessManager);
- authority.grantRole(C.GOV_ROLE, governor, 0);
+ authority.grantRole(C.ADMIN_ROLE, admin, 0);
+ // add to councils the corresponding role
+ authority.grantRole(C.CONTENT_COUNCIL_ROLE, contentCouncil, 0);
+ authority.grantRole(C.CUSTODY_COUNCIL_ROLE, nodesCouncil, 0);
+ vm.stopPrank();
+
+ vm.prank(admin);
+ authority.grantRole(C.SEC_ROLE, sec, 0);
}
// 02_DeployTollgate
@@ -114,12 +135,16 @@ abstract contract BaseTest is Test {
// set default admin as deployer..
DeployLedgerVault ledgerDeployer = new DeployLedgerVault();
bytes4[] memory ledgerAllowed = LedgerVaultOpsPermissions();
+ bytes4[] memory ledgerGovAllowed = LedgerVaultGovPermissions();
ledger = ledger == address(0) ? ledgerDeployer.run() : ledger;
- vm.prank(admin);
// op role needed to call functions in ledger contract
- IAccessManager authority = IAccessManager(accessManager);
- authority.setTargetFunctionRole(ledger, ledgerAllowed, C.OPS_ROLE);
+ _setOpsPermissions(ledger, ledgerAllowed);
+ _setGovPermissions(ledger, ledgerGovAllowed);
+
+ vm.prank(governor);
+ ILedgerVault(ledger).allowCurrency(token);
+
return ledger;
}
@@ -159,23 +184,20 @@ abstract contract BaseTest is Test {
DeployAssetReferendum assetReferendumDeployer = new DeployAssetReferendum();
bytes4[] memory referendumAllowed = AssetReferendumGovPermissions();
assetReferendum = assetReferendum == address(0) ? assetReferendumDeployer.run() : assetReferendum;
- _setGovPermissions(assetReferendum, referendumAllowed);
+ _setContentCouncilPermissions(assetReferendum, referendumAllowed);
}
- function deployAssetOwnership() public {
+ function deployAssetRegistry() public {
deployAssetReferendum();
// set default admin as deployer..
- DeployAssetOwnership assetOwnershipDeployer = new DeployAssetOwnership();
- assetOwnership = assetOwnership == address(0) ? assetOwnershipDeployer.run() : assetOwnership;
+ DeployAssetRegistry assetRegistryDeployer = new DeployAssetRegistry();
+ assetRegistry = assetRegistry == address(0) ? assetRegistryDeployer.run() : assetRegistry;
}
function deployAssetSafe() public {
- deployAssetOwnership();
-
+ deployAssetRegistry();
DeployAssetSafe assetVaultDeployer = new DeployAssetSafe();
- bytes4[] memory referendumAllowed = AssetReferendumGovPermissions();
assetSafe = assetSafe == address(0) ? assetVaultDeployer.run() : assetSafe;
- _setGovPermissions(assetSafe, referendumAllowed);
}
// 08_DeployCustodian
@@ -193,29 +215,91 @@ abstract contract BaseTest is Test {
bytes4[] memory custodianReferendumAllowed = CustodianReferendumGovPermissions();
custodianReferendum = custodianReferendum == address(0) ? distReferendumDeployer.run() : custodianReferendum;
// GOV permission set to custodian referendum functions
- _setGovPermissions(custodianReferendum, custodianReferendumAllowed);
+ _setNodesCouncilPermissions(custodianReferendum, custodianReferendumAllowed);
}
-
function deployRightsAssetCustodian() public {
deployCustodianReferendum();
// set default admin as deployer..
DeployRightsAssetCustodian rightAssetCustodianDeployer = new DeployRightsAssetCustodian();
- rightAssetCustodian = rightAssetCustodian == address(0) ? rightAssetCustodianDeployer.run() : rightAssetCustodian;
+ rightAssetCustodian = rightAssetCustodian == address(0)
+ ? rightAssetCustodianDeployer.run()
+ : rightAssetCustodian;
+ }
+
+ function deployPolicyAudit() public {
+ DeployPolicyAudit policyAuditDeployer = new DeployPolicyAudit();
+ policyAudit = policyAudit == address(0) ? policyAuditDeployer.run() : policyAudit;
+ }
+
+ function deployRightsPolicyAuthorizer() public {
+ deployPolicyAudit();
+
+ DeployRightsPolicyAuthorizer rightsAuthorizerDeployer = new DeployRightsPolicyAuthorizer();
+ rightsPolicyAuthorizer = rightsPolicyAuthorizer == address(0)
+ ? rightsAuthorizerDeployer.run(policyAudit, accessManager)
+ : rightsPolicyAuthorizer;
+ }
+
+ function deployRightsPolicyManager() public {
+ deployAgreementSettler();
+ deployRightsPolicyAuthorizer();
+
+ DeployRightsPolicyManager rightsManagerDeployer = new DeployRightsPolicyManager();
+ rightsPolicyManager = rightsPolicyManager == address(0) ? rightsManagerDeployer.run() : rightsPolicyManager;
+ }
+
+ function _setContentCouncilPermissions(address target, bytes4[] memory allowed) public {
+ vm.startPrank(governor);
+ IAccessManager authority = IAccessManager(accessManager);
+ // assign permissions to VAL_ROLE for allowed functions to call in target
+ authority.setTargetFunctionRole(target, allowed, C.CONTENT_COUNCIL_ROLE);
+ vm.stopPrank();
+ }
+
+ function _setNodesCouncilPermissions(address target, bytes4[] memory allowed) public {
+ vm.startPrank(governor);
+ IAccessManager authority = IAccessManager(accessManager);
+ // assign permissions to VAL_ROLE for allowed functions to call in target
+ authority.setTargetFunctionRole(target, allowed, C.CUSTODY_COUNCIL_ROLE);
+ vm.stopPrank();
+ }
+
+ function _setOpsPermissions(address target, bytes4[] memory allowed) public {
+ vm.startPrank(governor);
+ IAccessManager authority = IAccessManager(accessManager);
+ // assign permissions to OPS_ROLE for allowed functions to call in target
+ authority.setTargetFunctionRole(target, allowed, C.OPS_ROLE);
+ vm.stopPrank();
}
function _setGovPermissions(address target, bytes4[] memory allowed) public {
- vm.startPrank(admin);
+ vm.startPrank(governor);
IAccessManager authority = IAccessManager(accessManager);
// assign permissions to GOV_ROLE for allowed functions to call in target
authority.setTargetFunctionRole(target, allowed, C.GOV_ROLE);
vm.stopPrank();
}
+ function _setSecPermissions(address target, bytes4[] memory allowed) public {
+ vm.startPrank(governor);
+ IAccessManager authority = IAccessManager(accessManager);
+ // assign permissions to ADMIN_ROLE for allowed functions to call in target
+ authority.setTargetFunctionRole(target, allowed, C.SEC_ROLE);
+ vm.stopPrank();
+ }
+
function _assignOpRole(address target) public {
vm.startPrank(admin);
IAccessManager authority = IAccessManager(accessManager);
authority.grantRole(C.OPS_ROLE, target, 0);
vm.stopPrank();
}
+
+ function _grantRole(uint64 role, address account) public {
+ vm.startPrank(admin);
+ IAccessManager authority = IAccessManager(accessManager);
+ authority.grantRole(role, account, 0);
+ vm.stopPrank();
+ }
}
diff --git a/test/assets/AssetReferendum.t.sol b/test/assets/AssetReferendum.t.sol
index d3f43fd..5631814 100644
--- a/test/assets/AssetReferendum.t.sol
+++ b/test/assets/AssetReferendum.t.sol
@@ -2,15 +2,14 @@
pragma solidity 0.8.26;
import "forge-std/Test.sol";
-import { IAccessControl } from "@openzeppelin/contracts/access/IAccessControl.sol";
-import { IAssetRegistrable } from "contracts/core/interfaces/assets/IAssetRegistrable.sol";
-import { IAssetRevokable } from "contracts/core/interfaces/assets/IAssetRevokable.sol";
-import { IAssetVerifiable } from "contracts/core/interfaces/assets/IAssetVerifiable.sol";
+import { IAssetReferendumRegistrable } from "contracts/core/interfaces/assets/IAssetReferendumRegistrable.sol";
+import { IAssetReferendumRevokable } from "contracts/core/interfaces/assets/IAssetReferendumRevokable.sol";
+import { IAssetReferendumVerifiable } from "contracts/core/interfaces/assets/IAssetReferendumVerifiable.sol";
import { AssetReferendum } from "contracts/assets/AssetReferendum.sol";
+import { IAccessManager } from "contracts/core/interfaces/access/IAccessManager.sol";
+import { C } from "contracts/core/primitives/Constants.sol";
import { BaseTest } from "test/BaseTest.t.sol";
-import { T } from "contracts/core/primitives/Types.sol";
-import { C } from "contracts/core/primitives/Constants.sol";
contract AssetReferendumTest is BaseTest {
function setUp() public initialize {
@@ -23,90 +22,171 @@ contract AssetReferendumTest is BaseTest {
vm.prank(user);
vm.expectEmit(true, true, false, true, address(assetReferendum));
emit AssetReferendum.Submitted(user, 1);
- IAssetRegistrable(assetReferendum).submit(1);
+ IAssetReferendumRegistrable(assetReferendum).submit(1);
}
function test_Submit_SubmittedValidStates() public {
_submitContentAsUser(1);
- assertFalse(IAssetVerifiable(assetReferendum).isActive(1), "Asset should not be active yet");
- assertFalse(IAssetVerifiable(assetReferendum).isApproved(user, 1), "Asset should not be approved yet");
+ IAssetReferendumVerifiable referendum = IAssetReferendumVerifiable(assetReferendum);
+ assertFalse(referendum.isActive(1), "Asset should not be active yet");
+ assertFalse(referendum.isApproved(user, 1), "Asset should not be approved yet");
+ }
+
+ function test_Submit_RevertsWhenDuplicate() public {
+ uint256 assetId = 22;
+ _submitContentAsUser(assetId);
+
+ vm.prank(user);
+ vm.expectRevert(abi.encodeWithSelector(AssetReferendum.SubmissionFailed.selector, user, assetId));
+ IAssetReferendumRegistrable(assetReferendum).submit(assetId);
}
function test_Approve_ApprovedEventEmitted() public {
uint256 assetId = 1;
_submitContentAsUser(assetId);
vm.warp(1641070805);
- vm.startPrank(governor); // approve by governance..
+ vm.startPrank(contentCouncil); // approve by council..
vm.expectEmit(true, false, false, true, address(assetReferendum));
emit AssetReferendum.Approved(assetId);
- IAssetRegistrable(assetReferendum).approve(assetId);
+ IAssetReferendumRegistrable(assetReferendum).approve(assetId);
vm.stopPrank();
}
function test_Approve_ApprovedValidStates() public {
- uint256 assetId;
+ uint256 assetId = 1;
+
_submitContentAsUser(assetId);
-
- vm.prank(governor); // approve by governance..
- IAssetRegistrable(assetReferendum).approve(assetId);
- assertTrue(IAssetVerifiable(assetReferendum).isActive(assetId), "Asset should be active");
- assertTrue(IAssetVerifiable(assetReferendum).isApproved(user, assetId), "Asset should be approved");
+
+ vm.prank(contentCouncil); // approve by council..
+ IAssetReferendumRegistrable(assetReferendum).approve(assetId);
+
+ IAssetReferendumVerifiable referendum = IAssetReferendumVerifiable(assetReferendum);
+ assertTrue(referendum.isActive(assetId), "Asset should be active");
+ assertTrue(referendum.isApproved(user, assetId), "Asset should be approved");
}
function test_Reject_RejectedEventEmitted() public {
uint256 assetId = 1;
_submitContentAsUser(assetId);
vm.warp(1641070805);
- vm.prank(governor); // approve by governance..
+ vm.prank(contentCouncil); // approve by council..
vm.expectEmit(true, false, false, true, address(assetReferendum));
emit AssetReferendum.Rejected(assetId);
- IAssetRevokable(assetReferendum).reject(assetId);
+
+ IAssetReferendumRevokable(assetReferendum).reject(assetId);
}
function test_Reject_RejectedValidStates() public {
uint256 assetId = 1;
_submitContentAsUser(assetId);
- vm.prank(governor); // approve by governance..
- IAssetRevokable(assetReferendum).reject(assetId);
- assertFalse(IAssetVerifiable(assetReferendum).isActive(assetId), "Asset should not be active");
- assertFalse(IAssetVerifiable(assetReferendum).isApproved(user, assetId), "Asset should not be approved");
+ vm.prank(contentCouncil); // approve by council..
+ IAssetReferendumRevokable(assetReferendum).reject(assetId);
+
+ IAssetReferendumVerifiable referendum = IAssetReferendumVerifiable(assetReferendum);
+ assertFalse(referendum.isActive(assetId), "Asset should not be active");
+ assertFalse(referendum.isApproved(user, assetId), "Asset should not be approved");
}
function test_Revoked_RevokedEventEmitted() public {
uint256 assetId = 1;
_submitContentAsUser(assetId);
vm.warp(1641070805);
- vm.startPrank(governor); // approve by governance..
+
+ vm.startPrank(contentCouncil); // approve by council..
// first an approval should ve done
- // then a revoke should ve done
- IAssetRegistrable(assetReferendum).approve(assetId);
+ // then a revoke
+ IAssetReferendumRegistrable(assetReferendum).approve(assetId);
vm.expectEmit(true, false, false, true, address(assetReferendum));
emit AssetReferendum.Revoked(assetId);
- IAssetRevokable(assetReferendum).revoke(assetId);
+ IAssetReferendumRevokable(assetReferendum).revoke(assetId);
vm.stopPrank(); // reject by governance..
}
function test_Revoked_RevokedValidStates() public {
uint256 assetId = 1;
_submitAndApproveContent(assetId);
- vm.prank(governor); // approve by governance..
- IAssetRevokable(assetReferendum).revoke(assetId);
- assertFalse(IAssetVerifiable(assetReferendum).isActive(assetId), "Asset should not be active");
- assertFalse(IAssetVerifiable(assetReferendum).isApproved(user, assetId), "Asset should not be approved");
+ vm.prank(contentCouncil); // approve by council..
+ IAssetReferendumRevokable(assetReferendum).revoke(assetId);
+
+ IAssetReferendumVerifiable referendum = IAssetReferendumVerifiable(assetReferendum);
+ assertFalse(referendum.isActive(assetId), "Asset should not be active");
+ assertFalse(referendum.isApproved(user, assetId), "Asset should not be approved");
+ }
+
+ function testFuzz_SubmitAndApprove(address submitter, uint256 assetId) public {
+ vm.assume(submitter != address(0));
+ vm.assume(submitter != contentCouncil);
+ assetId = bound(assetId, 1, type(uint128).max);
+
+ vm.prank(submitter);
+ IAssetReferendumRegistrable(assetReferendum).submit(assetId);
+
+ vm.prank(contentCouncil);
+ IAssetReferendumRegistrable(assetReferendum).approve(assetId);
+
+ IAssetReferendumVerifiable referendum = IAssetReferendumVerifiable(assetReferendum);
+ assertTrue(referendum.isActive(assetId), "Asset should be active after approval");
+ assertTrue(referendum.isApproved(submitter, assetId), "Submitter should have approved asset");
+ }
+
+ function testFuzz_SubmitAndReject(address submitter, uint256 assetId) public {
+ vm.assume(submitter != address(0));
+ vm.assume(submitter != contentCouncil);
+ assetId = bound(assetId, 1, type(uint128).max);
+
+ vm.prank(submitter);
+ IAssetReferendumRegistrable(assetReferendum).submit(assetId);
+
+ vm.prank(contentCouncil);
+ IAssetReferendumRevokable(assetReferendum).reject(assetId);
+
+ IAssetReferendumVerifiable referendum = IAssetReferendumVerifiable(assetReferendum);
+ assertFalse(referendum.isActive(assetId), "Rejected asset should be inactive");
+ assertFalse(referendum.isApproved(submitter, assetId), "Rejected asset should not be approved");
+ }
+
+ function testFuzz_ApproveThenRevoke(address submitter, uint256 assetId) public {
+ vm.assume(submitter != address(0));
+ vm.assume(submitter != contentCouncil);
+ assetId = bound(assetId, 1, type(uint128).max);
+
+ vm.prank(submitter);
+ IAssetReferendumRegistrable(assetReferendum).submit(assetId);
+
+ vm.prank(contentCouncil);
+ IAssetReferendumRegistrable(assetReferendum).approve(assetId);
+
+ vm.prank(contentCouncil);
+ IAssetReferendumRevokable(assetReferendum).revoke(assetId);
+
+ IAssetReferendumVerifiable referendum = IAssetReferendumVerifiable(assetReferendum);
+ assertFalse(referendum.isActive(assetId), "Revoked asset should not be active");
+ assertFalse(referendum.isApproved(submitter, assetId), "Revoked asset should not be approved");
+ }
+
+ function test_IsApproved_ReturnsTrueForVerifiedAccount() public {
+ address verified = vm.addr(77);
+ IAccessManager authority = IAccessManager(accessManager);
+
+ vm.prank(governor);
+ authority.grantRole(C.VER_ROLE, verified, 0);
+
+ IAssetReferendumVerifiable referendum = IAssetReferendumVerifiable(assetReferendum);
+ assertTrue(referendum.isApproved(verified, 999), "Verified account should bypass submission requirement");
}
function _submitAndApproveContent(uint256 assetId) internal {
_submitContentAsUser(assetId);
vm.warp(1641070805);
- vm.startPrank(governor); // approve by governance..
- IAssetRegistrable(assetReferendum).approve(assetId);
+ vm.prank(contentCouncil); // approve by council..
+ IAssetReferendumRegistrable(assetReferendum).approve(assetId);
vm.stopPrank(); // approve by governance..
}
function _submitContentAsUser(uint256 assetId) internal {
vm.prank(user); // the default user submitting content..
- IAssetRegistrable(assetReferendum).submit(assetId);
+ IAssetReferendumRegistrable(assetReferendum).submit(assetId);
}
}
diff --git a/test/assets/AssetRegistry.t.sol b/test/assets/AssetRegistry.t.sol
new file mode 100644
index 0000000..8e7211a
--- /dev/null
+++ b/test/assets/AssetRegistry.t.sol
@@ -0,0 +1,172 @@
+// SPDX-License-Identifier: BUSL-1.1
+pragma solidity 0.8.26;
+
+import "forge-std/Test.sol";
+import { AssetRegistry } from "contracts/assets/AssetRegistry.sol";
+import { IAssetRegistry } from "contracts/core/interfaces/assets/IAssetRegistry.sol";
+import { IAssetReferendumRegistrable } from "contracts/core/interfaces/assets/IAssetReferendumRegistrable.sol";
+import { IERC721StatefulVerifiable } from "contracts/core/interfaces/token/erc721/IERC721StatefulVerifiable.sol";
+import { IAccessManaged } from "@openzeppelin/contracts/access/manager/IAccessManaged.sol";
+
+import { BaseTest } from "test/BaseTest.t.sol";
+
+contract AssetRegistryTest is BaseTest {
+ function setUp() public initialize {
+ // setup the access manager to use during tests..
+ deployAssetRegistry();
+ }
+
+ function test_Register_ValidRegistration() public {
+ uint256 assetId = 1;
+
+ _submitAndApproveAsset(user, assetId);
+
+ vm.expectEmit(false, false, false, true, assetRegistry);
+ emit AssetRegistry.AssetEnabled(assetId);
+
+ vm.expectEmit(true, true, false, true, assetRegistry);
+ emit AssetRegistry.RegisteredAsset(user, assetId);
+
+ vm.prank(user);
+ IAssetRegistry(assetRegistry).register(user, assetId);
+
+ assertEq(IAssetRegistry(assetRegistry).ownerOf(assetId), user, "Invalid unexpected owner");
+ assertTrue(IERC721StatefulVerifiable(assetRegistry).isActive(assetId), "Asset must be active");
+ assertEq(AssetRegistry(assetRegistry).totalSupply(), 1, "Total supply should reflect minted asset");
+ assertEq(IAssetRegistry(assetRegistry).balanceOf(user), 1, "Owner balance should increment");
+ }
+
+ function test_Register_RevertWhen_NotApproved() public {
+ uint256 assetId = 11;
+
+ vm.expectRevert(AssetRegistry.InvalidNotApprovedAsset.selector);
+ vm.prank(user);
+ IAssetRegistry(assetRegistry).register(user, assetId);
+ }
+
+ function test_Register_RevertWhen_DuplicateAsset() public {
+ uint256 assetId = 21;
+ _submitAndApproveAsset(user, assetId);
+
+ vm.prank(user);
+ IAssetRegistry(assetRegistry).register(user, assetId);
+
+ vm.expectRevert();
+ vm.prank(user);
+ IAssetRegistry(assetRegistry).register(user, assetId);
+ }
+
+ function test_Transfer_ValidOwner() public {
+ uint256 assetId = 31;
+ address recipient = vm.addr(33);
+
+ _submitAndApproveAsset(user, assetId);
+
+ vm.prank(user);
+ IAssetRegistry(assetRegistry).register(user, assetId);
+
+ vm.expectEmit(true, true, false, true, assetRegistry);
+ emit AssetRegistry.TransferredAsset(user, recipient, assetId);
+
+ vm.prank(user);
+ IAssetRegistry(assetRegistry).transfer(recipient, assetId);
+
+ assertEq(IAssetRegistry(assetRegistry).ownerOf(assetId), recipient, "Recipient must own the asset");
+ }
+
+ function test_Transfer_RevertWhen_NotOwner() public {
+ uint256 assetId = 41;
+ address recipient = vm.addr(34);
+
+ _submitAndApproveAsset(user, assetId);
+
+ vm.prank(user);
+ IAssetRegistry(assetRegistry).register(user, assetId);
+
+ vm.expectRevert(abi.encodeWithSignature("InvalidUnauthorizedOperation(string)", "Only the asset owner can modify its state."));
+ vm.prank(recipient);
+ IAssetRegistry(assetRegistry).transfer(recipient, assetId);
+ }
+
+ function test_SwitchState_Toggles() public {
+ uint256 assetId = 51;
+ _submitAndApproveAsset(user, assetId);
+
+ vm.prank(user);
+ IAssetRegistry(assetRegistry).register(user, assetId);
+
+ vm.expectEmit(false, false, false, true, assetRegistry);
+ emit AssetRegistry.AssetDisabled(assetId);
+
+ vm.prank(user);
+ bool newState = AssetRegistry(assetRegistry).switchState(assetId);
+ assertFalse(newState, "Asset should be disabled after first toggle");
+ assertFalse(IERC721StatefulVerifiable(assetRegistry).isActive(assetId), "Asset should be inactive");
+
+ vm.expectEmit(false, false, false, true, assetRegistry);
+ emit AssetRegistry.AssetEnabled(assetId);
+
+ vm.prank(user);
+ bool backToActive = AssetRegistry(assetRegistry).switchState(assetId);
+ assertTrue(backToActive, "Asset should be active after second toggle");
+ assertTrue(IERC721StatefulVerifiable(assetRegistry).isActive(assetId), "Asset should be active again");
+ }
+
+ function test_SwitchState_RevertWhen_NotOwner() public {
+ uint256 assetId = 52;
+ address stranger = vm.addr(777);
+
+ _submitAndApproveAsset(user, assetId);
+ vm.prank(user);
+ IAssetRegistry(assetRegistry).register(user, assetId);
+
+ vm.expectRevert(
+ abi.encodeWithSignature(
+ "InvalidUnauthorizedOperation(string)",
+ "Only the asset owner can modify its state."
+ )
+ );
+ vm.prank(stranger);
+ AssetRegistry(assetRegistry).switchState(assetId);
+ }
+
+ function test_Revoke_DisablesAndBurns() public {
+ uint256 assetId = 61;
+ _submitAndApproveAsset(user, assetId);
+
+ vm.prank(user);
+ IAssetRegistry(assetRegistry).register(user, assetId);
+
+ vm.expectEmit(true, true, false, true, assetRegistry);
+ emit AssetRegistry.RevokedAsset(user, assetId);
+
+ vm.prank(governor);
+ IAssetRegistry(assetRegistry).revoke(assetId);
+
+ assertFalse(IERC721StatefulVerifiable(assetRegistry).isActive(assetId), "Revoked asset should be inactive");
+ assertEq(AssetRegistry(assetRegistry).totalSupply(), 0, "Total supply should decrease after burn");
+
+ vm.expectRevert();
+ IAssetRegistry(assetRegistry).ownerOf(assetId);
+ }
+
+ function test_Revoke_RevertWhen_NotAdmin() public {
+ uint256 assetId = 71;
+ _submitAndApproveAsset(user, assetId);
+
+ vm.prank(user);
+ IAssetRegistry(assetRegistry).register(user, assetId);
+
+ vm.expectRevert(abi.encodeWithSelector(IAccessManaged.AccessManagedUnauthorized.selector, user));
+ vm.prank(user);
+ AssetRegistry(assetRegistry).revoke(assetId);
+ }
+
+ function _submitAndApproveAsset(address submitter, uint256 assetId) private {
+ vm.prank(submitter);
+ IAssetReferendumRegistrable(assetReferendum).submit(assetId);
+
+ vm.prank(contentCouncil);
+ IAssetReferendumRegistrable(assetReferendum).approve(assetId);
+ }
+}
diff --git a/test/assets/AssetSafe.t.sol b/test/assets/AssetSafe.t.sol
index f94cb2a..9c7d0b4 100644
--- a/test/assets/AssetSafe.t.sol
+++ b/test/assets/AssetSafe.t.sol
@@ -2,24 +2,18 @@
pragma solidity 0.8.26;
import "forge-std/Test.sol";
-import { IAccessControl } from "@openzeppelin/contracts/access/IAccessControl.sol";
-import { IAssetRegistrable } from "contracts/core/interfaces/assets/IAssetRegistrable.sol";
-import { IAssetVerifiable } from "contracts/core/interfaces/assets/IAssetVerifiable.sol";
-import { IAssetOwnership } from "contracts/core/interfaces/assets/IAssetOwnership.sol";
+import { IAssetReferendumRegistrable } from "contracts/core/interfaces/assets/IAssetReferendumRegistrable.sol";
+import { IAssetRegistry } from "contracts/core/interfaces/assets/IAssetRegistry.sol";
import { IAssetSafe } from "contracts/core/interfaces/assets/IAssetSafe.sol";
import { AssetSafe } from "contracts/assets/AssetSafe.sol";
import { BaseTest } from "test/BaseTest.t.sol";
import { T } from "contracts/core/primitives/Types.sol";
-import { C } from "contracts/core/primitives/Constants.sol";
contract AssetSafeTest is BaseTest {
-
-
function setUp() public initialize {
// setup the access manager to use during tests..
deployAssetSafe();
-
}
function test_SetContent_ValidOwner() public {
@@ -28,9 +22,9 @@ contract AssetSafeTest is BaseTest {
_registerAndApproveAsset(user, assetId);
vm.prank(user);
- IAssetSafe assetSafe = IAssetSafe(assetSafe);
- assetSafe.setContent(assetId, T.Cipher.LIT, "");
- assertEq(assetSafe.getContent(assetId, T.Cipher.LIT), "", "Content should be set to empty string");
+ IAssetSafe safe = IAssetSafe(assetSafe);
+ safe.setContent(assetId, T.Cipher.LIT, "");
+ assertEq(safe.getContent(assetId, T.Cipher.LIT), "", "Content should be set to empty string");
}
function test_SetContent_RevertIf_InvalidOwner() public {
@@ -40,8 +34,8 @@ contract AssetSafeTest is BaseTest {
vm.prank(user);
vm.expectRevert(abi.encodeWithSignature("InvalidAssetRightsHolder()"));
- IAssetSafe assetSafe = IAssetSafe(assetSafe);
- assetSafe.setContent(assetId, T.Cipher.LIT, "");
+ IAssetSafe safe = IAssetSafe(assetSafe);
+ safe.setContent(assetId, T.Cipher.LIT, "");
}
function test_SetContent_ContentEventEmitted() public {
@@ -51,8 +45,29 @@ contract AssetSafeTest is BaseTest {
vm.prank(user);
vm.expectEmit(true, true, false, true, address(assetSafe));
emit AssetSafe.ContentStored(assetId, user, T.Cipher.LIT);
- IAssetSafe assetSafe = IAssetSafe(assetSafe);
- assetSafe.setContent(assetId, T.Cipher.LIT, "");
+ IAssetSafe safe = IAssetSafe(assetSafe);
+ safe.setContent(assetId, T.Cipher.LIT, "");
+ }
+
+ function test_SetContent_MultipleCiphersUpdatesTypeOnly() public {
+ uint256 assetId = 222;
+ _registerAndApproveAsset(user, assetId);
+ IAssetSafe safe = IAssetSafe(assetSafe);
+
+ bytes memory litData = bytes("first");
+ vm.prank(user);
+ safe.setContent(assetId, T.Cipher.LIT, litData);
+
+ bytes memory ecData = bytes("second");
+ vm.prank(user);
+ safe.setContent(assetId, T.Cipher.EC, ecData);
+
+ // Latest cipher should be EC
+ assertEq(uint8(safe.getType(assetId)), uint8(T.Cipher.EC), "Cipher type should reflect last write");
+ // New cipher data stored
+ assertEq(keccak256(safe.getContent(assetId, T.Cipher.EC)), keccak256(ecData), "EC payload mismatch");
+ // Previous cipher data remains accessible
+ assertEq(keccak256(safe.getContent(assetId, T.Cipher.LIT)), keccak256(litData), "LIT payload should remain");
}
function test_SetContent_ValidScheme() public {
@@ -61,10 +76,10 @@ contract AssetSafeTest is BaseTest {
_registerAndApproveAsset(user, assetId);
vm.prank(user);
- IAssetSafe assetSafe = IAssetSafe(assetSafe);
- assetSafe.setContent(assetId, T.Cipher.LIT, "");
+ IAssetSafe safe = IAssetSafe(assetSafe);
+ safe.setContent(assetId, T.Cipher.LIT, "");
- T.Cipher safeType = assetSafe.getType(assetId);
+ T.Cipher safeType = safe.getType(assetId);
assertEq(uint256(safeType), uint256(T.Cipher.LIT), "Safe type should be LIT");
}
@@ -85,22 +100,70 @@ contract AssetSafeTest is BaseTest {
bytes memory data = abi.encode(b64);
vm.prank(user);
- IAssetSafe assetSafe = IAssetSafe(assetSafe);
- assetSafe.setContent(assetId, T.Cipher.LIT, data);
+ IAssetSafe safe = IAssetSafe(assetSafe);
+ safe.setContent(assetId, T.Cipher.LIT, data);
vm.prank(admin);
- bytes memory got = assetSafe.getContent(assetId, T.Cipher.LIT);
+ bytes memory got = safe.getContent(assetId, T.Cipher.LIT);
string memory expected = abi.decode(got, (string));
assertEq(keccak256(abi.encodePacked(expected)), keccak256(abi.encodePacked(b64)), "Content should match the expected data");
}
+ function test_GetContent_ReturnsEmptyWhenNotSet() public view {
+ IAssetSafe safe = IAssetSafe(assetSafe);
+ bytes memory raw = safe.getContent(98765, T.Cipher.LIT);
+ assertEq(raw.length, 0, "Unset content should return empty bytes");
+ }
+
+ function testFuzz_SetContent_StoresData(
+ address owner,
+ uint256 assetId,
+ uint8 cipherSeed,
+ uint256 dataSeed
+ ) public {
+ vm.assume(owner != address(0));
+ assetId = bound(assetId, 1, type(uint128).max);
+
+ _registerAndApproveAsset(owner, assetId);
+
+ T.Cipher cipher = T.Cipher(uint8(bound(cipherSeed, 1, uint8(T.Cipher.EC))));
+ bytes memory data = abi.encode(dataSeed);
+
+ IAssetSafe safeContract = IAssetSafe(assetSafe);
+
+ vm.prank(owner);
+ safeContract.setContent(assetId, cipher, data);
+
+ assertEq(uint8(safeContract.getType(assetId)), uint8(cipher), "Cipher type should match stored value");
+ bytes memory stored = safeContract.getContent(assetId, cipher);
+ assertEq(keccak256(stored), keccak256(data), "Stored content should match provided data");
+ }
+
+ function testFuzz_SetContent_RevertsForNonOwner(
+ address owner,
+ address attacker,
+ uint256 assetId
+ ) public {
+ vm.assume(owner != address(0));
+ vm.assume(attacker != address(0));
+ vm.assume(attacker != owner);
+ assetId = bound(assetId, 1, type(uint128).max);
+
+ _registerAndApproveAsset(owner, assetId);
+
+ vm.expectRevert(AssetSafe.InvalidAssetRightsHolder.selector);
+ vm.prank(attacker);
+ IAssetSafe(assetSafe).setContent(assetId, T.Cipher.LIT, "");
+ }
+
function _registerAndApproveAsset(address to, uint256 assetId) private {
vm.prank(to);
- IAssetRegistrable(assetReferendum).submit(assetId);
- vm.prank(governor);
- IAssetRegistrable(assetReferendum).approve(assetId);
+ IAssetReferendumRegistrable(assetReferendum).submit(assetId);
+
+ vm.prank(contentCouncil);
+ IAssetReferendumRegistrable(assetReferendum).approve(assetId);
vm.prank(to);
- IAssetOwnership(assetOwnership).register(to, assetId);
+ IAssetRegistry(assetRegistry).register(to, assetId);
}
}
diff --git a/test/custody/CustodianImpl.t.sol b/test/custody/CustodianImpl.t.sol
deleted file mode 100644
index eef3595..0000000
--- a/test/custody/CustodianImpl.t.sol
+++ /dev/null
@@ -1,119 +0,0 @@
-// SPDX-License-Identifier: BUSL-1.1
-pragma solidity 0.8.26;
-
-import "forge-std/Test.sol";
-import { IERC165 } from "@openzeppelin/contracts/interfaces/IERC165.sol";
-import { IERC20 } from "@openzeppelin/contracts/interfaces/IERC20.sol";
-import { ICustodian } from "contracts/core/interfaces/custody/ICustodian.sol";
-import { IBalanceVerifiable } from "contracts/core/interfaces/base/IBalanceVerifiable.sol";
-import { IBalanceWithdrawable } from "contracts/core/interfaces/base/IBalanceWithdrawable.sol";
-import { ICustodianFactory } from "contracts/core/interfaces/custody/ICustodianFactory.sol";
-import { BaseTest } from "test/BaseTest.t.sol";
-
-contract CustodianImplTest is BaseTest {
- function setUp() public initialize {
- deployToken();
- deployCustodianFactory();
- }
-
- function deployCustodian(string memory endpoint) public returns (address) {
- vm.prank(admin);
- ICustodianFactory factory = ICustodianFactory(custodianFactory);
- return factory.create(endpoint);
- }
-
- function test_Create_ValidCustodian() public {
- address custodian = deployCustodian("test.com");
- bool supportedInterface = IERC165(custodian).supportsInterface(type(ICustodian).interfaceId);
- assertEq(supportedInterface, true, "Custodian should support ICustodian interface");
- }
-
- function test_GetOwner_ExpectedDeployer() public {
- address custodian = deployCustodian("test2.com");
- assertEq(ICustodian(custodian).getManager(), admin, "Expected owner should be the deployer");
- }
-
- function test_GetEndpoint_ExpectedEndpoint() public {
- address custodian = deployCustodian("test3.com");
- assertEq(ICustodian(custodian).getEndpoint(), "test3.com", "Expected endpoint should match");
- }
-
- function test_SetEndpoint_ValidEndpoint() public {
- // created with an initial endpoint
- address custodian = deployCustodian("1.1.1.1");
- // changed to a dns domain
- vm.prank(admin); // only owner can do this
- ICustodian(custodian).setEndpoint("mynew.com");
- string memory endpoint = ICustodian(custodian).getEndpoint();
- assertEq(endpoint, "mynew.com", "Expected endpoint should be updated");
- }
-
- function test_SetEndpoint_RevertWhen_InvalidOwner() public {
- // created with an initial endpoint
- address custodian = deployCustodian("1.1.1.1");
- vm.prank(user);
- vm.expectRevert(abi.encodeWithSignature("OwnableUnauthorizedAccount(address)", user));
- ICustodian(custodian).setEndpoint("mynew.com");
- }
-
- function test_GetBalance_ValidBalance() public {
- // created with an initial endpoint
- address custodian = deployCustodian("1.1.1.1");
- uint256 expected = 100 * 1e18;
- // admin acting as reward system to transfer funds
- // here the expected is that rewards system do it.
- vm.startPrank(admin); // only owner can get balance by default deployer
- IERC20(token).transfer(custodian, expected);
- uint256 currentBalance = IBalanceVerifiable(custodian).getBalance(token);
- assertEq(currentBalance, expected, "Expected balance should match");
- vm.stopPrank();
- }
-
- function test_Withdraw_ValidFundsWithdrawn() public {
- // created with an initial endpoint
- uint256 expected = 100 * 1e18;
- address custodian = deployCustodian("1.1.1.1");
-
- vm.startPrank(admin); // only owner can get balance by default deployer
- IERC20(token).transfer(custodian, expected);
- // only owner can withdraw funds by default deployer
- IBalanceWithdrawable(custodian).withdraw(user, expected, token);
- vm.stopPrank();
-
- uint256 userBalance = IERC20(token).balanceOf(user);
- assertEq(userBalance, expected, "User should receive the withdrawn funds");
- }
-
- function test_Withdraw_EmitFundsWithdrawn() public {
- // created with an initial endpoint
- uint256 expected = 100 * 1e18;
- address custodian = deployCustodian("1.1.1.1");
-
- vm.startPrank(admin); // only owner can get balance by default deployer
- IERC20(token).transfer(custodian, expected);
- // only owner can withdraw funds by default deployer
- vm.expectEmit(true, true, false, true, address(custodian));
- emit IBalanceWithdrawable.FundsWithdrawn(user, admin, expected, token);
- IBalanceWithdrawable(custodian).withdraw(user, expected, token);
- }
-
- function test_Withdraw_RevertWhen_NoBalance() public {
- // created with an initial endpoint
- uint256 expected = 100 * 1e18;
- address custodian = deployCustodian("1.1.1.1");
-
- vm.startPrank(admin); // only owner can get balance by default deployer
- vm.expectRevert(abi.encodeWithSignature("NoFundsToWithdraw()"));
- IBalanceWithdrawable(custodian).withdraw(user, expected, token);
- }
-
- function test_Withdraw_RevertWhen_InvalidOwner() public {
- // created with an initial endpoint
- uint256 expected = 100 * 1e18;
- address custodian = deployCustodian("1.1.1.1");
-
- vm.prank(user);
- vm.expectRevert(abi.encodeWithSignature("OwnableUnauthorizedAccount(address)", user));
- IBalanceWithdrawable(custodian).withdraw(user, expected, token);
- }
-}
diff --git a/test/custody/CustodianReferendum.t.sol b/test/custody/CustodianReferendum.t.sol
deleted file mode 100644
index 3509297..0000000
--- a/test/custody/CustodianReferendum.t.sol
+++ /dev/null
@@ -1,179 +0,0 @@
-// SPDX-License-Identifier: BUSL-1.1
-pragma solidity 0.8.26;
-
-import "forge-std/Test.sol";
-
-
-import { ICustodianVerifiable } from "contracts/core/interfaces/custody/ICustodianVerifiable.sol";
-import { ICustodianExpirable } from "contracts/core/interfaces/custody/ICustodianExpirable.sol";
-import { ICustodianRegistrable } from "contracts/core/interfaces/custody/ICustodianRegistrable.sol";
-import { ICustodianInspectable } from "contracts/core/interfaces/custody/ICustodianInspectable.sol";
-import { ICustodianRevokable } from "contracts/core/interfaces/custody/ICustodianRevokable.sol";
-import { ICustodianFactory } from "contracts/core/interfaces/custody/ICustodianFactory.sol";
-
-import { CustodianShared } from "test/shared/CustodianShared.t.sol";
-import { CustodianReferendum } from "contracts/custody/CustodianReferendum.sol";
-import { CustodianImpl } from "contracts/custody/CustodianImpl.sol";
-
-
-contract CustodianReferendumTest is CustodianShared {
- /// ----------------------------------------------------------------
-
- function test_Init_ExpirationPeriod() public view {
- // test initialized treasury address
- uint256 expected = 180 days;
- uint256 period = ICustodianExpirable(custodianReferendum).getExpirationPeriod();
- assertEq(period, expected, "Expected expiration period should be 180 days");
- }
-
- function test_SetExpirationPeriod_ValidExpiration() public {
- uint256 expireIn = 3600; // seconds
- vm.prank(governor);
- ICustodianExpirable(custodianReferendum).setExpirationPeriod(expireIn);
- uint256 currentExpiration = ICustodianExpirable(custodianReferendum).getExpirationPeriod();
- assertEq(currentExpiration, expireIn, "Expected expiration period should match");
- }
-
- function test_SetExpirationPeriod_EmitPeriodSet() public {
- uint256 expireIn = 3600; // seconds
- vm.prank(governor);
- vm.expectEmit(true, false, false, true, address(custodianReferendum));
- emit CustodianReferendum.PeriodSet(expireIn);
- ICustodianExpirable(custodianReferendum).setExpirationPeriod(expireIn);
- }
-
- function test_SetExpirationPeriod_RevertWhen_Unauthorized() public {
- vm.expectRevert();
- ICustodianExpirable(custodianReferendum).setExpirationPeriod(10);
- }
-
- function test_Register_RegisteredEventEmitted() public {
- uint256 expectedFees = 100 * 1e18;
- address custodian = deployCustodian("contentrider.com");
- _setFeesAsGovernor(expectedFees); // free enrollment: test purpose
- // after register a custodian a Registered event is expected
- vm.warp(1641070803);
- vm.startPrank(admin);
- // approve fees payment: admin default account
- address[] memory parties = new address[](1);
- parties[0] = custodian;
- uint256 proof = _createAgreement(expectedFees, parties);
-
- vm.expectEmit(true, false, false, true, address(custodianReferendum));
- emit CustodianReferendum.Registered(custodian, expectedFees);
- ICustodianRegistrable(custodianReferendum).register(proof, custodian);
- vm.stopPrank();
- }
-
- function test_Register_RevertIf_InvalidAgreement() public {
- uint256 expectedFees = 100 * 1e18; // 100 MMC
- address custodian = deployCustodian("contentrider.com");
- _setFeesAsGovernor(expectedFees);
- // expected revert if not valid allowance
- vm.prank(user);
- vm.expectRevert(abi.encodeWithSignature("UnauthorizedEscrowAgent()"));
- ICustodianRegistrable(custodianReferendum).register(0, custodian);
- }
-
- function test_Register_SetValidEnrollmentTime() public {
- address custodian = deployCustodian("contentrider.com");
- ICustodianInspectable inspectable = ICustodianInspectable(custodianReferendum);
- ICustodianExpirable expirable = ICustodianExpirable(custodianReferendum);
-
- _setFeesAsGovernor(1 * 1e18);
- uint256 expectedExpiration = expirable.getExpirationPeriod();
- uint256 currentTime = 1727976358;
- vm.warp(currentTime); // set block.time to current time
-
- // register the custodian expecting the right enrollment time..
- _registerCustodianWithApproval(custodian, 1 * 1e18);
- uint256 expected = currentTime + expectedExpiration;
- uint256 got = inspectable.getEnrollmentDeadline(custodian);
- assertEq(got, expected, "Expected enrollment deadline should match");
- }
-
- function test_Register_SetWaitingState() public {
- _setFeesAsGovernor(1 * 1e18);
- address custodian = deployCustodian("contentrider.com");
- // register the custodian expecting the right status.
- _registerCustodianWithApproval(custodian, 1 * 1e18);
- bool isWaiting = ICustodianVerifiable(custodianReferendum).isWaiting(custodian);
- assertTrue(isWaiting, "Custodian should be in waiting state");
- }
-
- function test_Register_RevertIf_InvalidCustodian() public {
- vm.prank(user);
- address custodian = address(new CustodianImpl());
-
- vm.prank(admin); //
- vm.expectRevert(abi.encodeWithSignature("UnregisteredCustodian(address)", admin));
- ICustodianRegistrable(custodianReferendum).register(0, custodian);
- }
-
- function test_Approve_ApprovedEventEmitted() public {
- _setFeesAsGovernor(1 * 1e18);
- address custodian = deployCustodian("contentrider.com");
- _registerCustodianWithApproval(custodian, 1 * 1e18);
-
- vm.prank(governor); // as governor.
- vm.warp(1641070802);
- // after register a custodian a Registered event is expected
- vm.expectEmit(true, false, false, true, address(custodianReferendum));
- emit CustodianReferendum.Approved(custodian);
- ICustodianRegistrable(custodianReferendum).approve(custodian);
- }
-
- function test_Approve_SetActiveState() public {
- address custodian = deployCustodian("contentrider.com");
- _registerAndApproveCustodian(custodian);
- bool isActive = ICustodianVerifiable(custodianReferendum).isActive(custodian);
- assertTrue(isActive, "Custodian should be active after approval");
- }
-
- function test_Approve_IncrementEnrollmentCount() public {
- address custodian = deployCustodian("contentrider.com");
- address custodian2 = deployCustodian("test2.com");
- address custodian3 = deployCustodian("test3.com");
-
- _registerAndApproveCustodian(custodian);
- _registerAndApproveCustodian(custodian2); // still governor prank
- _registerAndApproveCustodian(custodian3); // still governor prank
-
- // valid approvals, increments the total of enrollments
- uint256 enrollment = ICustodianInspectable(custodianReferendum).getEnrollmentCount();
- assertEq(enrollment, 3, "Enrollment count should be 3");
- }
-
- function test_Revoke_RevokedEventEmitted() public {
- address custodian = deployCustodian("contentrider.com");
- _registerAndApproveCustodian(custodian); // still governor prank
- vm.prank(governor);
- vm.warp(1641070801);
- // after register a custodian a Registered event is expected
- vm.expectEmit(true, false, false, true, address(custodianReferendum));
- emit CustodianReferendum.Revoked(custodian);
- ICustodianRevokable(custodianReferendum).revoke(custodian);
- }
-
- function test_Revoke_DecrementEnrollmentCount() public {
- address custodian = deployCustodian("contentrider.com");
- _registerAndApproveCustodian(custodian); // still governor prank
- // valid approvals, increments the total of enrollments
- vm.prank(governor);
- ICustodianRevokable(custodianReferendum).revoke(custodian);
- uint256 enrollment = ICustodianInspectable(custodianReferendum).getEnrollmentCount();
- assertEq(enrollment, 0, "Enrollment count should be 0 after revocation");
- }
-
- function test_Revoke_SetBlockedState() public {
- address custodian = deployCustodian("contentrider.com");
- _registerAndApproveCustodian(custodian); // still governor prank
- // custodian get revoked by governance..
- vm.prank(governor);
- ICustodianRevokable(custodianReferendum).revoke(custodian);
- bool isBlocked = ICustodianVerifiable(custodianReferendum).isBlocked(custodian);
- assertTrue(isBlocked, "Custodian should be blocked after revocation");
- }
-
-
-}
diff --git a/test/economics/Tollgate.t.sol b/test/economics/Tollgate.t.sol
index cccc29d..b96b41a 100644
--- a/test/economics/Tollgate.t.sol
+++ b/test/economics/Tollgate.t.sol
@@ -1,10 +1,12 @@
// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.26;
+import "forge-std/Test.sol";
import { BaseTest } from "test/BaseTest.t.sol";
import { Tollgate } from "contracts/economics/Tollgate.sol";
import { ITollgate } from "contracts/core/interfaces/economics/ITollgate.sol";
import { T } from "contracts/core/primitives/Types.sol";
+import { IAccessManaged } from "@openzeppelin/contracts/access/manager/IAccessManaged.sol";
contract TargetA {}
@@ -75,6 +77,19 @@ contract TollgateTest is BaseTest {
ITollgate(tollgate).setFees(T.Scheme.NOMINAL, target, invalidFees, token);
}
+ function test_SetFees_RevertWhen_UnauthorizedCaller() public {
+ address target = address(new TargetA());
+ vm.expectRevert(abi.encodeWithSelector(IAccessManaged.AccessManagedUnauthorized.selector, user));
+ vm.prank(user);
+ ITollgate(tollgate).setFees(T.Scheme.FLAT, target, 1, token);
+ }
+
+ function test_SetFees_RevertWhen_TargetZero() public {
+ vm.prank(governor);
+ vm.expectRevert(abi.encodeWithSignature("InvalidTargetScheme(address)", address(0)));
+ ITollgate(tollgate).setFees(T.Scheme.FLAT, address(0), 1, token);
+ }
+
function test_SetFees_RevertIf_NotSupportedSchemeByTarget() public {
vm.startPrank(governor);
// expected revert if not valid allowance
@@ -110,12 +125,6 @@ contract TollgateTest is BaseTest {
assertEq(uint256(c), 3, "Expected scheme should be BPS");
}
- function test_GetFees_RevertWhen_NotSupportedScheme() public {
- address invalidTokenAddress = vm.addr(3);
- address target = vm.addr(8);
- vm.expectRevert(abi.encodeWithSignature("UnsupportedCurrency(address,address)", target, invalidTokenAddress));
- ITollgate(tollgate).getFees(target, invalidTokenAddress);
- }
function test_SupportedCurrencies_ReturnExpectedCurrencies() public {
address target = custodianReferendum;
diff --git a/test/finance/AgreementManager.t.sol b/test/finance/AgreementManager.t.sol
new file mode 100644
index 0000000..37d1541
--- /dev/null
+++ b/test/finance/AgreementManager.t.sol
@@ -0,0 +1,206 @@
+// SPDX-License-Identifier: BUSL-1.1
+pragma solidity 0.8.26;
+
+import "forge-std/Test.sol";
+
+import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
+import { ITollgate } from "contracts/core/interfaces/economics/ITollgate.sol";
+import { ILedgerVault } from "contracts/core/interfaces/financial/ILedgerVault.sol";
+import { ILedgerVerifiable } from "@synaps3/core/interfaces/base/ILedgerVerifiable.sol";
+import { IAgreementManager } from "contracts/core/interfaces/financial/IAgreementManager.sol";
+import { AgreementManager } from "contracts/financial/AgreementManager.sol";
+import { BaseTest } from "test/BaseTest.t.sol";
+import { T } from "contracts/core/primitives/Types.sol";
+import { C } from "contracts/core/primitives/Constants.sol";
+
+interface IAgreementManagerExtended is IAgreementManager {
+ function maxParties() external view returns (uint256);
+ function setMaxParties(uint256 newMax) external;
+}
+
+contract AgreementManagerMockArbiter {}
+
+contract AgreementManagerTest is BaseTest {
+ uint256 internal constant INITIAL_DEPOSIT = 1_000 * 1e18;
+ address internal arbiter;
+
+ function setUp() public initialize {
+ deployAgreementManager();
+ arbiter = address(new AgreementManagerMockArbiter());
+
+ vm.startPrank(governor);
+ ITollgate(tollgate).setFees(T.Scheme.BPS, arbiter, 500, token);
+ IERC20(token).approve(ledger, INITIAL_DEPOSIT);
+ ILedgerVault(ledger).deposit(user, INITIAL_DEPOSIT, token);
+ vm.stopPrank();
+ }
+
+ function _buildParties(uint256 len) private view returns (address[] memory parties) {
+ parties = new address[](len);
+ for (uint256 i = 0; i < len; i++) {
+ parties[i] = vm.addr(100 + i);
+ }
+ }
+
+ function _penaltyBps(uint256 partiesLen, uint256 maxParties) private pure returns (uint256) {
+ if (partiesLen <= maxParties) return 0;
+ uint256 excess = partiesLen - maxParties;
+ return ((excess * (excess + 1)) / 2) * 100;
+ }
+
+ function test_SetMaxParties_UpdatesValue() public {
+ vm.prank(admin);
+ IAgreementManagerExtended(agreementManager).setMaxParties(8);
+ assertEq(IAgreementManagerExtended(agreementManager).maxParties(), 8, "maxParties should update");
+ }
+
+ function test_SetMaxParties_RevertWhen_Zero() public {
+ vm.prank(admin);
+ vm.expectRevert(abi.encodeWithSelector(AgreementManager.InvalidMaxParties.selector, 0));
+ IAgreementManagerExtended(agreementManager).setMaxParties(0);
+ }
+
+ function test_SetMaxParties_RevertWhen_NotAdmin() public {
+ vm.prank(user);
+ vm.expectRevert(abi.encodeWithSignature("InvalidUnauthorizedOperation(string)", "Only admin can perform this action."));
+ IAgreementManagerExtended(agreementManager).setMaxParties(6);
+ }
+
+ function test_CreateAgreement_StoresAgreement() public {
+ uint256 amount = 50 * 1e18;
+ address[] memory parties = _buildParties(2);
+ bytes memory payload = abi.encode("agreement");
+
+ vm.expectEmit(true, false, false, true, agreementManager);
+ emit AgreementManager.AgreementCreated(user, 0, amount, token);
+
+ vm.prank(user);
+ uint256 proof = IAgreementManager(agreementManager).createAgreement(amount, token, arbiter, parties, payload);
+
+ T.Agreement memory stored = IAgreementManager(agreementManager).getAgreement(proof);
+ assertEq(stored.total, amount, "Total mismatch");
+ assertEq(stored.initiator, user, "Initiator mismatch");
+ assertEq(stored.arbiter, arbiter, "Arbiter mismatch");
+ assertEq(stored.parties, parties, "Parties mismatch");
+ assertEq(stored.payload, payload, "Payload mismatch");
+
+ uint256 userLedger = ILedgerVerifiable(ledger).getLedgerBalance(user, token);
+ assertEq(userLedger, INITIAL_DEPOSIT - stored.locked, "Ledger should reflect locked funds");
+ }
+
+ function test_CreateAgreement_RevertWhen_UnsupportedCurrency() public {
+ address unsupportedArbiter = address(new AgreementManagerMockArbiter());
+ address[] memory parties;
+
+ vm.prank(user);
+ vm.expectRevert(abi.encodeWithSelector(AgreementManager.UnsupportedAgreementTarget.selector, unsupportedArbiter, token));
+ IAgreementManager(agreementManager).createAgreement(1e18, token, unsupportedArbiter, parties, "");
+ }
+
+ function test_CreateAgreement_RevertWhen_FlatFeeExceedsTotal() public {
+ vm.prank(governor);
+ ITollgate(tollgate).setFees(T.Scheme.FLAT, arbiter, 20 * 1e18, token);
+
+ address[] memory parties = _buildParties(1);
+ vm.prank(user);
+ vm.expectRevert(abi.encodeWithSelector(AgreementManager.FlatFeeExceedsTotal.selector, 10 * 1e18, 20 * 1e18));
+ IAgreementManager(agreementManager).createAgreement(10 * 1e18, token, arbiter, parties, "");
+ }
+
+ function test_CreateAgreement_RevertWhen_InsufficientBalance() public {
+ address[] memory parties;
+ uint256 amount = INITIAL_DEPOSIT + 1;
+
+ vm.prank(user);
+ vm.expectRevert(bytes4(keccak256("NoFundsToLock()")));
+ IAgreementManager(agreementManager).createAgreement(amount, token, arbiter, parties, "");
+ }
+
+ function test_CreateAgreement_ComputesFeesForBpsScheme() public {
+ vm.prank(governor);
+ ITollgate(tollgate).setFees(T.Scheme.BPS, arbiter, 500, token);
+
+ uint256 amount = 200 * 1e18;
+ address[] memory parties = _buildParties(1);
+
+ vm.prank(user);
+ uint256 proof = IAgreementManager(agreementManager).createAgreement(amount, token, arbiter, parties, "");
+
+ T.Agreement memory stored = IAgreementManager(agreementManager).getAgreement(proof);
+ uint256 expectedFee = (amount * 500) / C.BPS_MAX; // 500 bps configured in setUp
+ assertEq(stored.fees, expectedFee, "BPS fee mismatch");
+ }
+
+ function test_CreateAgreement_ComputesFeesForNominalScheme() public {
+ vm.prank(governor);
+ ITollgate(tollgate).setFees(T.Scheme.NOMINAL, arbiter, 25, token); // 25% nominal
+
+ uint256 amount = 80 * 1e18;
+ address[] memory parties = _buildParties(2);
+
+ vm.prank(user);
+ uint256 proof = IAgreementManager(agreementManager).createAgreement(amount, token, arbiter, parties, "");
+
+ T.Agreement memory stored = IAgreementManager(agreementManager).getAgreement(proof);
+ uint256 expectedFee = (amount * 25 * 100) / C.BPS_MAX; // nominal converted to bps internally
+ assertEq(stored.fees, expectedFee, "Nominal fee mismatch");
+
+ vm.prank(governor);
+ ITollgate(tollgate).setFees(T.Scheme.BPS, arbiter, 500, token);
+ }
+
+ function test_CreateAgreement_RevertWhen_ExceedsMaxPartiesPenaltyCap() public {
+ uint256 maxParties = IAgreementManagerExtended(agreementManager).maxParties();
+ uint256 partiesLen = maxParties + 15; // ensures penalty bps > 10_000
+ address[] memory parties = _buildParties(partiesLen);
+
+ vm.prank(user);
+ vm.expectRevert(AgreementManager.ExceedsMaxParties.selector);
+ IAgreementManager(agreementManager).createAgreement(10 * 1e18, token, arbiter, parties, "");
+ }
+
+ function test_PreviewAgreement_NoPenalizationWhenWithinLimit() public {
+ uint256 amount = 60 * 1e18;
+ uint256 maxParties = IAgreementManagerExtended(agreementManager).maxParties();
+ address[] memory parties = _buildParties(maxParties);
+
+ vm.prank(user);
+ T.Agreement memory preview = IAgreementManager(agreementManager).previewAgreement(amount, token, arbiter, parties, "");
+
+ assertEq(preview.locked, amount, "Locked amount should equal total when within limit");
+ }
+
+ function test_CreateAgreement_WithPenalizationLocksAmount() public {
+ uint256 partiesLen = IAgreementManagerExtended(agreementManager).maxParties() + 2;
+ address[] memory parties = _buildParties(partiesLen);
+ uint256 amount = 100 * 1e18;
+
+ vm.prank(user);
+ uint256 proof = IAgreementManager(agreementManager).createAgreement(amount, token, arbiter, parties, "");
+
+ T.Agreement memory stored = IAgreementManager(agreementManager).getAgreement(proof);
+ uint256 penaltyBps = _penaltyBps(partiesLen, IAgreementManagerExtended(agreementManager).maxParties());
+ uint256 expectedLocked = amount + ((amount * penaltyBps) / C.BPS_MAX);
+
+ assertEq(stored.locked, expectedLocked, "Locked amount mismatch");
+
+ uint256 userLedger = ILedgerVerifiable(ledger).getLedgerBalance(user, token);
+ assertEq(userLedger, INITIAL_DEPOSIT - stored.locked, "Ledger balance should reflect locked amount");
+ }
+
+ function test_PreviewAgreement_Penalization() public {
+ uint256 partiesLen = 9;
+ address[] memory parties = _buildParties(partiesLen);
+ uint256 amount = 100 * 1e18;
+
+ vm.prank(user);
+ T.Agreement memory agreement = IAgreementManager(agreementManager).previewAgreement(amount, token, arbiter, parties, "");
+
+ uint256 exceed = partiesLen - IAgreementManagerExtended(agreementManager).maxParties();
+ uint256 expectedPercentage = (exceed * (exceed + 1)) / 2;
+ uint256 expectedPenalization = (amount * expectedPercentage * 100) / C.BPS_MAX;
+
+ assertEq(agreement.locked, amount + expectedPenalization, "Penalization mismatch");
+ }
+
+}
diff --git a/test/finance/AgreementSettler.t.sol b/test/finance/AgreementSettler.t.sol
new file mode 100644
index 0000000..0b43061
--- /dev/null
+++ b/test/finance/AgreementSettler.t.sol
@@ -0,0 +1,170 @@
+// SPDX-License-Identifier: BUSL-1.1
+pragma solidity 0.8.26;
+
+import "forge-std/Test.sol";
+
+import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
+import { ITollgate } from "contracts/core/interfaces/economics/ITollgate.sol";
+import { ILedgerVault } from "contracts/core/interfaces/financial/ILedgerVault.sol";
+import { ILedgerVerifiable } from "@synaps3/core/interfaces/base/ILedgerVerifiable.sol";
+import { IAgreementManager } from "contracts/core/interfaces/financial/IAgreementManager.sol";
+import { IAgreementSettler } from "contracts/core/interfaces/financial/IAgreementSettler.sol";
+import { AgreementSettler } from "contracts/financial/AgreementSettler.sol";
+import { BaseTest } from "test/BaseTest.t.sol";
+import { T } from "contracts/core/primitives/Types.sol";
+import { C } from "contracts/core/primitives/Constants.sol";
+
+interface IAgreementManagerExtended is IAgreementManager {
+ function maxParties() external view returns (uint256);
+}
+
+interface IAgreementSettlerExtended is IAgreementSettler {
+ function quitAgreement(uint256 proof) external returns (T.Agreement memory);
+}
+
+contract SettlerMockArbiter {
+ IAgreementSettler public immutable SETTLER;
+
+ constructor(address settler) {
+ SETTLER = IAgreementSettler(settler);
+ }
+
+ function execute(uint256 proof, address counterparty) external returns (T.Agreement memory) {
+ return SETTLER.settleAgreement(proof, counterparty);
+ }
+}
+
+contract AgreementSettlerTest is BaseTest {
+ uint256 internal constant INITIAL_DEPOSIT = 5_000 * 1e18;
+ SettlerMockArbiter arbiter;
+
+ function setUp() public initialize {
+ deployAgreementSettler();
+ arbiter = new SettlerMockArbiter(agreementSettler);
+
+ vm.startPrank(governor);
+ ITollgate(tollgate).setFees(T.Scheme.BPS, address(arbiter), 500, token);
+ IERC20(token).approve(ledger, INITIAL_DEPOSIT);
+ ILedgerVault(ledger).deposit(user, INITIAL_DEPOSIT, token);
+ vm.stopPrank();
+ }
+
+ function _penaltyBps(uint256 partiesLen, uint256 maxParties) private pure returns (uint256) {
+ if (partiesLen <= maxParties) return 0;
+ uint256 excess = partiesLen - maxParties;
+ return ((excess * (excess + 1)) / 2) * 100;
+ }
+
+ function _buildParties(uint256 len) private view returns (address[] memory parties) {
+ parties = new address[](len);
+ for (uint256 i = 0; i < len; i++) {
+ parties[i] = vm.addr(1_100 + i);
+ }
+ }
+
+ function _boundedAmount(uint256 seed, uint256 partiesLen) private view returns (uint256) {
+ uint256 maxParties = IAgreementManagerExtended(agreementManager).maxParties();
+ uint256 penalty = _penaltyBps(partiesLen, maxParties);
+ uint256 available = ILedgerVerifiable(ledger).getLedgerBalance(user, token);
+ uint256 maxAmount = available;
+ if (penalty > 0) {
+ maxAmount = (available * C.BPS_MAX) / (C.BPS_MAX + penalty);
+ }
+ if (maxAmount < 1e18) return 0;
+ return bound(seed, 1e18, maxAmount);
+ }
+
+ function _createAgreement(uint256 amount, uint256 partiesLen) private returns (uint256 proof, T.Agreement memory stored) {
+ address[] memory parties = _buildParties(partiesLen);
+ vm.prank(user);
+ proof = IAgreementManager(agreementManager).createAgreement(amount, token, address(arbiter), parties, "");
+ stored = IAgreementManager(agreementManager).getAgreement(proof);
+ }
+
+ function test_SettleAgreement_DistributesFunds() public {
+ uint256 amount = 150 * 1e18;
+ (uint256 proof, T.Agreement memory agreement) = _createAgreement(amount, 4);
+ address counterparty = vm.addr(5555);
+
+ vm.expectEmit(true, true, true, true, agreementSettler);
+ emit AgreementSettler.AgreementSettled(address(arbiter), counterparty, proof, agreement.fees + (agreement.locked - agreement.total));
+ vm.prank(address(arbiter));
+ T.Agreement memory settled = IAgreementSettlerExtended(agreementSettler).settleAgreement(proof, counterparty);
+
+ uint256 available = settled.total - settled.fees;
+ uint256 protocolTake = settled.fees + (settled.locked - settled.total);
+
+ assertEq(ILedgerVerifiable(ledger).getLedgerBalance(counterparty, token), available, "Counterparty payout mismatch");
+ assertEq(ILedgerVerifiable(ledger).getLedgerBalance(agreementSettler, token), protocolTake, "Protocol take mismatch");
+
+ vm.expectRevert(AgreementSettler.AgreementAlreadySettled.selector);
+ arbiter.execute(proof, counterparty);
+ }
+
+ function test_SettleAgreement_RevertWhen_NotArbiter() public {
+ uint256 amount = 10 * 1e18;
+ (uint256 proof, ) = _createAgreement(amount, 0);
+
+ vm.expectRevert(AgreementSettler.UnauthorizedEscrowAgent.selector);
+ IAgreementSettler(agreementSettler).settleAgreement(proof, vm.addr(999));
+ }
+
+ function test_QuitAgreement_ReleasesFunds() public {
+ uint256 amount = 75 * 1e18;
+ (uint256 proof, T.Agreement memory agreement) = _createAgreement(amount, 6);
+
+ uint256 protocolTake = agreement.fees + (agreement.locked - agreement.total);
+
+ vm.prank(user);
+ vm.expectEmit(true, false, true, true, agreementSettler);
+ emit AgreementSettler.AgreementCancelled(user, proof, protocolTake);
+ T.Agreement memory cancelled = IAgreementSettlerExtended(agreementSettler).quitAgreement(proof);
+
+ assertEq(cancelled.total, agreement.total, "Quit agreement mismatch");
+ assertEq(ILedgerVerifiable(ledger).getLedgerBalance(agreementSettler, token), protocolTake, "Protocol take mismatch");
+ assertEq(
+ ILedgerVerifiable(ledger).getLedgerBalance(user, token),
+ INITIAL_DEPOSIT - protocolTake,
+ "User ledger mismatch"
+ );
+
+ vm.prank(user);
+ vm.expectRevert(AgreementSettler.AgreementAlreadySettled.selector);
+ IAgreementSettlerExtended(agreementSettler).quitAgreement(proof);
+ }
+
+ function test_SettleAgreement_RevertWhen_AlreadySettled() public {
+ uint256 amount = 60 * 1e18;
+ (uint256 proof, ) = _createAgreement(amount, 2);
+ address counterparty = vm.addr(7001);
+
+ vm.prank(address(arbiter));
+ IAgreementSettlerExtended(agreementSettler).settleAgreement(proof, counterparty);
+
+ vm.expectRevert(AgreementSettler.AgreementAlreadySettled.selector);
+ vm.prank(address(arbiter));
+ IAgreementSettlerExtended(agreementSettler).settleAgreement(proof, counterparty);
+ }
+
+ function test_QuitAgreement_RevertWhen_NotInitiator() public {
+ uint256 amount = 30 * 1e18;
+ (uint256 proof, ) = _createAgreement(amount, 1);
+
+ vm.expectRevert(AgreementSettler.UnauthorizedInitiator.selector);
+ vm.prank(vm.addr(3333));
+ IAgreementSettlerExtended(agreementSettler).quitAgreement(proof);
+ }
+
+ function test_QuitAgreement_RevertWhen_AlreadySettled() public {
+ uint256 amount = 90 * 1e18;
+ (uint256 proof, ) = _createAgreement(amount, 2);
+
+ vm.prank(address(arbiter));
+ IAgreementSettlerExtended(agreementSettler).settleAgreement(proof, vm.addr(9000));
+
+ vm.expectRevert(AgreementSettler.AgreementAlreadySettled.selector);
+ vm.prank(user);
+ IAgreementSettlerExtended(agreementSettler).quitAgreement(proof);
+ }
+
+}
diff --git a/test/finance/LedgerVault.t.sol b/test/finance/LedgerVault.t.sol
new file mode 100644
index 0000000..ee9a941
--- /dev/null
+++ b/test/finance/LedgerVault.t.sol
@@ -0,0 +1,290 @@
+// SPDX-License-Identifier: BUSL-1.1
+pragma solidity 0.8.26;
+
+import "forge-std/Test.sol";
+
+import { IAccessManaged } from "@openzeppelin/contracts/access/manager/IAccessManaged.sol";
+import { ERC20 } from "@openzeppelin/contracts/token/ERC20/ERC20.sol";
+import { PausableUpgradeable } from "@openzeppelin/contracts-upgradeable/utils/PausableUpgradeable.sol";
+import { AccessControlledUpgradeable } from "contracts/core/primitives/upgradeable/AccessControlledUpgradeable.sol";
+
+import { LedgerVault } from "contracts/financial/LedgerVault.sol";
+import { ILedgerVerifiable } from "contracts/core/interfaces/base/ILedgerVerifiable.sol";
+import { FinancialOps } from "contracts/core/libraries/FinancialOps.sol";
+import { BaseTest } from "test/BaseTest.t.sol";
+import { C } from "contracts/core/primitives/Constants.sol";
+
+contract MockToken is ERC20 {
+ constructor() ERC20("Mock Token", "MOCK") {}
+
+ function mint(address to, uint256 amount) external {
+ _mint(to, amount);
+ }
+}
+
+contract LedgerVaultTest is BaseTest {
+ LedgerVault internal vault;
+ MockToken internal mockToken;
+
+ address internal operator = vm.addr(11);
+ address internal claimer = vm.addr(12);
+ address internal other = vm.addr(13);
+
+ function setUp() public initialize {
+ mockToken = new MockToken();
+ token = address(mockToken);
+
+ deployLedgerVault();
+ vault = LedgerVault(ledger);
+
+ bytes4[] memory secSelectors = new bytes4[](2);
+ secSelectors[0] = AccessControlledUpgradeable.pause.selector;
+ secSelectors[1] = AccessControlledUpgradeable.unpause.selector;
+
+ _setSecPermissions(ledger, secSelectors);
+ _grantRole(C.OPS_ROLE, operator);
+ _grantRole(C.OPS_ROLE, claimer);
+
+ mockToken.mint(admin, 1_000_000 ether);
+ mockToken.mint(user, 1_000_000 ether);
+ mockToken.mint(other, 1_000_000 ether);
+ }
+
+ function _deposit(address account, uint256 amount) internal returns (uint256) {
+ vm.startPrank(account);
+ mockToken.approve(address(vault), amount);
+ uint256 confirmed = vault.deposit(account, amount, address(mockToken));
+ vm.stopPrank();
+ return confirmed;
+ }
+
+ function test_Deposit_Succeeds() public {
+ uint256 confirmed = _deposit(admin, 100 ether);
+
+ assertEq(confirmed, 100 ether, "Deposit return mismatch");
+ assertEq(
+ ILedgerVerifiable(address(vault)).getLedgerBalance(admin, address(mockToken)),
+ 100 ether,
+ "Ledger balance mismatch"
+ );
+ assertEq(mockToken.balanceOf(address(vault)), 100 ether, "Vault token balance mismatch");
+ }
+
+ function test_Deposit_RevertWhen_NoAllowance() public {
+ vm.prank(admin);
+ vm.expectRevert(abi.encodeWithSelector(FinancialOps.FailDuringDeposit.selector, "Amount exceeds allowance."));
+ vault.deposit(admin, 10 ether, address(mockToken));
+ }
+
+ function test_Deposit_RevertWhen_CurrencyNotApproved() public {
+ MockToken unapproved = new MockToken();
+ unapproved.mint(admin, 100 ether);
+
+ vm.startPrank(admin);
+ unapproved.approve(address(vault), 10 ether);
+ vm.expectRevert(abi.encodeWithSelector(LedgerVault.CurrencyNotAllowed.selector, address(unapproved)));
+ vault.deposit(admin, 10 ether, address(unapproved));
+ vm.stopPrank();
+ }
+
+ function test_Withdraw_Succeeds() public {
+ _deposit(admin, 200 ether);
+
+ vm.prank(admin);
+ uint256 withdrawn = vault.withdraw(admin, 150 ether, address(mockToken));
+
+ assertEq(withdrawn, 150 ether, "Withdrawn amount mismatch");
+ assertEq(
+ ILedgerVerifiable(address(vault)).getLedgerBalance(admin, address(mockToken)),
+ 50 ether,
+ "Ledger balance after withdraw"
+ );
+ assertEq(mockToken.balanceOf(admin), 1_000_000 ether - 50 ether, "Admin token balance mismatch");
+ }
+
+ function test_Withdraw_RevertWhen_NoFunds() public {
+ vm.prank(admin);
+ vm.expectRevert(bytes4(keccak256("NoFundsToWithdraw()")));
+ vault.withdraw(admin, 1 ether, address(mockToken));
+ }
+
+ function test_Transfer_Succeeds() public {
+ _deposit(admin, 100 ether);
+
+ vm.prank(admin);
+ uint256 moved = vault.transfer(user, 40 ether, address(mockToken));
+
+ assertEq(moved, 40 ether, "Transfer amount mismatch");
+ assertEq(
+ ILedgerVerifiable(address(vault)).getLedgerBalance(admin, address(mockToken)),
+ 60 ether,
+ "Admin ledger after transfer"
+ );
+ assertEq(
+ ILedgerVerifiable(address(vault)).getLedgerBalance(user, address(mockToken)),
+ 40 ether,
+ "User ledger after transfer"
+ );
+ assertEq(
+ ILedgerVerifiable(address(vault)).getLedgerBalance(admin, address(mockToken)) +
+ ILedgerVerifiable(address(vault)).getLedgerBalance(user, address(mockToken)),
+ 100 ether,
+ "Ledger totals must conserve balance"
+ );
+ }
+
+ function test_Transfer_RevertWhen_Self() public {
+ _deposit(admin, 50 ether);
+
+ vm.prank(admin);
+ vm.expectRevert(bytes4(keccak256("InvalidOperationParameters()")));
+ vault.transfer(admin, 10 ether, address(mockToken));
+ }
+
+ function test_Lock_Succeeds() public {
+ _deposit(user, 120 ether);
+
+ vm.prank(operator);
+ uint256 locked = vault.lock(user, 70 ether, address(mockToken));
+
+ assertEq(locked, 70 ether, "Locked amount mismatch");
+ assertEq(
+ ILedgerVerifiable(address(vault)).getLedgerBalance(user, address(mockToken)),
+ 50 ether,
+ "User ledger after lock"
+ );
+ assertEq(vault.getLockedBalance(user, address(mockToken)), 70 ether, "Locked balance mismatch");
+ }
+
+ function test_Lock_RevertWhen_Insufficient() public {
+ _deposit(user, 10 ether);
+
+ vm.prank(operator);
+ vm.expectRevert(bytes4(keccak256("NoFundsToLock()")));
+ vault.lock(user, 20 ether, address(mockToken));
+ }
+
+ function test_Release_Succeeds() public {
+ _deposit(user, 90 ether);
+ vm.prank(operator);
+ vault.lock(user, 60 ether, address(mockToken));
+
+ vm.prank(operator);
+ uint256 released = vault.release(user, 30 ether, address(mockToken));
+
+ assertEq(released, 30 ether, "Released amount mismatch");
+ assertEq(vault.getLockedBalance(user, address(mockToken)), 30 ether, "Locked balance after release");
+ assertEq(
+ ILedgerVerifiable(address(vault)).getLedgerBalance(user, address(mockToken)),
+ 60 ether,
+ "Ledger after release"
+ );
+ }
+
+ function test_Claim_Succeeds() public {
+ _deposit(user, 100 ether);
+ vm.prank(operator);
+ vault.lock(user, 40 ether, address(mockToken));
+
+ vm.prank(claimer);
+ uint256 claimed = vault.claim(user, 25 ether, address(mockToken));
+
+ assertEq(claimed, 25 ether, "Claim amount mismatch");
+ assertEq(vault.getLockedBalance(user, address(mockToken)), 15 ether, "Locked balance after claim");
+ assertEq(
+ ILedgerVerifiable(address(vault)).getLedgerBalance(claimer, address(mockToken)),
+ 25 ether,
+ "Claimer ledger after claim"
+ );
+ }
+
+ function test_Lock_RevertWhen_Unauthorized() public {
+ _deposit(user, 30 ether);
+
+ vm.expectRevert(abi.encodeWithSelector(IAccessManaged.AccessManagedUnauthorized.selector, user));
+ vm.prank(user);
+ vault.lock(user, 10 ether, address(mockToken));
+ }
+
+ function test_Claim_RevertWhen_Unauthorized() public {
+ _deposit(user, 50 ether);
+ vm.prank(operator);
+ vault.lock(user, 20 ether, address(mockToken));
+
+ vm.expectRevert(abi.encodeWithSelector(IAccessManaged.AccessManagedUnauthorized.selector, other));
+ vm.prank(other);
+ vault.claim(user, 5 ether, address(mockToken));
+ }
+
+ function test_Pause_BlocksStateChanging() public {
+ _deposit(admin, 10 ether);
+ vm.prank(sec);
+ vault.pause();
+
+ vm.prank(admin);
+ vm.expectRevert(PausableUpgradeable.EnforcedPause.selector);
+ vault.withdraw(admin, 1 ether, address(mockToken));
+
+ vm.prank(sec);
+ vault.unpause();
+
+ vm.prank(admin);
+ vault.withdraw(admin, 1 ether, address(mockToken));
+ }
+
+ function test_Integration_FullFlow() public {
+ _deposit(user, 200 ether);
+ vm.prank(operator);
+ vault.lock(user, 120 ether, address(mockToken));
+ vm.prank(claimer);
+ vault.claim(user, 70 ether, address(mockToken));
+ vm.prank(operator);
+ vault.release(user, 30 ether, address(mockToken));
+ vm.prank(user);
+ vault.transfer(other, 40 ether, address(mockToken));
+
+ assertEq(vault.getLockedBalance(user, address(mockToken)), 20 ether, "Remaining locked");
+ assertEq(
+ ILedgerVerifiable(address(vault)).getLedgerBalance(user, address(mockToken)),
+ 70 ether,
+ "User ledger after flow"
+ );
+ assertEq(
+ ILedgerVerifiable(address(vault)).getLedgerBalance(claimer, address(mockToken)),
+ 70 ether,
+ "Claimer ledger after flow"
+ );
+ }
+
+ function test_DepositWithdraw_FullAmountRestoresState() public {
+ uint256 amount = 250 ether;
+ _deposit(user, amount);
+
+ vm.prank(user);
+ uint256 withdrawn = vault.withdraw(user, amount, address(mockToken));
+ assertEq(withdrawn, amount, "Withdrawn amount mismatch");
+ assertEq(
+ ILedgerVerifiable(address(vault)).getLedgerBalance(user, address(mockToken)),
+ 0,
+ "Ledger balance should return to zero"
+ );
+ assertEq(mockToken.balanceOf(address(vault)), 0, "Vault should hold no tokens");
+ }
+
+ function test_LedgerAndLockedBalancesMatchVaultHoldings() public {
+ _deposit(user, 300 ether);
+ vm.prank(operator);
+ vault.lock(user, 120 ether, address(mockToken));
+ vm.prank(claimer);
+ vault.claim(user, 40 ether, address(mockToken));
+ vm.prank(operator);
+ vault.release(user, 50 ether, address(mockToken));
+
+ uint256 ledgerUser = ILedgerVerifiable(address(vault)).getLedgerBalance(user, address(mockToken));
+ uint256 ledgerClaimer = ILedgerVerifiable(address(vault)).getLedgerBalance(claimer, address(mockToken));
+ uint256 lockedUser = vault.getLockedBalance(user, address(mockToken));
+ uint256 vaultBalance = mockToken.balanceOf(address(vault));
+
+ assertEq(ledgerUser + ledgerClaimer + lockedUser, vaultBalance, "Vault balance must equal ledger + locked");
+ }
+}
diff --git a/test/libraries/FeesOps.t.sol b/test/libraries/FeesOps.t.sol
new file mode 100644
index 0000000..b2f7f64
--- /dev/null
+++ b/test/libraries/FeesOps.t.sol
@@ -0,0 +1,81 @@
+// SPDX-License-Identifier: BUSL-1.1
+pragma solidity 0.8.26;
+
+import "forge-std/Test.sol";
+
+import { FeesOps } from "contracts/core/libraries/FeesOps.sol";
+import { C } from "contracts/core/primitives/Constants.sol";
+
+contract FeesOpsHarness {
+ function isBasePoint(uint256 fee) external pure returns (bool) {
+ return FeesOps.isBasePoint(fee);
+ }
+
+ function isNominal(uint256 fee) external pure returns (bool) {
+ return FeesOps.isNominal(fee);
+ }
+
+ function perOf(uint256 amount, uint256 bps) external pure returns (uint256) {
+ return FeesOps.perOf(amount, bps);
+ }
+
+ function calcBps(uint256 percentage) external pure returns (uint256) {
+ return FeesOps.calcBps(percentage);
+ }
+}
+
+contract FeesOpsTest is Test {
+ FeesOpsHarness harness;
+
+ function setUp() public {
+ harness = new FeesOpsHarness();
+ }
+
+ function test_IsBasePoint_TrueWhenWithinBpsMax() public {
+ assertTrue(harness.isBasePoint(C.BPS_MAX), "BPS max should be valid");
+ assertTrue(harness.isBasePoint(0), "Zero should be valid bps");
+ }
+
+ function test_IsBasePoint_FalseWhenAboveMax() public {
+ assertFalse(harness.isBasePoint(C.BPS_MAX + 1), "Above max should be invalid");
+ }
+
+ function test_IsNominal_TrueWhenWithinScale() public {
+ assertTrue(harness.isNominal(C.SCALE_FACTOR), "Scale factor should be valid nominal");
+ assertTrue(harness.isNominal(0), "Zero should be valid nominal");
+ }
+
+ function test_IsNominal_FalseWhenAboveScale() public {
+ assertFalse(harness.isNominal(C.SCALE_FACTOR + 1), "Above scale should be invalid nominal");
+ }
+
+ function test_PerOf_ComputesPercentage() public {
+ uint256 amount = 1_000 ether;
+ uint256 bps = 250; // 2.5%
+ uint256 expected = (amount * bps) / C.BPS_MAX;
+ assertEq(harness.perOf(amount, bps), expected, "Percentage calculation mismatch");
+ }
+
+ function test_PerOf_RevertsWhen_BpsAboveMax() public {
+ vm.expectRevert(bytes("BPS cannot be greater than 10_000"));
+ harness.perOf(1, C.BPS_MAX + 1);
+ }
+
+ function test_CalcBps_ComputesNominalToBps() public {
+ uint256 per = 5;
+ assertEq(harness.calcBps(per), per * C.SCALE_FACTOR, "calcBps mismatch");
+ }
+
+ function test_PerOf_ReturnsZeroWhenAmountZero() public {
+ assertEq(harness.perOf(0, 1_000), 0, "Zero amount should produce zero result");
+ }
+
+ function test_PerOf_ReturnsZeroWhenBpsZero() public {
+ assertEq(harness.perOf(1_000 ether, 0), 0, "Zero bps should produce zero result");
+ }
+
+ function test_CalcBps_LargePercentage() public {
+ uint256 per = 1_000_000; // 1000000%
+ assertEq(harness.calcBps(per), per * C.SCALE_FACTOR, "Large percentage scaling mismatch");
+ }
+}
diff --git a/test/libraries/FinancialOps.t.sol b/test/libraries/FinancialOps.t.sol
index 49c96a9..f6f122e 100644
--- a/test/libraries/FinancialOps.t.sol
+++ b/test/libraries/FinancialOps.t.sol
@@ -1 +1,245 @@
-// TODO complete here
\ No newline at end of file
+// SPDX-License-Identifier: BUSL-1.1
+pragma solidity 0.8.26;
+
+import "forge-std/Test.sol";
+
+import { FinancialOps } from "contracts/core/libraries/FinancialOps.sol";
+import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
+import { ERC20 } from "@openzeppelin/contracts/token/ERC20/ERC20.sol";
+
+contract TestToken is ERC20 {
+ constructor(string memory name_, string memory symbol_) ERC20(name_, symbol_) {}
+
+ function mint(address to, uint256 amount) external {
+ _mint(to, amount);
+ }
+}
+
+contract FinancialOpsHarness {
+ using FinancialOps for address;
+
+ function depositNative(uint256 amount) external payable returns (uint256) {
+ return FinancialOps.safeDeposit(msg.sender, amount, address(0));
+ }
+
+ function depositToken(address from, uint256 amount, address token) external returns (uint256) {
+ return FinancialOps.safeDeposit(from, amount, token);
+ }
+
+ function transferFunds(address to, uint256 amount, address token) external {
+ FinancialOps.transfer(to, amount, token);
+ }
+
+ function increaseTokenAllowance(address spender, uint256 amount, address token) external {
+ FinancialOps.increaseAllowance(spender, amount, token);
+ }
+
+ function queryAllowance(address owner, address token) external view returns (uint256) {
+ return FinancialOps.allowance(owner, token);
+ }
+
+ function queryNativeAllowance(address owner) external payable returns (uint256) {
+ return FinancialOps.allowance(owner, address(0));
+ }
+
+ function queryBalance(address target, address token) external view returns (uint256) {
+ return FinancialOps.balanceOf(target, token);
+ }
+
+ receive() external payable {}
+}
+
+contract FinancialOpsTest is Test {
+ FinancialOpsHarness internal harness;
+ TestToken internal token;
+ address internal alice;
+ address internal bob;
+ address internal carol;
+ uint256 internal constant INITIAL_TOKEN_ALLOCATION = 1e24;
+ uint256 internal constant INITIAL_NATIVE_ALLOCATION = 100 ether;
+
+ function setUp() public {
+ harness = new FinancialOpsHarness();
+ token = new TestToken("Mock Token", "MOCK");
+ alice = vm.addr(1);
+ bob = vm.addr(2);
+ carol = vm.addr(3);
+
+ token.mint(alice, INITIAL_TOKEN_ALLOCATION);
+ token.mint(bob, INITIAL_TOKEN_ALLOCATION);
+ vm.deal(alice, INITIAL_NATIVE_ALLOCATION);
+ vm.deal(bob, INITIAL_NATIVE_ALLOCATION);
+ }
+
+ function test_SafeDepositNative_Succeeds() public {
+ uint256 amount = 5 ether;
+ vm.prank(alice);
+ uint256 deposited = harness.depositNative{ value: amount }(amount);
+ assertEq(deposited, amount, "Deposit return mismatch");
+ assertEq(address(harness).balance, amount, "Harness native balance mismatch");
+ }
+
+ function test_SafeDepositNative_RevertWhen_Mismatch() public {
+ uint256 amount = 3 ether;
+ vm.prank(alice);
+ vm.expectRevert(abi.encodeWithSelector(FinancialOps.FailDuringDeposit.selector, "Invalid expected sent balance."));
+ harness.depositNative{ value: amount - 1 }(amount);
+ }
+
+ function test_SafeDepositNative_RevertWhen_Zero() public {
+ vm.prank(alice);
+ vm.expectRevert(abi.encodeWithSelector(FinancialOps.FailDuringDeposit.selector, "Invalid amount or sender."));
+ harness.depositNative{ value: 0 }(0);
+ }
+
+ function test_SafeDepositERC20_Succeeds() public {
+ uint256 amount = 2e21;
+ vm.startPrank(alice);
+ token.approve(address(harness), amount);
+ uint256 deposited = harness.depositToken(alice, amount, address(token));
+ vm.stopPrank();
+
+ assertEq(deposited, amount, "ERC20 deposit return mismatch");
+ assertEq(token.balanceOf(address(harness)), amount, "Harness token balance mismatch");
+ assertEq(token.balanceOf(alice), INITIAL_TOKEN_ALLOCATION - amount, "Alice token balance mismatch");
+ }
+
+ function test_SafeDepositERC20_RevertWhen_NoAllowance() public {
+ uint256 amount = 1e18;
+ vm.prank(alice);
+ vm.expectRevert(abi.encodeWithSelector(FinancialOps.FailDuringDeposit.selector, "Amount exceeds allowance."));
+ harness.depositToken(alice, amount, address(token));
+ }
+
+ function test_SafeDepositERC20_RevertWhen_Zero() public {
+ vm.prank(alice);
+ vm.expectRevert(abi.encodeWithSelector(FinancialOps.FailDuringDeposit.selector, "Invalid amount or sender."));
+ harness.depositToken(alice, 0, address(token));
+ }
+
+ function test_TransferNative_Succeeds() public {
+ uint256 amount = 6 ether;
+ vm.prank(alice);
+ harness.depositNative{ value: amount }(amount);
+
+ uint256 transferAmount = 2 ether;
+ uint256 bobBefore = bob.balance;
+ harness.transferFunds(bob, transferAmount, address(0));
+
+ assertEq(address(harness).balance, amount - transferAmount, "Harness native balance mismatch");
+ assertEq(bob.balance, bobBefore + transferAmount, "Bob native balance mismatch");
+ }
+
+ function test_TransferERC20_Succeeds() public {
+ uint256 amount = 1e22;
+ vm.startPrank(alice);
+ token.approve(address(harness), amount);
+ harness.depositToken(alice, amount, address(token));
+ vm.stopPrank();
+
+ uint256 transferAmount = 3e21;
+ uint256 bobBefore = token.balanceOf(bob);
+ harness.transferFunds(bob, transferAmount, address(token));
+
+ assertEq(token.balanceOf(address(harness)), amount - transferAmount, "Harness token balance mismatch");
+ assertEq(token.balanceOf(bob), bobBefore + transferAmount, "Bob token balance mismatch");
+ }
+
+ function test_Transfer_RevertWhen_InvalidAmount() public {
+ vm.expectRevert(abi.encodeWithSelector(FinancialOps.FailDuringTransfer.selector, "Invalid amount or recipient."));
+ harness.transferFunds(address(0), 1, address(token));
+ }
+
+ function test_IncreaseAllowance_Succeeds() public {
+ uint256 allowanceAmount = 5e20;
+ harness.increaseTokenAllowance(carol, allowanceAmount, address(token));
+ assertEq(token.allowance(address(harness), carol), allowanceAmount, "Allowance mismatch");
+ }
+
+ function test_IncreaseAllowance_RevertWhen_ZeroAmount() public {
+ vm.expectRevert(abi.encodeWithSelector(FinancialOps.FailDuringDeposit.selector, "Invalid spender or allowance attempt"));
+ harness.increaseTokenAllowance(carol, 0, address(token));
+ }
+
+ function test_IncreaseAllowance_RevertWhen_NativeToken() public {
+ vm.expectRevert(abi.encodeWithSelector(FinancialOps.FailDuringDeposit.selector, "Invalid spender or allowance attempt"));
+ harness.increaseTokenAllowance(carol, 1, address(0));
+ }
+
+ function test_Allowance_NativeReflectsMsgValue() public {
+ uint256 amount = 3 ether;
+ uint256 reported = harness.queryNativeAllowance{ value: amount }(alice);
+ assertEq(reported, amount, "Native allowance mismatch");
+ }
+
+ function test_Allowance_ERC20MatchesApproval() public {
+ uint256 amount = 9e20;
+ vm.startPrank(alice);
+ token.approve(address(harness), amount);
+ vm.stopPrank();
+
+ uint256 reported = harness.queryAllowance(alice, address(token));
+ assertEq(reported, amount, "Allowance report mismatch");
+ }
+
+ function test_BalanceOf_ReturnsBalances() public {
+ uint256 nativeAmount = 2 ether;
+ vm.prank(alice);
+ harness.depositNative{ value: nativeAmount }(nativeAmount);
+
+ uint256 tokenAmount = 3e21;
+ vm.startPrank(bob);
+ token.approve(address(harness), tokenAmount);
+ harness.depositToken(bob, tokenAmount, address(token));
+ vm.stopPrank();
+
+ assertEq(harness.queryBalance(address(harness), address(0)), nativeAmount, "Native balance query mismatch");
+ assertEq(harness.queryBalance(address(harness), address(token)), tokenAmount, "Token balance query mismatch");
+ }
+
+ function test_Integration_MultiActorFlow() public {
+ vm.prank(alice);
+ harness.depositNative{ value: 10 ether }(10 ether);
+ vm.prank(bob);
+ harness.depositNative{ value: 4 ether }(4 ether);
+
+ vm.startPrank(alice);
+ token.approve(address(harness), 5e21);
+ harness.depositToken(alice, 5e21, address(token));
+ vm.stopPrank();
+
+ vm.startPrank(bob);
+ token.approve(address(harness), 2e21);
+ harness.depositToken(bob, 2e21, address(token));
+ vm.stopPrank();
+
+ harness.transferFunds(carol, 6 ether, address(0));
+ harness.transferFunds(carol, 3e21, address(token));
+
+ assertEq(address(harness).balance, 8 ether, "Harness native residual mismatch");
+ assertEq(token.balanceOf(address(harness)), 4e21, "Harness token residual mismatch");
+ assertEq(carol.balance, 6 ether, "Carol native balance mismatch");
+ assertEq(token.balanceOf(carol), 3e21, "Carol token balance mismatch");
+ }
+
+ function test_DepositWithdrawFullyRestoresBalances() public {
+ uint256 amount = 12 ether;
+ vm.prank(alice);
+ harness.depositNative{ value: amount }(amount);
+
+ vm.prank(alice);
+ harness.transferFunds(alice, amount, address(0));
+ assertEq(address(harness).balance, 0, "Harness native balance should be zero");
+ }
+
+ function test_TokenDepositWithdrawFullyRestoresBalances() public {
+ uint256 amount = 4e21;
+ vm.startPrank(alice);
+ token.approve(address(harness), amount);
+ harness.depositToken(alice, amount, address(token));
+ vm.stopPrank();
+
+ harness.transferFunds(alice, amount, address(token));
+ assertEq(token.balanceOf(address(harness)), 0, "Harness token balance should be zero");
+ }
+}
diff --git a/test/libraries/RollingOps.t.sol b/test/libraries/RollingOps.t.sol
index 9fd776a..450b712 100644
--- a/test/libraries/RollingOps.t.sol
+++ b/test/libraries/RollingOps.t.sol
@@ -2,244 +2,130 @@
pragma solidity 0.8.26;
import "forge-std/Test.sol";
+
import { RollingOps } from "contracts/core/libraries/RollingOps.sol";
-import { console } from "forge-std/console.sol";
-/// @title RollingOpsWrapper
-/// @notice A wrapper contract to test the RollingOps library using Foundry.
-contract RollingOpsWrapper {
+contract RollingOpsHarness {
using RollingOps for RollingOps.AddressArray;
- RollingOps.AddressArray private rollingArray;
+ RollingOps.AddressArray internal set;
- /// @notice Configures the max window size of the rolling array.
- /// @param window The new max size of the array.
- function configureWindow(uint256 window) external {
- rollingArray.configure(window);
+ function configure(uint256 window) external {
+ set.configure(window);
}
- /// @notice Returns the maximum window size.
- function getWindow() external view returns (uint256) {
- return rollingArray.window();
+ function roll(address value) external {
+ set.roll(value);
}
- /// @notice Adds a new address to the rolling array.
- /// @param value The address to be added.
- function add(address value) external {
- rollingArray.roll(value);
+ function contains(address value) external view returns (bool) {
+ return set.contains(value);
}
- /// @notice Checks if an address exists in the rolling array.
- /// @param value The address to check.
- /// @return exists True if the address is in the rolling array, false otherwise.
- function exists(address value) external view returns (bool) {
- return rollingArray.contains(value);
+ function length() external view returns (uint256) {
+ return set.length();
}
- /// @notice Gets the length of the rolling array.
- /// @return The number of stored addresses.
- function getLength() external view returns (uint256) {
- return rollingArray.length();
+ function window() external view returns (uint256) {
+ return set.window();
}
- /// @notice Gets an address at a specific index.
- /// @param index The index (1-based) to retrieve.
- /// @return The address stored at the given index.
- function getAt(uint256 index) external view returns (address) {
- return rollingArray.at(index);
+ function at(uint256 index) external view returns (address) {
+ return set.at(index);
}
- /// @notice Retrieves all addresses currently stored in the rolling array.
- /// @return An array of all addresses in the rolling array.
- function getAll() external view returns (address[] memory) {
- return rollingArray.values();
+ function values() external view returns (address[] memory) {
+ return set.values();
}
}
contract RollingOpsTest is Test {
- RollingOpsWrapper private rolling;
+ RollingOpsHarness harness;
function setUp() public {
- rolling = new RollingOpsWrapper();
+ harness = new RollingOpsHarness();
}
- function test_Window_ReturnDefaultWindowSize() public view {
- assertEq(rolling.getWindow(), 3, "Default window size should be 3");
+ function test_DefaultWindow_IsThree() public {
+ assertEq(harness.window(), 3, "Default window mismatch");
+ assertEq(harness.length(), 0, "Initial length should be zero");
}
- function test_Configure_SetValidWindowSize() public {
- rolling.configureWindow(5);
- assertEq(rolling.getWindow(), 5, "Expected window size should be 5");
+ function test_Configure_SetsCustomWindow() public {
+ harness.configure(5);
+ assertEq(harness.window(), 5, "Window should update to configured value");
}
- function test_Configure_MaximumWindowSize() public {
- uint256 maxWindow = type(uint256).max;
- rolling.configureWindow(maxWindow);
- assertEq(rolling.getWindow(), maxWindow, "Expected window size should be max uint256");
+ function test_Configure_RevertWhen_ZeroWindow() public {
+ vm.expectRevert(RollingOps.InvalidZeroWindowSize.selector);
+ harness.configure(0);
}
- function test_RevertIf_SetZeroWindowSize() public {
- vm.expectRevert();
- rolling.configureWindow(0);
+ function test_Roll_AppendsUntilWindow() public {
+ address a = vm.addr(1);
+ address b = vm.addr(2);
+ harness.roll(a);
+ harness.roll(b);
+ assertEq(harness.length(), 2, "Length mismatch after roll");
+ assertEq(harness.at(0), a, "First element mismatch");
+ assertEq(harness.at(1), b, "Second element mismatch");
}
- function test_Add_NotRollingElements() public {
- address addr1 = vm.addr(1);
- address addr2 = vm.addr(2);
- address addr3 = vm.addr(3);
- // using default window
- rolling.add(addr1);
- rolling.add(addr2);
- rolling.add(addr3);
-
- address[] memory got = rolling.getAll();
- address[] memory expected = new address[](3);
- expected[0] = addr1;
- expected[1] = addr2;
- expected[2] = addr3;
+ function test_Roll_RollsOutOldestWhenWindowExceeded() public {
+ harness.configure(3);
+ address[4] memory addrs = [vm.addr(1), vm.addr(2), vm.addr(3), vm.addr(4)];
+ for (uint256 i = 0; i < addrs.length; i++) {
+ harness.roll(addrs[i]);
+ }
- assertEq(got, expected, "Expected addresses should match");
+ assertEq(harness.length(), 3, "Length should not exceed window");
+ assertEq(harness.at(0), addrs[1], "Oldest element not rolled out");
+ assertEq(harness.at(1), addrs[2], "Order mismatch after roll");
+ assertEq(harness.at(2), addrs[3], "Newest element missing");
}
- function test_Add_RollingOldestElement() public {
- address addr1 = vm.addr(1);
- address addr2 = vm.addr(2);
- address addr3 = vm.addr(3);
- address addr4 = vm.addr(4);
-
- // using default window
- rolling.add(addr1);
- rolling.add(addr2);
- rolling.add(addr3);
-
- // before = [addr1, addr2, addr3]
- // after = [ addr2, addr3, addr4]
- rolling.add(addr4);
-
- address[] memory got = rolling.getAll();
- address[] memory expected = new address[](3);
- expected[0] = addr2;
- expected[1] = addr3;
- expected[2] = addr4;
+ function test_Contains_ReturnsFalseWhenMissing() public {
+ assertFalse(harness.contains(vm.addr(99)), "Contains should be false for missing value");
+ }
- assertEq(got, expected, "Expected addresses should match after rolling");
+ function test_Contains_ReturnsTrueAfterRoll() public {
+ address value = vm.addr(42);
+ harness.roll(value);
+ assertTrue(harness.contains(value), "Contains should be true after roll");
}
- function test_Add_SizeOne() public {
- rolling.configureWindow(1);
- address addr1 = vm.addr(1);
- address addr2 = vm.addr(2);
+ function test_At_RevertWhen_IndexOutOfBounds() public {
+ vm.expectRevert(RollingOps.IndexOutOfBounds.selector);
+ harness.at(0);
+ }
- rolling.add(addr1);
- assertEq(rolling.getAt(0), addr1, "First address should be addr1");
+ function test_Values_ReturnsAllInOrder() public {
+ harness.configure(3);
+ address[3] memory addrs = [vm.addr(1), vm.addr(2), vm.addr(3)];
+ for (uint256 i = 0; i < addrs.length; i++) {
+ harness.roll(addrs[i]);
+ }
- rolling.add(addr2);
- assertEq(rolling.getAt(0), addr2, "Last address should be addr2 after rolling");
+ address[] memory vals = harness.values();
+ assertEq(vals.length, 3, "Values length mismatch");
+ for (uint256 i = 0; i < vals.length; i++) {
+ assertEq(vals[i], addrs[i], "Values order mismatch");
+ }
}
- function test_Add_MultipleRollovers() public {
- // using window = 5
- rolling.configureWindow(5);
-
- address addr1 = vm.addr(1);
- address addr2 = vm.addr(2);
- address addr3 = vm.addr(3);
- address addr4 = vm.addr(4);
- address addr5 = vm.addr(5);
- address addr6 = vm.addr(6);
- address addr7 = vm.addr(7);
+ function test_Integration_ConfiguredWindowFlow() public {
+ harness.configure(2);
+ address a = vm.addr(1);
+ address b = vm.addr(2);
+ address c = vm.addr(3);
- rolling.add(addr1); // out
- rolling.add(addr2); // out
- rolling.add(addr3);
- rolling.add(addr4);
- rolling.add(addr5);
- rolling.add(addr6);
- rolling.add(addr7);
+ harness.roll(a);
+ harness.roll(b);
+ harness.roll(c);
- address[] memory got = rolling.getAll();
- address[] memory expected = new address[](5);
- expected[0] = addr3;
- expected[1] = addr4;
- expected[2] = addr5;
- expected[3] = addr6;
- expected[4] = addr7;
-
- assertEq(got, expected, "Expected addresses should match after multiple rollovers");
- }
-
- function test_Exists_ReturnTrueIfExists() public {
- address addr1 = vm.addr(1);
- address addr2 = vm.addr(2);
- address addr3 = vm.addr(3);
- address addr4 = vm.addr(4);
- address addr5 = vm.addr(5);
- address addr6 = vm.addr(6);
-
- // using default window
- rolling.add(addr1);
- rolling.add(addr2);
- rolling.add(addr3);
- rolling.add(addr4);
- rolling.add(addr5);
- rolling.add(addr6);
-
- // rolled out should return false
- assertFalse(rolling.exists(addr1), "Address should not exist after rolling out");
- assertFalse(rolling.exists(addr2), "Address should not exist after rolling out");
- assertFalse(rolling.exists(addr3), "Address should not exist after rolling out");
- assertTrue(rolling.exists(addr4), "Address should not exist after rolling out");
- assertTrue(rolling.exists(addr5), "Address should not exist after rolling out");
- assertTrue(rolling.exists(addr6), "Address should not exist after rolling out");
- }
-
- function test_Length_ReturnValidLen() public {
- address addr1 = vm.addr(1);
- address addr2 = vm.addr(2);
-
- rolling.add(addr1);
- rolling.add(addr2);
- assertEq(rolling.getLength(), 2, "Expected length should be 2");
-
- address addr3 = vm.addr(3);
- address addr4 = vm.addr(4);
- address addr5 = vm.addr(5);
- rolling.add(addr3);
- rolling.add(addr4);
- rolling.add(addr5);
- // do not grow; default window is 3
- // must keep the same window size
- assertEq(rolling.getLength(), 3, "Expected length should be 3 after rolling");
- }
-
- function test_At_ReturnCorrespondingValue() public {
- address addr1 = vm.addr(1);
- address addr2 = vm.addr(2);
-
- rolling.add(addr1);
- rolling.add(addr2);
-
- assertEq(rolling.getAt(0), addr1, "First address should be addr1");
- assertEq(rolling.getAt(1), addr2, "Second address should be addr2");
- }
-
- function test_At_ReturnLastElement() public {
- rolling.configureWindow(3);
- address addr1 = vm.addr(1);
- address addr2 = vm.addr(2);
- address addr3 = vm.addr(3);
- rolling.add(addr1);
- rolling.add(addr2);
- rolling.add(addr3);
-
- assertEq(rolling.getAt(2), addr3, "Last address should be addr3");
- }
-
- function test_At_RevertIf_InvalidIndex() public {
- address addr1 = vm.addr(1);
- rolling.add(addr1);
- // only index 0 existing
- vm.expectRevert();
- rolling.getAt(1);
+ assertEq(harness.length(), 2, "Length should clamp to window size");
+ assertEq(harness.at(0), b, "First element should be second rolled");
+ assertEq(harness.at(1), c, "Second element should be latest rolled");
+ assertFalse(harness.contains(a), "Rolled out element should not be contained");
}
}
diff --git a/test/policies/PolicyAudit.t.sol b/test/policies/PolicyAudit.t.sol
new file mode 100644
index 0000000..7bb7a79
--- /dev/null
+++ b/test/policies/PolicyAudit.t.sol
@@ -0,0 +1,169 @@
+// SPDX-License-Identifier: BUSL-1.1
+pragma solidity 0.8.26;
+
+import "forge-std/Test.sol";
+import { PolicyAudit } from "contracts/policies/PolicyAudit.sol";
+import { AccessControlledUpgradeable } from "contracts/core/primitives/upgradeable/AccessControlledUpgradeable.sol";
+import { QuorumUpgradeable } from "contracts/core/primitives/upgradeable/QuorumUpgradeable.sol";
+import { IPolicy } from "contracts/core/interfaces/policies/IPolicy.sol";
+import { T } from "contracts/core/primitives/Types.sol";
+import { ERC165 } from "@openzeppelin/contracts/utils/introspection/ERC165.sol";
+
+import { BaseTest } from "test/BaseTest.t.sol";
+
+contract MockPolicy is ERC165, IPolicy {
+ function setup(address, bytes calldata) external override {}
+
+ function enforce(address, T.Agreement calldata) external pure override returns (uint256[] memory) {
+ return new uint256[](0);
+ }
+
+ function isAccessAllowed(address, bytes calldata) external pure override returns (bool) {
+ return true;
+ }
+
+ function getLicense(address, bytes calldata) external pure override returns (uint256) {
+ return 0;
+ }
+
+ function resolveTerms(bytes calldata) external pure override returns (T.Terms memory) {
+ return T.Terms({ amount: 0, currency: address(0), timeFrame: T.TimeFrame.NONE, uri: "" });
+ }
+
+ function getAttestationProvider() external pure override returns (address) {
+ return address(0);
+ }
+
+ function name() external pure override returns (string memory) {
+ return "Mock Policy";
+ }
+
+ function description() external pure override returns (string memory) {
+ return "Mock policy for testing";
+ }
+
+ function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {
+ return interfaceId == type(IPolicy).interfaceId || super.supportsInterface(interfaceId);
+ }
+}
+
+contract NotPolicy {}
+
+contract PolicyAuditTest is BaseTest {
+ PolicyAudit internal audit;
+ address internal nonAdmin;
+
+ function setUp() public initialize {
+ deployPolicyAudit();
+ audit = PolicyAudit(policyAudit);
+ nonAdmin = vm.addr(77);
+ }
+
+ function test_Submit_RevertWhen_InvalidPolicy() public {
+ NotPolicy invalid = new NotPolicy();
+ vm.expectRevert(abi.encodeWithSelector(PolicyAudit.InvalidPolicyContract.selector, address(invalid)));
+ audit.submit(address(invalid));
+ }
+
+ function test_Submit_RegistersPolicy() public {
+ MockPolicy policy = new MockPolicy();
+ vm.expectEmit(true, true, false, true, address(audit));
+ emit PolicyAudit.PolicySubmitted(address(policy), address(this));
+ audit.submit(address(policy));
+ assertTrue(audit.isPending(address(policy)), "Policy should be pending approval");
+ assertFalse(audit.isRejected(address(policy)), "Policy should not be rejected");
+ assertFalse(audit.isApproved(address(policy)), "Policy should not be approved yet");
+ }
+
+ function test_Submit_RevertWhen_AlreadySubmitted() public {
+ MockPolicy policy = new MockPolicy();
+ audit.submit(address(policy));
+ vm.expectRevert(QuorumUpgradeable.NotPendingApproval.selector);
+ audit.submit(address(policy));
+ }
+
+ function test_Approve_TransitionsToActive() public {
+ MockPolicy policy = new MockPolicy();
+ audit.submit(address(policy));
+
+ vm.expectEmit(true, true, false, true, address(audit));
+ emit PolicyAudit.PolicyApproved(address(policy), admin);
+ vm.prank(admin);
+ audit.approve(address(policy));
+ assertTrue(audit.isApproved(address(policy)), "Policy should be approved");
+ assertFalse(audit.isRejected(address(policy)), "Policy should not be rejected");
+ assertFalse(audit.isPending(address(policy)), "Policy should not remain pending");
+ }
+
+ function test_Approve_RevertWhen_NotWaiting() public {
+ MockPolicy policy = new MockPolicy();
+ vm.prank(admin);
+ vm.expectRevert(QuorumUpgradeable.NotWaitingApproval.selector);
+ audit.approve(address(policy));
+ }
+
+ function test_Reject_TransitionsToBlocked() public {
+ MockPolicy policy = new MockPolicy();
+ audit.submit(address(policy));
+ vm.prank(admin);
+ audit.approve(address(policy));
+
+ vm.expectEmit(true, true, false, true, address(audit));
+ emit PolicyAudit.PolicyRevoked(address(policy), admin);
+ vm.prank(admin);
+ audit.reject(address(policy));
+ assertFalse(audit.isApproved(address(policy)), "Policy should no longer be approved");
+ assertTrue(audit.isRejected(address(policy)), "Policy should be rejected");
+ assertFalse(audit.isPending(address(policy)), "Rejected policy should not be pending");
+ vm.startPrank(admin);
+ vm.expectRevert(QuorumUpgradeable.NotWaitingApproval.selector);
+ audit.approve(address(policy));
+ vm.stopPrank();
+ }
+
+ function test_Reject_RevertWhen_NotActive() public {
+ MockPolicy policy = new MockPolicy();
+ audit.submit(address(policy));
+ vm.prank(admin);
+ vm.expectRevert(QuorumUpgradeable.InvalidInactiveState.selector);
+ audit.reject(address(policy));
+ }
+
+ function test_OnlyAdminMayApprove() public {
+ MockPolicy policy = new MockPolicy();
+ audit.submit(address(policy));
+
+ vm.expectRevert(
+ abi.encodeWithSelector(
+ AccessControlledUpgradeable.InvalidUnauthorizedOperation.selector,
+ "Only admin can perform this action."
+ )
+ );
+ vm.prank(nonAdmin);
+ audit.approve(address(policy));
+ }
+
+ function test_Integration_SubmitApproveRejectMultiple() public {
+ MockPolicy policy1 = new MockPolicy();
+ MockPolicy policy2 = new MockPolicy();
+ MockPolicy policy3 = new MockPolicy();
+
+ audit.submit(address(policy1));
+ audit.submit(address(policy2));
+ audit.submit(address(policy3));
+
+ vm.prank(admin);
+ audit.approve(address(policy1));
+ vm.prank(admin);
+ audit.approve(address(policy2));
+ vm.prank(admin);
+ audit.reject(address(policy2));
+
+ assertTrue(audit.isApproved(address(policy1)), "Policy1 should be active");
+ assertFalse(audit.isApproved(address(policy2)), "Policy2 should be blocked");
+ assertTrue(audit.isRejected(address(policy2)), "Policy2 should be rejected");
+ assertFalse(audit.isApproved(address(policy3)), "Policy3 should remain unapproved");
+ assertFalse(audit.isRejected(address(policy3)), "Policy3 should not be rejected");
+ assertTrue(audit.isPending(address(policy3)), "Policy3 should remain pending");
+ }
+}
diff --git a/test/policies/PolicyBase.t.sol b/test/policies/PolicyBase.t.sol
new file mode 100644
index 0000000..628a80e
--- /dev/null
+++ b/test/policies/PolicyBase.t.sol
@@ -0,0 +1,273 @@
+// SPDX-License-Identifier: BUSL-1.1
+pragma solidity 0.8.26;
+
+import "forge-std/Test.sol";
+
+import { PolicyBase } from "contracts/policies/PolicyBase.sol";
+import { IPolicy } from "contracts/core/interfaces/policies/IPolicy.sol";
+import { IAttestationProvider } from "contracts/core/interfaces/base/IAttestationProvider.sol";
+import { IAssetRegistry } from "contracts/core/interfaces/assets/IAssetRegistry.sol";
+import { IRightsPolicyManagerVerifiable } from "contracts/core/interfaces/rights/IRightsPolicyManagerVerifiable.sol";
+import { IRightsPolicyAuthorizerVerifiable } from "contracts/core/interfaces/rights/IRightsPolicyAuthorizerVerifiable.sol";
+import { T } from "contracts/core/primitives/Types.sol";
+
+import { ERC721 } from "@openzeppelin/contracts/token/ERC721/ERC721.sol";
+
+contract RightsPolicyManagerVerifiableMock is IRightsPolicyManagerVerifiable {
+ function getPolicies(address) external pure override returns (address[] memory policies) {
+ policies = new address[](0);
+ }
+
+ function getActivePolicy(address, bytes memory) external pure override returns (bool active, address policyAddress) {
+ return (false, address(0));
+ }
+
+ function getActivePolicies(address, bytes memory) external pure override returns (address[] memory policies) {
+ policies = new address[](0);
+ }
+
+ function isActivePolicy(address, address, bytes calldata) external pure override returns (bool) {
+ return false;
+ }
+
+ function isRegisteredPolicy(address, address) external pure override returns (bool) {
+ return false;
+ }
+}
+
+contract RightsPolicyAuthorizerVerifiableMock is IRightsPolicyAuthorizerVerifiable {
+ function getAuthorizedPolicies(address) external pure override returns (address[] memory policies) {
+ policies = new address[](0);
+ }
+
+ function isPolicyAuthorized(address, address) external pure override returns (bool) {
+ return false;
+ }
+}
+
+contract AttestationProviderMock is IAttestationProvider {
+ address[] internal _lastRecipients;
+ uint256 public lastExpireAt;
+ bytes public lastData;
+ uint256 public attestCalls;
+ uint256 private _nextId = 1;
+ mapping(address => mapping(uint256 => bool)) internal _issued;
+
+ function getName() external pure returns (string memory) {
+ return "mock";
+ }
+
+ function getAddress() external view returns (address) {
+ return address(this);
+ }
+
+ function attest(
+ address[] calldata recipients,
+ uint256 expireAt,
+ bytes calldata data
+ ) external override returns (uint256[] memory attestationIds) {
+ delete _lastRecipients;
+ uint256 len = recipients.length;
+ for (uint256 i = 0; i < len; i++) {
+ _lastRecipients.push(recipients[i]);
+ }
+
+ lastExpireAt = expireAt;
+ lastData = data;
+ attestCalls++;
+
+ attestationIds = new uint256[](len);
+ for (uint256 i = 0; i < len; i++) {
+ uint256 id = _nextId++;
+ attestationIds[i] = id;
+ _issued[recipients[i]][id] = true;
+ }
+ if (len == 0) {
+ _nextId++;
+ }
+ }
+
+ function lastRecipientsLength() external view returns (uint256) {
+ return _lastRecipients.length;
+ }
+
+ function lastRecipientAt(uint256 index) external view returns (address) {
+ return _lastRecipients[index];
+ }
+
+ function verify(uint256 attestationId, address recipient) external view returns (bool) {
+ return _issued[recipient][attestationId];
+ }
+}
+
+contract AssetRegistryMock is ERC721, IAssetRegistry {
+ constructor() ERC721("MockRegistry", "MREG") {}
+
+ function register(address to, uint256 assetId) external override {
+ _mint(to, assetId);
+ }
+
+ function revoke(uint256 assetId) external override {
+ _burn(assetId);
+ }
+
+ function transfer(address to, uint256 assetId) external override {
+ safeTransferFrom(msg.sender, to, assetId);
+ }
+}
+
+contract PolicyBaseHarness is PolicyBase {
+ constructor(
+ address rightsPolicyManager,
+ address rightsAuthorizer,
+ address assetRegistry,
+ address attestationProvider
+ ) PolicyBase(rightsPolicyManager, rightsAuthorizer, assetRegistry, attestationProvider) {}
+
+ function managerPing() external onlyPolicyManager returns (bool) {
+ return true;
+ }
+
+ function authorizerPing() external onlyPolicyAuthorizer returns (bool) {
+ return true;
+ }
+
+ function exposeCommit(
+ address holder,
+ T.Agreement calldata agreement,
+ uint256 expireAt
+ ) external returns (uint256[] memory) {
+ return _commit(holder, agreement, expireAt);
+ }
+
+ function exposeSetAttestation(address account, bytes memory context, uint256 attestationId) external {
+ _setAttestation(account, context, attestationId);
+ }
+
+ function exposeHolder(uint256 assetId) external view returns (address) {
+ return _getHolder(assetId);
+ }
+
+ function setup(address, bytes calldata) external pure override {}
+
+ function enforce(
+ address,
+ T.Agreement calldata agreement
+ ) external pure override returns (uint256[] memory result) {
+ result = new uint256[](agreement.parties.length);
+ }
+
+ function isAccessAllowed(address, bytes calldata) external pure override returns (bool) {
+ return true;
+ }
+
+ function resolveTerms(bytes calldata) external pure override returns (T.Terms memory terms) {}
+
+ function name() external pure override returns (string memory) {
+ return "PolicyBaseHarness";
+ }
+
+ function description() external pure override returns (string memory) {
+ return "Policy base test harness";
+ }
+}
+
+contract PolicyBaseTest is Test {
+ RightsPolicyManagerVerifiableMock internal manager;
+ RightsPolicyAuthorizerVerifiableMock internal authorizer;
+ AssetRegistryMock internal registry;
+ AttestationProviderMock internal attestation;
+ PolicyBaseHarness internal policy;
+
+ address internal holder = address(0xBEEF);
+ address internal keeper = address(0xCAFE);
+
+ function setUp() public {
+ manager = new RightsPolicyManagerVerifiableMock();
+ authorizer = new RightsPolicyAuthorizerVerifiableMock();
+ registry = new AssetRegistryMock();
+ attestation = new AttestationProviderMock();
+ policy = new PolicyBaseHarness(address(manager), address(authorizer), address(registry), address(attestation));
+ }
+
+ function test_GetAttestationProvider_ReturnsConfiguredAddress() public {
+ assertEq(policy.getAttestationProvider(), address(attestation), "Unexpected attestation provider address");
+ }
+
+ function test_SupportsInterface_ForIPolicy() public {
+ assertTrue(policy.supportsInterface(type(IPolicy).interfaceId), "IPolicy interface support missing");
+ assertFalse(policy.supportsInterface(bytes4(0xdeadbeef)), "Unexpected interface should be unsupported");
+ }
+
+ function test_OnlyPolicyManager_AllowsManagerAddress() public {
+ vm.expectRevert(
+ abi.encodeWithSelector(PolicyBase.InvalidUnauthorizedCall.selector, "Only rights policy manager allowed.")
+ );
+ policy.managerPing();
+
+ vm.prank(address(manager));
+ assertTrue(policy.managerPing(), "Manager call should succeed");
+ }
+
+ function test_OnlyPolicyAuthorizer_AllowsAuthorizerAddress() public {
+ vm.expectRevert(
+ abi.encodeWithSelector(PolicyBase.InvalidUnauthorizedCall.selector, "Only rights policy authorizer allowed.")
+ );
+ policy.authorizerPing();
+
+ vm.prank(address(authorizer));
+ assertTrue(policy.authorizerPing(), "Authorizer call should succeed");
+ }
+
+ function test_SetAttestationStoresAndEmits() public {
+ address account = address(0xA11CE);
+ bytes memory context = abi.encodePacked(uint256(1));
+ uint256 attestationId = 321;
+ bytes32 composedKey = keccak256(abi.encodePacked(account, context));
+
+ vm.expectEmit(true, true, true, true);
+ emit PolicyBase.AttestedAgreement(composedKey, account, attestationId);
+
+ policy.exposeSetAttestation(account, context, attestationId);
+ assertEq(policy.getLicense(account, context), attestationId, "Stored attestation mismatch");
+ }
+
+ function test_CommitCreatesAttestations() public {
+ address[] memory parties = new address[](2);
+ parties[0] = holder;
+ parties[1] = keeper;
+
+ T.Agreement memory agreement = T.Agreement({
+ arbiter: address(0xAB),
+ currency: address(0xCD),
+ initiator: address(0xEF),
+ total: 1_000,
+ fees: 100,
+ locked: 900,
+ parties: parties,
+ payload: abi.encode("payload")
+ });
+
+ uint256 expireAt = block.timestamp + 1 days;
+
+ vm.expectEmit(true, false, false, true);
+ emit PolicyBase.AgreementCommitted(holder, parties.length, agreement.total, agreement.fees);
+
+ uint256[] memory attestationIds = policy.exposeCommit(holder, agreement, expireAt);
+
+ assertEq(attestationIds.length, parties.length, "Attestation length mismatch");
+ assertEq(attestationIds[0], 1, "First attestation id mismatch");
+ assertEq(attestationIds[1], 2, "Second attestation id mismatch");
+ assertEq(attestation.attestCalls(), 1, "Attest should be invoked once");
+ assertEq(attestation.lastExpireAt(), expireAt, "Expire timestamp mismatch");
+ assertEq(attestation.lastRecipientsLength(), parties.length, "Recipients length mismatch");
+ assertEq(attestation.lastRecipientAt(0), holder, "First recipient mismatch");
+ assertEq(attestation.lastRecipientAt(1), keeper, "Second recipient mismatch");
+ }
+
+ function test_GetHolderReadsFromRegistry() public {
+ uint256 assetId = 42;
+ registry.register(holder, assetId);
+ assertEq(policy.exposeHolder(assetId), holder, "Holder should come from registry");
+ }
+}
diff --git a/test/primitives/AllowanceOperatorUpgradeable.t.sol b/test/primitives/AllowanceOperatorUpgradeable.t.sol
new file mode 100644
index 0000000..bbd2e43
--- /dev/null
+++ b/test/primitives/AllowanceOperatorUpgradeable.t.sol
@@ -0,0 +1,152 @@
+// SPDX-License-Identifier: BUSL-1.1
+pragma solidity 0.8.26;
+
+import "forge-std/Test.sol";
+
+import { AllowanceOperatorUpgradeable } from "contracts/core/primitives/upgradeable/AllowanceOperatorUpgradeable.sol";
+import { ILedgerVerifiable } from "contracts/core/interfaces/base/ILedgerVerifiable.sol";
+import { IAllowanceApprovable } from "contracts/core/interfaces/base/IAllowanceApprovable.sol";
+import { IAllowanceCollectable } from "contracts/core/interfaces/base/IAllowanceCollectable.sol";
+
+contract AllowanceOperatorHarness is AllowanceOperatorUpgradeable {
+ function initialize() external initializer {
+ __AllowanceOperator_init();
+ }
+
+ function boostLedger(address account, uint256 amount, address currency) external {
+ _sumLedgerEntry(account, amount, currency);
+ }
+
+ function approve(address to, uint256 amount, address currency) external override returns (uint256) {
+ return _approve(to, amount, currency);
+ }
+
+ function revoke(address to, uint256 amount, address currency) external override returns (uint256) {
+ return _revoke(to, amount, currency);
+ }
+
+ function collect(address from, uint256 amount, address currency) external override returns (uint256) {
+ return _collect(from, amount, currency);
+ }
+
+ function allowance(address owner, address spender, address currency) external view returns (uint256) {
+ return getApprovedAmount(owner, spender, currency);
+ }
+}
+
+contract AllowanceOperatorUpgradeableTest is Test {
+ AllowanceOperatorHarness internal harness;
+ address internal constant TOKEN = address(0xC0FFEE);
+ address internal alice = vm.addr(1);
+ address internal bob = vm.addr(2);
+ address internal carol = vm.addr(3);
+
+ function setUp() public {
+ harness = new AllowanceOperatorHarness();
+ harness.initialize();
+ }
+
+ function test_ApproveStoresAllowanceAndEmits() public {
+ vm.prank(alice);
+ vm.expectEmit(true, true, false, true, address(harness));
+ emit IAllowanceApprovable.FundsApproved(alice, bob, 25 ether, TOKEN);
+ harness.approve(bob, 25 ether, TOKEN);
+
+ assertEq(harness.allowance(alice, bob, TOKEN), 25 ether, "Allowance mismatch");
+ }
+
+ function test_Approve_RevertWhen_InvalidParameters() public {
+ vm.prank(alice);
+ vm.expectRevert(bytes4(keccak256("InvalidOperationParameters()")));
+ harness.approve(address(0), 10, TOKEN);
+
+ vm.prank(alice);
+ vm.expectRevert(bytes4(keccak256("InvalidOperationParameters()")));
+ harness.approve(bob, 0, TOKEN);
+
+ vm.prank(alice);
+ vm.expectRevert(bytes4(keccak256("InvalidOperationParameters()")));
+ harness.approve(alice, 5, TOKEN);
+ }
+
+ function test_RevokeReducesAllowance() public {
+ vm.startPrank(alice);
+ harness.approve(bob, 40 ether, TOKEN);
+ uint256 revoked = harness.revoke(bob, 15 ether, TOKEN);
+ vm.stopPrank();
+
+ assertEq(revoked, 15 ether, "Revoked amount mismatch");
+ assertEq(harness.allowance(alice, bob, TOKEN), 25 ether, "Remaining allowance mismatch");
+ }
+
+ function test_Revoke_RevertWhen_InsufficientAllowance() public {
+ vm.startPrank(alice);
+ harness.approve(bob, 10 ether, TOKEN);
+ vm.expectRevert(bytes4(keccak256("NoFundsToRevoke()")));
+ harness.revoke(bob, 12 ether, TOKEN);
+ vm.stopPrank();
+ }
+
+ function test_CollectTransfersLedgerBetweenAccounts() public {
+ harness.boostLedger(alice, 60 ether, TOKEN);
+ vm.prank(alice);
+ harness.approve(bob, 45 ether, TOKEN);
+
+ vm.expectEmit(true, true, false, true, address(harness));
+ emit IAllowanceCollectable.FundsCollected(alice, bob, 30 ether, TOKEN);
+ vm.prank(bob);
+ harness.collect(alice, 30 ether, TOKEN);
+
+ ILedgerVerifiable ledger = ILedgerVerifiable(address(harness));
+ assertEq(harness.allowance(alice, bob, TOKEN), 15 ether, "Allowance should decrease");
+ assertEq(ledger.getLedgerBalance(alice, TOKEN), 30 ether, "Alice ledger mismatch");
+ assertEq(ledger.getLedgerBalance(bob, TOKEN), 30 ether, "Bob ledger mismatch");
+ }
+
+ function test_Collect_RevertWhen_NoAllowance() public {
+ harness.boostLedger(alice, 20 ether, TOKEN);
+ vm.prank(bob);
+ vm.expectRevert(bytes4(keccak256("NoFundsToCollect()")));
+ harness.collect(alice, 10 ether, TOKEN);
+ }
+
+ function test_Collect_RevertWhen_InsufficientLedger() public {
+ vm.prank(alice);
+ harness.approve(bob, 10 ether, TOKEN);
+ vm.prank(bob);
+ vm.expectRevert(bytes4(keccak256("NoFundsToCollect()")));
+ harness.collect(alice, 5 ether, TOKEN);
+ }
+
+ function test_Integration_ApproveCollectRevokeFlow() public {
+ harness_boostAndApprove(alice, bob, 80 ether, 60 ether);
+ harness_boostAndApprove(alice, carol, 80 ether, 15 ether);
+
+ vm.prank(bob);
+ harness.collect(alice, 30 ether, TOKEN);
+
+ vm.prank(carol);
+ harness.collect(alice, 10 ether, TOKEN);
+
+ vm.startPrank(alice);
+ harness.revoke(bob, 10 ether, TOKEN);
+ vm.stopPrank();
+
+ ILedgerVerifiable ledger = ILedgerVerifiable(address(harness));
+ assertEq(ledger.getLedgerBalance(alice, TOKEN), 80 ether - 40 ether, "Alice residual ledger mismatch");
+ assertEq(ledger.getLedgerBalance(bob, TOKEN), 30 ether, "Bob ledger mismatch");
+ assertEq(ledger.getLedgerBalance(carol, TOKEN), 10 ether, "Carol ledger mismatch");
+ assertEq(harness.allowance(alice, bob, TOKEN), 20 ether, "Bob allowance mismatch");
+ assertEq(harness.allowance(alice, carol, TOKEN), 5 ether, "Carol allowance mismatch");
+ }
+
+ function harness_boostAndApprove(address owner, address spender, uint256 seedAmount, uint256 allowanceAmount) internal {
+ ILedgerVerifiable ledger = ILedgerVerifiable(address(harness));
+ uint256 currentBalance = ledger.getLedgerBalance(owner, TOKEN);
+ if (currentBalance < seedAmount) {
+ harness.boostLedger(owner, seedAmount - currentBalance, TOKEN);
+ }
+ vm.prank(owner);
+ harness.approve(spender, allowanceAmount, TOKEN);
+ }
+}
diff --git a/test/primitives/BalanceOperator.t.sol b/test/primitives/BalanceOperator.t.sol
deleted file mode 100644
index ab8c9b4..0000000
--- a/test/primitives/BalanceOperator.t.sol
+++ /dev/null
@@ -1,177 +0,0 @@
-// test balanaceoperator here// SPDX-License-Identifier: BUSL-1.1
-pragma solidity 0.8.26;
-
-import "forge-std/Test.sol";
-import { BaseTest } from "test/BaseTest.t.sol";
-import { IERC20 } from "@openzeppelin/contracts/interfaces/IERC20.sol";
-import { ILedgerVerifiable } from "contracts/core/interfaces/base/ILedgerVerifiable.sol";
-import { IBalanceDepositor } from "contracts/core/interfaces/base/IBalanceDepositor.sol";
-import { IBalanceVerifiable } from "contracts/core/interfaces/base/IBalanceVerifiable.sol";
-import { IBalanceTransferable } from "contracts/core/interfaces/base/IBalanceTransferable.sol";
-import { IBalanceWithdrawable } from "contracts/core/interfaces/base/IBalanceWithdrawable.sol";
-import { BalanceOperatorUpgradeable } from "contracts/core/primitives/upgradeable/BalanceOperatorUpgradeable.sol";
-
-contract BalanceOperatorWrapper is BalanceOperatorUpgradeable {}
-
-contract BalanceOperatorTest is BaseTest {
- address op;
-
- function setUp() public initialize {
- deployToken();
- op = address(new BalanceOperatorWrapper());
- }
-
- function test_Deposit_ValidDeposit() public {
- // 100 MMC
- uint256 amount = 100 * 1e18;
- vm.startPrank(admin);
- uint256 prevBalance = IERC20(token).balanceOf(admin);
- uint256 confirmed = _validDeposit(admin, amount);
- uint256 afterBalance = IERC20(token).balanceOf(admin);
-
- uint256 balance = ILedgerVerifiable(op).getLedgerBalance(admin, token);
- uint256 contractBalance = IBalanceVerifiable(op).getBalance(token);
- vm.stopPrank();
-
- assertEq(confirmed, balance, "Confirmed amount should match ledger balance");
- assertEq(contractBalance, confirmed, "Contract balance should match confirmed amount");
- assertEq(afterBalance, prevBalance - confirmed, "Admin balance should decrease by confirmed amount");
- }
-
- function test_Deposit_FundsDepositedEventEmitted() public {
- uint256 amount = 100 * 1e18;
- vm.startPrank(admin);
- IERC20(token).approve(op, amount);
- vm.expectEmit(true, true, false, true, address(op));
- emit IBalanceDepositor.FundsDeposited(admin, admin, amount, token);
- IBalanceDepositor(op).deposit(admin, amount, token);
- vm.stopPrank();
- }
-
- function test_Deposit_RevertWhen_InvalidApproval() public {
- vm.expectRevert(abi.encodeWithSignature("FailDuringDeposit(string)", "Amount exceeds allowance."));
- IBalanceDepositor(op).deposit(admin, 100 * 1e18, token);
- }
-
- function test_Deposit_RevertIf_InvalidParams() public {
- uint256 amount = 0;
- address account = address(0);
- bytes4 err = bytes4(keccak256("InvalidOperationParameters()"));
- // must fail if account = address(0) or amount == 0
- vm.expectRevert(err);
- IBalanceDepositor(op).deposit(admin, amount, token);
-
- vm.expectRevert(err);
- IBalanceDepositor(op).deposit(account, 1 * 1e18, token);
- }
-
- function test_Withdraw_ValidWithdraw() public {
- // 100 MMC
- uint256 amount = 100 * 1e18;
- vm.startPrank(admin);
- uint256 prevBalance = IERC20(token).balanceOf(admin);
- uint256 deposited = _validDeposit(admin, amount);
- uint256 afterBalance = IERC20(token).balanceOf(admin);
-
- uint256 confirmed = IBalanceWithdrawable(op).withdraw(admin, deposited, token);
- uint256 balance = ILedgerVerifiable(op).getLedgerBalance(admin, token);
- uint256 contractBalance = IBalanceVerifiable(op).getBalance(token);
- vm.stopPrank();
-
- assertEq(confirmed, deposited, "Confirmed amount should match deposited amount");
- assertEq(prevBalance, afterBalance + confirmed, "Admin balance should increase by confirmed amount");
- assertEq(contractBalance, 0, "Contract balance should be zero after withdrawal");
- assertEq(balance, 0, "Ledger balance should be zero after withdrawal");
- }
-
- function test_Withdraw_FundsWithdrawnEventEmitted() public {
- uint256 amount = 100 * 1e18;
- vm.startPrank(admin);
- _validDeposit(admin, amount);
-
- vm.expectEmit(true, true, false, true, address(op));
- emit IBalanceWithdrawable.FundsWithdrawn(admin, admin, amount, token);
- IBalanceWithdrawable(op).withdraw(admin, amount, token);
- vm.stopPrank();
- }
-
- function test_Withdraw_RevertIf_NoFunds() public {
- vm.expectRevert(bytes4(keccak256("NoFundsToWithdraw()")));
- IBalanceWithdrawable(op).withdraw(admin, 1 * 1e18, token);
- }
-
- function test_Withdraw_RevertIf_InvalidParams() public {
- uint256 amount = 0;
- address account = address(0);
- bytes4 err = bytes4(keccak256("InvalidOperationParameters()"));
- // must fail if account = address(0) or amount == 0
- vm.expectRevert(err);
- IBalanceWithdrawable(op).withdraw(admin, amount, token);
-
- vm.expectRevert(err);
- IBalanceWithdrawable(op).withdraw(account, 1 * 1e18, token);
- }
-
- function test_Transfer_ValidTransfer() public {
- // 100 MMC
- uint256 amount = 100 * 1e18;
- uint256 expectedAfter = amount / 2;
- address user = vm.addr(7);
-
- vm.startPrank(admin);
- _validDeposit(admin, amount);
- // transfer the haft of the balance to user
- uint256 confirmed = IBalanceTransferable(op).transfer(user, expectedAfter, token);
- uint256 contractBalance = IBalanceVerifiable(op).getBalance(token);
- vm.stopPrank();
-
- ILedgerVerifiable verifier = ILedgerVerifiable(op);
- uint256 balanceAdmin = verifier.getLedgerBalance(admin, token);
- uint256 balanceUser = verifier.getLedgerBalance(user, token);
-
- assertEq(contractBalance, amount, "Contract balance should match initial deposit");
- assertEq(balanceAdmin, expectedAfter, "Admin balance should be half after transfer");
- assertEq(balanceUser, confirmed, "User balance should match transferred amount");
- }
-
- function test_Transfer_FundsTransferredEventEmitted() public {
- // 100 MMC
- uint256 amount = 100 * 1e18;
- address user = vm.addr(7);
-
- vm.startPrank(admin);
- _validDeposit(admin, amount);
- // transfer the haft of the balance to user
- vm.expectEmit(true, true, false, true, address(op));
- emit IBalanceTransferable.FundsTransferred(user, admin, amount, token);
- IBalanceTransferable(op).transfer(user, amount, token);
- vm.stopPrank();
- }
-
- function test_Transfer_RevertIf_NoFunds() public {
- vm.expectRevert(bytes4(keccak256("NoFundsToTransfer()")));
- IBalanceTransferable(op).transfer(vm.addr(7), 1 * 1e18, token);
- }
-
- function test_Transfer_RevertIf_InvalidParams() public {
- uint256 amount = 0;
- address account = address(0);
- bytes4 err = bytes4(keccak256("InvalidOperationParameters()"));
- // must fail if account = address(0) or amount == 0
- vm.expectRevert(err);
- IBalanceTransferable(op).transfer(admin, amount, token);
-
- vm.expectRevert(err);
- IBalanceTransferable(op).transfer(account, 1 * 1e18, token);
-
- vm.prank(admin);
- vm.expectRevert(err);
- // sender cannot be the recipient
- IBalanceTransferable(op).transfer(admin, 1 * 1e18, token);
- }
-
- function _validDeposit(address account, uint256 amount) private returns (uint256) {
- IERC20(token).approve(op, amount);
- return IBalanceDepositor(op).deposit(account, amount, token);
- }
-}
diff --git a/test/primitives/BalanceOperatorUpgradeable.t.sol b/test/primitives/BalanceOperatorUpgradeable.t.sol
new file mode 100644
index 0000000..1178ca1
--- /dev/null
+++ b/test/primitives/BalanceOperatorUpgradeable.t.sol
@@ -0,0 +1,201 @@
+// SPDX-License-Identifier: BUSL-1.1
+pragma solidity 0.8.26;
+
+import "forge-std/Test.sol";
+
+import { BalanceOperatorUpgradeable } from "contracts/core/primitives/upgradeable/BalanceOperatorUpgradeable.sol";
+import { IBalanceDepositor } from "contracts/core/interfaces/base/IBalanceDepositor.sol";
+import { IBalanceWithdrawable } from "contracts/core/interfaces/base/IBalanceWithdrawable.sol";
+import { IBalanceTransferable } from "contracts/core/interfaces/base/IBalanceTransferable.sol";
+import { IBalanceVerifiable } from "contracts/core/interfaces/base/IBalanceVerifiable.sol";
+import { ILedgerVerifiable } from "contracts/core/interfaces/base/ILedgerVerifiable.sol";
+import { IERC20 } from "@openzeppelin/contracts/interfaces/IERC20.sol";
+
+contract MockToken is IERC20 {
+ string public constant name = "MockToken";
+ string public constant symbol = "MOCK";
+ uint8 public constant decimals = 18;
+
+ mapping(address => uint256) private _balances;
+ mapping(address => mapping(address => uint256)) private _allowances;
+ uint256 private _totalSupply;
+
+ function totalSupply() external view override returns (uint256) {
+ return _totalSupply;
+ }
+
+ function balanceOf(address account) external view override returns (uint256) {
+ return _balances[account];
+ }
+
+ function transfer(address to, uint256 amount) external override returns (bool) {
+ _transfer(msg.sender, to, amount);
+ return true;
+ }
+
+ function allowance(address owner, address spender) external view override returns (uint256) {
+ return _allowances[owner][spender];
+ }
+
+ function approve(address spender, uint256 amount) external override returns (bool) {
+ _allowances[msg.sender][spender] = amount;
+ emit Approval(msg.sender, spender, amount);
+ return true;
+ }
+
+ function transferFrom(address from, address to, uint256 amount) external override returns (bool) {
+ uint256 currentAllowance = _allowances[from][msg.sender];
+ require(currentAllowance >= amount, "insufficient allowance");
+ _allowances[from][msg.sender] = currentAllowance - amount;
+ _transfer(from, to, amount);
+ return true;
+ }
+
+ function mint(address to, uint256 amount) external {
+ _balances[to] += amount;
+ _totalSupply += amount;
+ emit Transfer(address(0), to, amount);
+ }
+
+ function _transfer(address from, address to, uint256 amount) private {
+ require(to != address(0), "invalid to");
+ require(_balances[from] >= amount, "insufficient balance");
+ _balances[from] -= amount;
+ _balances[to] += amount;
+ emit Transfer(from, to, amount);
+ }
+}
+
+contract BalanceOperatorHarness is BalanceOperatorUpgradeable {
+ function deposit(address recipient, uint256 amount, address currency) external payable returns (uint256) {
+ return _deposit(recipient, amount, currency);
+ }
+
+ function withdraw(address recipient, uint256 amount, address currency) external returns (uint256) {
+ return _withdraw(recipient, amount, currency);
+ }
+
+ function transfer(address recipient, uint256 amount, address currency) external returns (uint256) {
+ return _transfer(recipient, amount, currency);
+ }
+}
+
+contract BalanceOperatorUpgradeableTest is Test {
+ BalanceOperatorHarness internal operator;
+ MockToken internal token;
+ address internal op;
+ address internal alice;
+ address internal bob;
+
+ function setUp() public {
+ operator = new BalanceOperatorHarness();
+ token = new MockToken();
+ op = address(operator);
+ alice = vm.addr(1);
+ bob = vm.addr(2);
+
+ token.mint(alice, 1_000 ether);
+ token.mint(bob, 500 ether);
+ }
+
+ function _deposit(address account, uint256 amount) internal returns (uint256) {
+ vm.startPrank(account);
+ token.approve(op, amount);
+ uint256 confirmed = IBalanceDepositor(op).deposit(account, amount, address(token));
+ vm.stopPrank();
+ return confirmed;
+ }
+
+ function test_Deposit_UpdatesLedgerAndVault() public {
+ uint256 amount = 200 ether;
+ uint256 confirmed = _deposit(alice, amount);
+
+ assertEq(confirmed, amount, "Confirmed amount mismatch");
+ assertEq(
+ ILedgerVerifiable(op).getLedgerBalance(alice, address(token)),
+ amount,
+ "Ledger should reflect deposit"
+ );
+ assertEq(IBalanceVerifiable(op).getBalance(address(token)), amount, "Vault balance mismatch");
+ assertEq(token.balanceOf(alice), 800 ether, "Token balance should decrease");
+ }
+
+ function test_Deposit_RevertWhen_NoAllowance() public {
+ vm.expectRevert(abi.encodeWithSignature("FailDuringDeposit(string)", "Amount exceeds allowance."));
+ IBalanceDepositor(op).deposit(alice, 1 ether, address(token));
+ }
+
+ function test_Withdraw_ReturnsFundsAndClearsLedger() public {
+ uint256 amount = 150 ether;
+ _deposit(alice, amount);
+
+ vm.prank(alice);
+ uint256 withdrawn = IBalanceWithdrawable(op).withdraw(alice, amount, address(token));
+
+ assertEq(withdrawn, amount, "Withdrawn amount mismatch");
+ assertEq(ILedgerVerifiable(op).getLedgerBalance(alice, address(token)), 0, "Ledger should be zero");
+ assertEq(IBalanceVerifiable(op).getBalance(address(token)), 0, "Vault balance should be zero");
+ assertEq(token.balanceOf(alice), 1_000 ether, "Token balance should be restored");
+ }
+
+ function test_Withdraw_RevertWhen_InsufficientLedger() public {
+ _deposit(alice, 10 ether);
+ vm.expectRevert(bytes4(keccak256("NoFundsToWithdraw()")));
+ vm.prank(alice);
+ IBalanceWithdrawable(op).withdraw(alice, 20 ether, address(token));
+ }
+
+ function test_Transfer_MovesLedgerBalances() public {
+ uint256 amount = 120 ether;
+ _deposit(alice, amount);
+
+ vm.prank(alice);
+ uint256 moved = IBalanceTransferable(op).transfer(bob, 45 ether, address(token));
+
+ assertEq(moved, 45 ether, "Transfer amount mismatch");
+ ILedgerVerifiable ledger = ILedgerVerifiable(op);
+ assertEq(ledger.getLedgerBalance(alice, address(token)), 75 ether, "Alice ledger mismatch");
+ assertEq(ledger.getLedgerBalance(bob, address(token)), 45 ether, "Bob ledger mismatch");
+ assertEq(
+ ledger.getLedgerBalance(alice, address(token)) + ledger.getLedgerBalance(bob, address(token)),
+ amount,
+ "Ledger totals should conserve value"
+ );
+ }
+
+ function test_Transfer_RevertWhen_SelfOrZero() public {
+ _deposit(alice, 50 ether);
+
+ vm.expectRevert(bytes4(keccak256("InvalidOperationParameters()")));
+ vm.prank(alice);
+ IBalanceTransferable(op).transfer(alice, 10 ether, address(token));
+
+ vm.expectRevert(bytes4(keccak256("InvalidOperationParameters()")));
+ vm.prank(alice);
+ IBalanceTransferable(op).transfer(bob, 0, address(token));
+ }
+
+ function test_Integration_DepositTransferWithdraw() public {
+ uint256 amount = 300 ether;
+ _deposit(alice, amount);
+
+ vm.prank(alice);
+ IBalanceTransferable(op).transfer(bob, 100 ether, address(token));
+
+ vm.prank(bob);
+ uint256 withdrawnBob = IBalanceWithdrawable(op).withdraw(bob, 60 ether, address(token));
+ vm.prank(alice);
+ uint256 withdrawnAlice = IBalanceWithdrawable(op).withdraw(alice, 200 ether, address(token));
+
+ ILedgerVerifiable ledger = ILedgerVerifiable(op);
+ assertEq(withdrawnBob, 60 ether, "Bob withdrawal mismatch");
+ assertEq(withdrawnAlice, 200 ether, "Alice withdrawal mismatch");
+ assertEq(ledger.getLedgerBalance(alice, address(token)), 0, "Alice ledger should be zero");
+ assertEq(ledger.getLedgerBalance(bob, address(token)), 40 ether, "Bob remaining ledger mismatch");
+ assertEq(
+ IBalanceVerifiable(op).getBalance(address(token)),
+ 40 ether,
+ "Vault balance should equal remaining ledger"
+ );
+ }
+}
diff --git a/test/primitives/Ledger.t.sol b/test/primitives/Ledger.t.sol
deleted file mode 100644
index eebd84d..0000000
--- a/test/primitives/Ledger.t.sol
+++ /dev/null
@@ -1,29 +0,0 @@
-// SPDX-License-Identifier: BUSL-1.1
-pragma solidity 0.8.26;
-
-import "forge-std/Test.sol";
-import { LedgerUpgradeable } from "contracts/core/primitives/upgradeable/LedgerUpgradeable.sol";
-
-contract LedgerTest is Test, LedgerUpgradeable {
- function test_SetLedgerEntry() public {
- address account = vm.addr(1); // example address
- _setLedgerEntry(account, 1e18, address(0));
- assertEq(getLedgerBalance(account, address(0)), 1e18, "Expected balance should be 1e18 after setting entry");
- }
-
- function test_SumLedgerEntry() public {
- address account = vm.addr(1); // example address
- _sumLedgerEntry(account, 1e18, address(0));
- _sumLedgerEntry(account, 1e18, address(0));
- assertEq(getLedgerBalance(account, address(0)), 2e18, "Expected balance should be 2e18 after summation");
- }
-
- function test_SubLenderEntry() public {
- address account = vm.addr(1); // example address
- _sumLedgerEntry(account, 1e18, address(0));
- _sumLedgerEntry(account, 1e18, address(0));
- _subLedgerEntry(account, 1e18, address(0));
- _subLedgerEntry(account, 1e18, address(0));
- assertEq(getLedgerBalance(account, address(0)), 0, "Expected balance should be zero after subtraction");
- }
-}
diff --git a/test/primitives/LedgerUpgradeable.t.sol b/test/primitives/LedgerUpgradeable.t.sol
new file mode 100644
index 0000000..7d21ca5
--- /dev/null
+++ b/test/primitives/LedgerUpgradeable.t.sol
@@ -0,0 +1,77 @@
+// SPDX-License-Identifier: BUSL-1.1
+pragma solidity 0.8.26;
+
+import "forge-std/Test.sol";
+
+import { LedgerUpgradeable } from "contracts/core/primitives/upgradeable/LedgerUpgradeable.sol";
+
+contract LedgerUpgradeableHarness is LedgerUpgradeable {
+ function initialize() external initializer {
+ __Ledger_init();
+ }
+
+ function setEntry(address account, uint256 amount, address currency) external {
+ _setLedgerEntry(account, amount, currency);
+ }
+
+ function sumEntry(address account, uint256 amount, address currency) external {
+ _sumLedgerEntry(account, amount, currency);
+ }
+
+ function subEntry(address account, uint256 amount, address currency) external {
+ _subLedgerEntry(account, amount, currency);
+ }
+}
+
+contract LedgerUpgradeableTest is Test {
+ LedgerUpgradeableHarness internal harness;
+ address internal alice = vm.addr(11);
+ address internal bob = vm.addr(12);
+ address internal currency = vm.addr(33);
+
+ function setUp() public {
+ harness = new LedgerUpgradeableHarness();
+ harness.initialize();
+ }
+
+ function test_SetEntry_SetsBalance() public {
+ harness.setEntry(alice, 50 ether, currency);
+ assertEq(harness.getLedgerBalance(alice, currency), 50 ether, "Set should override balance");
+ }
+
+ function test_SumEntry_IncrementsBalance() public {
+ harness.setEntry(alice, 10 ether, currency);
+ harness.sumEntry(alice, 5 ether, currency);
+ assertEq(harness.getLedgerBalance(alice, currency), 15 ether, "Sum should add amount");
+ }
+
+ function test_SubEntry_DecrementsBalance() public {
+ harness.setEntry(alice, 20 ether, currency);
+ harness.subEntry(alice, 7 ether, currency);
+ assertEq(harness.getLedgerBalance(alice, currency), 13 ether, "Sub should remove amount");
+ }
+
+ function test_SubEntry_AllowsReducingToZero() public {
+ harness.setEntry(alice, 8 ether, currency);
+ harness.subEntry(alice, 8 ether, currency);
+ assertEq(harness.getLedgerBalance(alice, currency), 0, "Balance should reach zero");
+ }
+
+ function test_SetEntry_IsolatedPerAccountAndCurrency() public {
+ harness.setEntry(alice, 15 ether, currency);
+ harness.setEntry(bob, 30 ether, vm.addr(44));
+
+ assertEq(harness.getLedgerBalance(alice, currency), 15 ether, "Alice balance mismatch");
+ assertEq(harness.getLedgerBalance(bob, vm.addr(44)), 30 ether, "Bob balance mismatch");
+ assertEq(harness.getLedgerBalance(bob, currency), 0, "Cross account balances should stay zero");
+ }
+
+ function test_SequentialOperationsConserveMath() public {
+ harness.setEntry(alice, 40 ether, currency);
+ harness.sumEntry(alice, 12 ether, currency);
+ harness.subEntry(alice, 5 ether, currency);
+ harness.sumEntry(alice, 3 ether, currency);
+
+ assertEq(harness.getLedgerBalance(alice, currency), 50 ether, "Sequential math mismatch");
+ }
+}
diff --git a/test/primitives/LockOperatorUpgradeable.t.sol b/test/primitives/LockOperatorUpgradeable.t.sol
new file mode 100644
index 0000000..272c9c7
--- /dev/null
+++ b/test/primitives/LockOperatorUpgradeable.t.sol
@@ -0,0 +1,132 @@
+// SPDX-License-Identifier: BUSL-1.1
+pragma solidity 0.8.26;
+
+import "forge-std/Test.sol";
+
+import { LockOperatorUpgradeable } from "contracts/core/primitives/upgradeable/LockOperatorUpgradeable.sol";
+import { ILedgerVerifiable } from "contracts/core/interfaces/base/ILedgerVerifiable.sol";
+import { ILockLocker } from "contracts/core/interfaces/base/ILockLocker.sol";
+import { ILockReleaser } from "contracts/core/interfaces/base/ILockReleaser.sol";
+import { ILockClaimer } from "contracts/core/interfaces/base/ILockClaimer.sol";
+
+contract LockOperatorHarness is LockOperatorUpgradeable {
+ function initialize() external initializer {
+ __LockOperator_init();
+ }
+
+ function seedLedger(address account, uint256 amount, address currency) external {
+ _sumLedgerEntry(account, amount, currency);
+ }
+
+ function lock(address account, uint256 amount, address currency) external override returns (uint256) {
+ return _lock(account, amount, currency);
+ }
+
+ function release(address account, uint256 amount, address currency) external override returns (uint256) {
+ return _release(account, amount, currency);
+ }
+
+ function claim(address account, uint256 amount, address currency) external override returns (uint256) {
+ return _claim(account, amount, currency);
+ }
+}
+
+contract LockOperatorUpgradeableTest is Test {
+ LockOperatorHarness internal harness;
+ address internal constant TOKEN = address(0xC0FFEE);
+ address internal alice = vm.addr(1);
+ address internal bob = vm.addr(2);
+ address internal claimer = vm.addr(3);
+
+ function setUp() public {
+ harness = new LockOperatorHarness();
+ harness.initialize();
+ }
+
+ function test_LockDeductsLedgerAndTracksLocked() public {
+ harness.seedLedger(alice, 120 ether, TOKEN);
+
+ vm.expectEmit(true, true, false, true, address(harness));
+ emit ILockLocker.FundsLocked(address(this), alice, 40 ether, TOKEN);
+ harness.lock(alice, 40 ether, TOKEN);
+
+ assertEq(ILedgerVerifiable(address(harness)).getLedgerBalance(alice, TOKEN), 80 ether, "Ledger deduction mismatch");
+ assertEq(harness.getLockedBalance(alice, TOKEN), 40 ether, "Locked balance mismatch");
+ }
+
+ function test_Lock_RevertWhen_NoFunds() public {
+ vm.expectRevert(ILockLocker.NoFundsToLock.selector);
+ harness.lock(alice, 1 ether, TOKEN);
+ }
+
+ function test_Lock_RevertWhen_InvalidParams() public {
+ harness.seedLedger(alice, 10 ether, TOKEN);
+ bytes4 err = bytes4(keccak256("InvalidOperationParameters()"));
+
+ vm.expectRevert(err);
+ harness.lock(address(0), 1 ether, TOKEN);
+
+ vm.expectRevert(err);
+ harness.lock(alice, 0, TOKEN);
+ }
+
+ function test_Release_RestoresLedger() public {
+ harness.seedLedger(alice, 90 ether, TOKEN);
+ harness.lock(alice, 60 ether, TOKEN);
+
+ vm.expectEmit(true, true, false, true, address(harness));
+ emit ILockReleaser.FundsReleased(address(this), alice, 25 ether, TOKEN);
+ harness.release(alice, 25 ether, TOKEN);
+
+ assertEq(harness.getLockedBalance(alice, TOKEN), 35 ether, "Locked after release mismatch");
+ assertEq(ILedgerVerifiable(address(harness)).getLedgerBalance(alice, TOKEN), 55 ether, "Ledger after release mismatch");
+ }
+
+ function test_Release_RevertWhen_InsufficientLocked() public {
+ harness.seedLedger(alice, 20 ether, TOKEN);
+ harness.lock(alice, 10 ether, TOKEN);
+ vm.expectRevert(ILockReleaser.NoFundsToRelease.selector);
+ harness.release(alice, 15 ether, TOKEN);
+ }
+
+ function test_Claim_MovesLockedToClaimerLedger() public {
+ harness.seedLedger(alice, 70 ether, TOKEN);
+ harness.lock(alice, 30 ether, TOKEN);
+
+ vm.expectEmit(true, true, false, true, address(harness));
+ emit ILockClaimer.FundsClaimed(claimer, alice, 18 ether, TOKEN);
+ vm.prank(claimer);
+ harness.claim(alice, 18 ether, TOKEN);
+
+ ILedgerVerifiable ledger = ILedgerVerifiable(address(harness));
+ assertEq(harness.getLockedBalance(alice, TOKEN), 12 ether, "Locked remainder mismatch");
+ assertEq(ledger.getLedgerBalance(claimer, TOKEN), 18 ether, "Claimer ledger mismatch");
+ assertEq(ledger.getLedgerBalance(alice, TOKEN), 40 ether, "Alice ledger should reflect lock deduction");
+ }
+
+ function test_Claim_RevertWhen_InsufficientLocked() public {
+ harness.seedLedger(alice, 30 ether, TOKEN);
+ harness.lock(alice, 10 ether, TOKEN);
+
+ vm.expectRevert(ILockClaimer.NoFundsToClaim.selector);
+ harness.claim(alice, 12 ether, TOKEN);
+ }
+
+ function test_Integration_LockReleaseClaimFlow() public {
+ harness.seedLedger(alice, 200 ether, TOKEN);
+ harness.lock(alice, 120 ether, TOKEN);
+ harness.release(alice, 30 ether, TOKEN);
+ vm.prank(claimer);
+ harness.claim(alice, 50 ether, TOKEN);
+
+ uint256 locked = harness.getLockedBalance(alice, TOKEN);
+ ILedgerVerifiable ledger = ILedgerVerifiable(address(harness));
+ uint256 aliceLedger = ledger.getLedgerBalance(alice, TOKEN);
+ uint256 claimerLedger = ledger.getLedgerBalance(claimer, TOKEN);
+
+ assertEq(locked, 40 ether, "Final locked mismatch");
+ assertEq(aliceLedger, 110 ether, "Alice ledger mismatch");
+ assertEq(claimerLedger, 50 ether, "Claimer ledger mismatch");
+ assertEq(locked + aliceLedger + claimerLedger, 200 ether, "Total conservation mismatch");
+ }
+}
diff --git a/test/primitives/Quorum.t.sol b/test/primitives/Quorum.t.sol
deleted file mode 100644
index f7527ba..0000000
--- a/test/primitives/Quorum.t.sol
+++ /dev/null
@@ -1,135 +0,0 @@
-// SPDX-License-Identifier: BUSL-1.1
-pragma solidity 0.8.26;
-
-import "forge-std/Test.sol";
-import { QuorumUpgradeable } from "contracts/core/primitives/upgradeable/QuorumUpgradeable.sol";
-import { IQuorumInspectable } from "contracts/core/interfaces/base/IQuorumInspectable.sol";
-import { IQuorumRegistrable } from "contracts/core/interfaces/base/IQuorumRegistrable.sol";
-import { IQuorumRevokable } from "contracts/core/interfaces/base/IQuorumRevokable.sol";
-import { T } from "@synaps3/core/primitives/Types.sol";
-
-contract QuorumWrapper is QuorumUpgradeable {
- function status(uint256 entry) external view returns (uint8) {
- return uint8(_status(entry));
- }
-
- function revoke(uint256 entry) external {
- _revoke(entry);
- }
-
- function approve(uint256 entry) external {
- _approve(entry);
- }
-
- function quit(uint256 entry) external {
- _quit(entry);
- }
-
- function register(uint256 entry) external {
- _register(entry);
- }
-
- function reject(uint256 entry) external {
- _block(entry);
- }
-}
-
-contract QuorumTest is Test {
- address quorum;
-
- function setUp() public {
- quorum = address(new QuorumWrapper());
- }
-
- function test_DefaultStatus() public view {
- T.Status status = IQuorumInspectable(quorum).status(1234536789);
- assertTrue(status == T.Status.Pending, "Default status should be Pending");
- }
-
- function test_RegisterStatusFlow() public {
- uint256 entry = 1234567189;
- // initial pending status
- T.Status prevStatus = IQuorumInspectable(quorum).status(entry);
- assertTrue(prevStatus == T.Status.Pending, "Initial status should be Pending");
-
- // register status
- IQuorumRegistrable(quorum).register(entry);
- T.Status newStatus = IQuorumInspectable(quorum).status(entry);
- assertTrue(newStatus == T.Status.Waiting, "Expected Waiting status after registration");
- }
-
- function test_ActiveStatusFlow() public {
- uint256 entry = 1234526789;
- // waiting status -> active status
- IQuorumRegistrable(quorum).register(entry);
- IQuorumRegistrable(quorum).approve(entry);
- T.Status newStatus = IQuorumInspectable(quorum).status(entry);
- assertTrue(newStatus == T.Status.Active, "Expected Active status after approval");
- }
-
- function test_QuitStatusFlow() public {
- uint256 entry = 1234256789;
- // waiting status -> pending status
- IQuorumRegistrable(quorum).register(entry);
- IQuorumRegistrable(quorum).quit(entry);
- T.Status newStatus = IQuorumInspectable(quorum).status(entry);
- assertTrue(newStatus == T.Status.Pending, "Expected Pending status after quitting");
- }
-
- function test_BlockedStatusFlow() public {
- uint256 entry = 123455589;
- // waiting status -> blocked
- IQuorumRegistrable(quorum).register(entry);
- // blocked status happens before active
- IQuorumRegistrable(quorum).reject(entry);
- T.Status newStatus = IQuorumInspectable(quorum).status(entry);
- assertTrue(newStatus == T.Status.Blocked, "Expected Blocked status after rejection");
- }
-
- function test_RevokeStatusFlow() public {
- uint256 entry = 123455589;
- // waiting status -> active -> blocked
- IQuorumRegistrable(quorum).register(entry);
- IQuorumRegistrable(quorum).approve(entry);
- // revoked status happens after approved
- IQuorumRevokable(quorum).revoke(entry);
- T.Status newStatus = IQuorumInspectable(quorum).status(entry);
- assertTrue(newStatus == T.Status.Blocked, "Expected Blocked status after revocation");
- }
-
- function test_Approve_RevertWhen_ApproveNotRegistered() public {
- uint256 entry = 123456789;
- vm.expectRevert(QuorumUpgradeable.NotWaitingApproval.selector);
- IQuorumRegistrable(quorum).approve(entry);
- }
-
- function test_Register_RevertWhen_WaitingApproval() public {
- uint256 entry = 123456789;
- IQuorumRegistrable(quorum).register(entry);
- vm.expectRevert(QuorumUpgradeable.NotPendingApproval.selector);
- IQuorumRegistrable(quorum).register(entry);
- }
-
- function test_Revoke_RevertWhen_BlockedNotActive() public {
- uint256 entry = 12345677;
- IQuorumRegistrable(quorum).register(entry);
- vm.expectRevert(QuorumUpgradeable.InvalidInactiveState.selector);
- IQuorumRevokable(quorum).revoke(entry);
- }
-
- function test_Quit_RevertWhen_QuitNotWaiting() public {
- uint256 entry = 123456459;
- // blocked status
- vm.expectRevert(QuorumUpgradeable.NotWaitingApproval.selector);
- IQuorumRegistrable(quorum).quit(entry);
- }
-
- function test_Quit_RevertWhen_Blocked() public {
- uint256 entry = 123456789;
- // waiting status
- IQuorumRegistrable(quorum).register(entry);
- IQuorumRegistrable(quorum).reject(entry);
- vm.expectRevert(QuorumUpgradeable.NotWaitingApproval.selector);
- IQuorumRegistrable(quorum).quit(entry);
- }
-}
diff --git a/test/primitives/QuorumUpgradeable.t.sol b/test/primitives/QuorumUpgradeable.t.sol
new file mode 100644
index 0000000..417d5ef
--- /dev/null
+++ b/test/primitives/QuorumUpgradeable.t.sol
@@ -0,0 +1,106 @@
+// SPDX-License-Identifier: BUSL-1.1
+pragma solidity 0.8.26;
+
+import "forge-std/Test.sol";
+
+import { QuorumUpgradeable } from "contracts/core/primitives/upgradeable/QuorumUpgradeable.sol";
+import { T } from "contracts/core/primitives/Types.sol";
+
+contract QuorumUpgradeableHarness is QuorumUpgradeable {
+ function initialize() external initializer {
+ __Quorum_init();
+ }
+
+ function statusOf(uint256 entry) external view returns (T.Status) {
+ return _status(entry);
+ }
+
+ function register(uint256 entry) external {
+ _register(entry);
+ }
+
+ function approve(uint256 entry) external {
+ _approve(entry);
+ }
+
+ function blockEntry(uint256 entry) external {
+ _block(entry);
+ }
+
+ function quit(uint256 entry) external {
+ _quit(entry);
+ }
+
+ function revoke(uint256 entry) external {
+ _revoke(entry);
+ }
+}
+
+contract QuorumUpgradeableTest is Test {
+ QuorumUpgradeableHarness internal harness;
+
+ function setUp() public {
+ harness = new QuorumUpgradeableHarness();
+ harness.initialize();
+ }
+
+ function test_StatusDefaultsToPending() public {
+ assertEq(uint256(harness.statusOf(1)), uint256(T.Status.Pending), "Default status should be pending");
+ }
+
+ function test_RegisterMovesToWaiting() public {
+ harness.register(42);
+ assertEq(uint256(harness.statusOf(42)), uint256(T.Status.Waiting), "Register should move to waiting");
+ }
+
+ function test_Register_RevertWhen_NotPending() public {
+ harness.register(10);
+ vm.expectRevert(QuorumUpgradeable.NotPendingApproval.selector);
+ harness.register(10);
+ }
+
+ function test_ApproveMovesToActive() public {
+ harness.register(7);
+ harness.approve(7);
+ assertEq(uint256(harness.statusOf(7)), uint256(T.Status.Active), "Approve should activate entry");
+ }
+
+ function test_Approve_RevertWhen_NotWaiting() public {
+ vm.expectRevert(QuorumUpgradeable.NotWaitingApproval.selector);
+ harness.approve(99);
+ }
+
+ function test_BlockMovesToBlocked() public {
+ harness.register(5);
+ harness.blockEntry(5);
+ assertEq(uint256(harness.statusOf(5)), uint256(T.Status.Blocked), "Block should mark entry blocked");
+ }
+
+ function test_Block_RevertWhen_NotWaiting() public {
+ vm.expectRevert(QuorumUpgradeable.NotWaitingApproval.selector);
+ harness.blockEntry(12);
+ }
+
+ function test_QuitReturnsPending() public {
+ harness.register(3);
+ harness.quit(3);
+ assertEq(uint256(harness.statusOf(3)), uint256(T.Status.Pending), "Quit should restore pending state");
+ }
+
+ function test_Quit_RevertWhen_NotWaiting() public {
+ vm.expectRevert(QuorumUpgradeable.NotWaitingApproval.selector);
+ harness.quit(4);
+ }
+
+ function test_RevokeMovesActiveToBlocked() public {
+ harness.register(8);
+ harness.approve(8);
+ harness.revoke(8);
+ assertEq(uint256(harness.statusOf(8)), uint256(T.Status.Blocked), "Revoke should block active entry");
+ }
+
+ function test_Revoke_RevertWhen_NotActive() public {
+ vm.expectRevert(QuorumUpgradeable.InvalidInactiveState.selector);
+ harness.revoke(6);
+ }
+}
diff --git a/test/rights/RightAssetCustodian.t.sol b/test/rights/RightAssetCustodian.t.sol
deleted file mode 100644
index 3fab868..0000000
--- a/test/rights/RightAssetCustodian.t.sol
+++ /dev/null
@@ -1,309 +0,0 @@
-// calc weight based on expected balance, demand and priority
-// get balanced custodian with a expected proba
-//
-
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.26;
-import { console } from "forge-std/console.sol";
-
-import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
-import { Math } from "@openzeppelin/contracts/utils/math/Math.sol";
-import { IRightsAssetCustodianManager } from "contracts/core/interfaces/rights/IRightsAssetCustodianManager.sol";
-import { IRightsAssetCustodianVerifiable } from "contracts/core/interfaces/rights/IRightsAssetCustodianVerifiable.sol";
-import { IRightsAssetCustodianRegistrable } from "contracts/core/interfaces/rights/IRightsAssetCustodianRegistrable.sol";
-import { ICustodianVerifiable } from "contracts/core/interfaces/custody/ICustodianVerifiable.sol";
-import { ICustodianRevokable } from "contracts/core/interfaces/custody/ICustodianRevokable.sol";
-import { IBalanceVerifiable } from "contracts/core/interfaces/base/IBalanceVerifiable.sol";
-import { RightsAssetCustodian } from "contracts/rights/RightsAssetCustodian.sol";
-import { CustodianShared } from "test/shared/CustodianShared.t.sol";
-
-contract RightAssetCustodianTest is CustodianShared {
- using Math for uint256;
- address custodian;
-
- function setUp() public override {
- super.setUp();
- custodian = deployCustodian("weare.com");
- _registerAndApproveCustodian(custodian);
- deployRightsAssetCustodian();
- deployToken();
- }
-
- function test_SetMaxAllowedRedundancy_ValidValue() public {
- vm.startPrank(admin);
- uint256 newMaxRedundancy = 5;
- IRightsAssetCustodianManager(rightAssetCustodian).setMaxAllowedRedundancy(newMaxRedundancy);
- uint256 maxRedundancy = IRightsAssetCustodianManager(rightAssetCustodian).getMaxAllowedRedundancy();
- assertEq(maxRedundancy, newMaxRedundancy, "Max allowed redundancy should be updated");
- vm.stopPrank();
- }
-
- function test_SetMaxAllowedRedundancy_RevertIf_NotAuthorized() public {
- vm.startPrank(user);
- uint256 newMaxRedundancy = 5;
- vm.expectRevert(abi.encodeWithSignature("AccessManagedUnauthorized(address)", user));
- IRightsAssetCustodianManager(rightAssetCustodian).setMaxAllowedRedundancy(newMaxRedundancy);
- vm.stopPrank();
- }
-
- function test_GrantCustody_ValidCustodian() public {
- vm.prank(user);
- IRightsAssetCustodianRegistrable(rightAssetCustodian).grantCustody(custodian);
- bool isCustodian = IRightsAssetCustodianVerifiable(rightAssetCustodian).isCustodian(custodian, user);
- assertTrue(isCustodian, "Custodian should be registered");
- }
-
- function test_GrantCustody_EmitCustodialGranted() public {
- vm.prank(user);
-
- vm.expectEmit(true, true, false, true, address(rightAssetCustodian));
- emit RightsAssetCustodian.CustodialGranted(custodian, user, 1);
- IRightsAssetCustodianRegistrable(rightAssetCustodian).grantCustody(custodian);
- }
-
- function test_GrantCustody_RevertIf_GrantDuplication() public {
- vm.startPrank(user);
- // registered first time
- IRightsAssetCustodianRegistrable(rightAssetCustodian).grantCustody(custodian);
- uint256 demand = IRightsAssetCustodianManager(rightAssetCustodian).getDemand(custodian);
- assertEq(demand, 1, "Demand should be 1 after granting custody");
-
- // second expected failing attempt
- vm.expectRevert(abi.encodeWithSignature("GrantCustodyFailed(address,address)", custodian, user));
- IRightsAssetCustodianRegistrable(rightAssetCustodian).grantCustody(custodian);
- vm.stopPrank();
- }
-
- function test_GrantCustody_RevertIf_ExceedAvailableRedundancy() public {
- // MAX default = 3
- address custodian2 = deployCustodian("weare1.com");
- address custodian3 = deployCustodian("weare2.com");
- address custodian4 = deployCustodian("weare3.com");
- _registerAndApproveCustodian(custodian2);
- _registerAndApproveCustodian(custodian3);
-
- vm.startPrank(user);
- // registered first time
- IRightsAssetCustodianRegistrable(rightAssetCustodian).grantCustody(custodian);
- IRightsAssetCustodianRegistrable(rightAssetCustodian).grantCustody(custodian2);
- IRightsAssetCustodianRegistrable(rightAssetCustodian).grantCustody(custodian3);
- // 3 is reached, the validation is effective after this line
-
- // second expected failing attempt
- vm.expectRevert(abi.encodeWithSignature("MaxRedundancyAllowedReached()"));
- IRightsAssetCustodianRegistrable(rightAssetCustodian).grantCustody(custodian4);
- vm.stopPrank();
- }
-
- function test_RevokeCustody_ValidRegisteredCustodian() public {
- vm.startPrank(user);
- IRightsAssetCustodianRegistrable(rightAssetCustodian).grantCustody(custodian);
- IRightsAssetCustodianRegistrable(rightAssetCustodian).revokeCustody(custodian);
- vm.stopPrank();
-
- bool isCustodian = IRightsAssetCustodianVerifiable(rightAssetCustodian).isCustodian(custodian, user);
- assertFalse(isCustodian, "Custodian should be revoked");
- }
-
- function test_RevokeCustody_EmitCustodialRevoked() public {
- vm.startPrank(user);
- // first grant the custody
- IRightsAssetCustodianRegistrable(rightAssetCustodian).grantCustody(custodian);
-
- vm.expectEmit(true, true, false, true, address(rightAssetCustodian));
- emit RightsAssetCustodian.CustodialRevoked(custodian, user, 0);
- IRightsAssetCustodianRegistrable(rightAssetCustodian).revokeCustody(custodian);
- vm.stopPrank();
- }
-
- function test_RevokeCustody_RevertIf_NotRegisteredCustodian() public {
- vm.startPrank(user);
- // registered first time
- uint256 demand = IRightsAssetCustodianManager(rightAssetCustodian).getDemand(custodian);
- assertEq(demand, 0, "Demand should be 0 before granting custody");
-
- // second expected failing attempt
- vm.expectRevert(abi.encodeWithSignature("RevokeCustodyFailed(address,address)", custodian, user));
- IRightsAssetCustodianRegistrable(rightAssetCustodian).revokeCustody(custodian);
- vm.stopPrank();
- }
-
- function test_SetPriority_EmitPrioritySet() public {
- // second expected failing attempt
- uint256 designedPriority = 2;
- address custodian2 = deployCustodian("weare1.com");
-
- vm.startPrank(user);
- vm.expectEmit(true, true, false, true, address(rightAssetCustodian));
- emit RightsAssetCustodian.PrioritySet(custodian2, user, designedPriority);
- IRightsAssetCustodianManager(rightAssetCustodian).setPriority(custodian2, designedPriority);
- vm.stopPrank();
- }
-
- function test_SetPriority_ValidPriority() public {
- // second expected failing attempt
- uint256 designedPriority = 2;
- address custodian2 = deployCustodian("weare1.com");
-
- vm.prank(user);
- IRightsAssetCustodianManager(rightAssetCustodian).setPriority(custodian2, designedPriority);
- uint256 got = IRightsAssetCustodianManager(rightAssetCustodian).getPriority(custodian2, user);
- assertEq(got, designedPriority, "Priority should be set correctly");
- }
-
- function test_SetPriority_RevertIf_ValidPriority() public {
- // second expected failing attempt
- address custodian2 = deployCustodian("weare1.com");
- vm.expectRevert(abi.encodeWithSignature("InvalidPriority(uint256)", 0));
- IRightsAssetCustodianManager(rightAssetCustodian).setPriority(custodian2, 0);
- }
-
- function test_GetDemand_ReturnValidDemand() public {
- uint256 before = IRightsAssetCustodianManager(rightAssetCustodian).getDemand(custodian);
- assertEq(before, 0, "Demand should be 0 before granting custody");
-
- vm.prank(user);
- IRightsAssetCustodianRegistrable(rightAssetCustodian).grantCustody(custodian);
- uint256 after_ = IRightsAssetCustodianManager(rightAssetCustodian).getDemand(custodian);
- assertEq(after_, 1, "Demand should be 1 after granting custody");
-
- vm.prank(user);
- IRightsAssetCustodianRegistrable(rightAssetCustodian).revokeCustody(custodian);
- uint256 revoked = IRightsAssetCustodianManager(rightAssetCustodian).getDemand(custodian);
- assertEq(revoked, 0, "Demand should be 0 after revoked custody");
- }
-
- function test_GetWeight_ValidExpectedWeight() public {
- address user1 = vm.addr(4);
- IRightsAssetCustodianManager rightAssetCustodianMgr = IRightsAssetCustodianManager(rightAssetCustodian);
- IRightsAssetCustodianRegistrable rightAssetCustodianReg = IRightsAssetCustodianRegistrable(rightAssetCustodian);
- // expected d = 2
- vm.prank(user); // user grant custody
- rightAssetCustodianReg.grantCustody(custodian);
- vm.prank(user1);
- rightAssetCustodianReg.grantCustody(custodian);
-
- // expected b = log2(10000)
- vm.prank(admin);
- IERC20(token).transfer(custodian, 10000);
- // expected p = 2
- vm.prank(user);
- rightAssetCustodianMgr.setPriority(custodian, 2);
-
- // calc weight for user
- uint256 b = IBalanceVerifiable(custodian).getBalance(token);
- uint256 p = rightAssetCustodianMgr.getPriority(custodian, user);
- uint256 d = rightAssetCustodianMgr.getDemand(custodian);
- uint256 expectedWeight = rightAssetCustodianMgr.getWeight(custodian, user, token);
- uint256 w = p * (d + 1) * (b.log2() + 1);
-
- assertEq(w, 84, "Weight should match the expected formula");
- assertEq(w, expectedWeight, "Weight should match the expected formula");
-
- //
- }
-
- function test_GetCustodian_ValidGrantedCustodian() public {
- address custodian2 = deployCustodian("weare1.com");
- address custodian3 = deployCustodian("weare2.com");
-
- address user1 = vm.addr(4);
- IRightsAssetCustodianManager rightAssetCustodianMgr = IRightsAssetCustodianManager(rightAssetCustodian);
- IRightsAssetCustodianRegistrable rightAssetCustodianReg = IRightsAssetCustodianRegistrable(rightAssetCustodian);
-
- // simulate approval process
- _registerAndApproveCustodian(custodian2);
- _registerAndApproveCustodian(custodian3);
-
- // expected d = 2
- // higher demand for 'custodian'
- vm.prank(user1);
- rightAssetCustodianReg.grantCustody(custodian);
- vm.startPrank(user);
- // assign custodians to user
- IRightsAssetCustodianRegistrable(rightAssetCustodian).grantCustody(custodian);
- IRightsAssetCustodianRegistrable(rightAssetCustodian).grantCustody(custodian2);
- IRightsAssetCustodianRegistrable(rightAssetCustodian).grantCustody(custodian3);
- vm.stopPrank();
-
- // 1- sum the weights
- uint256 total = 0;
- uint256[] memory weights = new uint256[](3);
- address[] memory custodians = new address[](3);
- custodians[0] = custodian;
- custodians[1] = custodian2;
- custodians[2] = custodian3;
-
- for (uint256 i = 0; i < custodians.length; i++) {
- uint256 w = rightAssetCustodianMgr.getWeight(custodians[i], user, token);
- weights[i] = w;
- total += w;
- }
-
- // simulate the internal behavior of getCustodian
- // given the block A, the holder X and currency Z
- vm.roll(10); // initialize in block 10
- // same block number derive deterministic in the same blockhash
- // same hash + user + token derive in the same randomSeed
- bytes32 blockHash = blockhash(block.number - 1);
- uint256 randomSeed = uint256(keccak256(abi.encodePacked(blockHash, user, token)));
- // same modulus with same randomSeed and total derive deterministic in the same randomValue
- // the randomValue is in range [0, total]
- uint256 randomValue = randomSeed % total;
- uint256 acc = 0;
-
- // the same data is used to derive the custodian in deterministic way
- // must match exact the same custodian during categorical selection
- address selectedCustodian = rightAssetCustodianMgr.getCustodian(user, token);
- // iterate through the weights and custodians to find the selected custodian
- for (uint256 i = 0; i < weights.length; i++) {
- uint256 weight = weights[i];
- acc += weight;
-
- // assert the matched in the hit range
- if (randomValue < acc) {
- assertEq(selectedCustodian, custodians[i], "Selected custodian should match the expected one");
- break;
- }
- }
- }
-
- function test_IsCustodian_ReturnFalseIfRevokedNorExist() public {
- // MAX default = 3
- address custodian2 = deployCustodian("weare1.com");
- IRightsAssetCustodianVerifiable rightAssetCustodianVer = IRightsAssetCustodianVerifiable(rightAssetCustodian);
- IRightsAssetCustodianRegistrable rightAssetCustodianReg = IRightsAssetCustodianRegistrable(rightAssetCustodian);
-
- vm.prank(user);
- rightAssetCustodianReg.grantCustody(custodian);
- bool isCustodian = rightAssetCustodianVer.isCustodian(custodian, user);
- bool isFalseCustodian = rightAssetCustodianVer.isCustodian(custodian2, user);
-
- vm.prank(user);
- rightAssetCustodianReg.revokeCustody(custodian);
- bool isRevokedCustodian = rightAssetCustodianVer.isCustodian(custodian2, user);
-
- assertTrue(isCustodian, "Custodian should be registered after grant");
- assertFalse(isFalseCustodian, "Custodian should not be registered");
- assertFalse(isRevokedCustodian, "Custodian should not be registered after revoke");
- }
-
- function test_IsCustodian_ReturnFalseIfRevokedByGovernance() public {
- // MAX default = 3
- IRightsAssetCustodianVerifiable rightAssetCustodianVer = IRightsAssetCustodianVerifiable(rightAssetCustodian);
- IRightsAssetCustodianRegistrable rightAssetCustodianReg = IRightsAssetCustodianRegistrable(rightAssetCustodian);
-
- vm.prank(user);
- //custody granted
- rightAssetCustodianReg.grantCustody(custodian);
- bool isCustodian = rightAssetCustodianVer.isCustodian(custodian, user);
- assertTrue(isCustodian, "Custodian should be registered");
-
- vm.prank(governor);
- ICustodianRevokable(custodianReferendum).revoke(custodian);
- bool isRevokedCustodian = rightAssetCustodianVer.isCustodian(custodian, user);
- assertFalse(isRevokedCustodian, "Custodian should not be registered after revocation by governance");
- }
-
- // TODO assign demand + transfer balance and check calc of weight
-}
diff --git a/test/rights/RightsPolicyAuthorizer.t.sol b/test/rights/RightsPolicyAuthorizer.t.sol
new file mode 100644
index 0000000..3a04a70
--- /dev/null
+++ b/test/rights/RightsPolicyAuthorizer.t.sol
@@ -0,0 +1,364 @@
+// SPDX-License-Identifier: BUSL-1.1
+pragma solidity 0.8.26;
+
+import "forge-std/Test.sol";
+
+import { Initializable } from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
+
+import { RightsPolicyAuthorizer } from "contracts/rights/RightsPolicyAuthorizer.sol";
+import { IRightsPolicyAuthorizer } from "contracts/core/interfaces/rights/IRightsPolicyAuthorizer.sol";
+import { IPolicyAuditor } from "contracts/core/interfaces/policies/IPolicyAuditor.sol";
+import { PolicyBase } from "contracts/policies/PolicyBase.sol";
+import { IAttestationProvider } from "contracts/core/interfaces/base/IAttestationProvider.sol";
+import { T } from "contracts/core/primitives/Types.sol";
+
+import { BaseTest } from "test/BaseTest.t.sol";
+
+contract DummyAttestationProvider is IAttestationProvider {
+ function getName() external pure returns (string memory) {
+ return "DummyAttestationProvider";
+ }
+
+ function getAddress() external view returns (address) {
+ return address(this);
+ }
+
+ function attest(
+ address[] calldata recipients,
+ uint256,
+ bytes calldata
+ ) external pure override returns (uint256[] memory attestationIds) {
+ attestationIds = new uint256[](recipients.length);
+ }
+
+ function verify(uint256, address) external pure returns (bool) {
+ return false;
+ }
+}
+
+/// @dev Policy harness leveraging PolicyBase for authorizer testing.
+contract PolicyBaseAuthorizerHarness is PolicyBase {
+ IRightsPolicyAuthorizer public immutable AUTHORIZE_GATE;
+ address private _lastHolder;
+ bytes private _lastInitData;
+
+ bool public setupShouldRevert;
+ bool public setupRevertNoData;
+ bool public triggerReentrancy;
+ bool private _reentrancyExecuted;
+
+ constructor(address rightsAuthorizer, address attestationProvider)
+ PolicyBase(address(0), rightsAuthorizer, address(0), attestationProvider)
+ {
+ AUTHORIZE_GATE = IRightsPolicyAuthorizer(rightsAuthorizer);
+ }
+
+ function setSetupRevert(bool status) external {
+ setupShouldRevert = status;
+ }
+
+ function setSetupRevertNoData(bool status) external {
+ setupRevertNoData = status;
+ }
+
+ function setTriggerReentrancy(bool status) external {
+ triggerReentrancy = status;
+ _reentrancyExecuted = false;
+ }
+
+ function lastHolder() external view returns (address) {
+ return _lastHolder;
+ }
+
+ function lastInit() external view returns (bytes memory) {
+ return _lastInitData;
+ }
+
+ function setup(address holder, bytes calldata init) external virtual override {
+ if (setupShouldRevert) {
+ if (setupRevertNoData) {
+ assembly {
+ revert(0, 0)
+ }
+ } else {
+ revert("policy setup failed");
+ }
+ }
+ if (triggerReentrancy && !_reentrancyExecuted) {
+ _reentrancyExecuted = true;
+ AUTHORIZE_GATE.authorizePolicy(address(this), init);
+ }
+ _lastHolder = holder;
+ _lastInitData = init;
+ }
+
+ function enforce(address, T.Agreement calldata agreement) external pure override returns (uint256[] memory result) {
+ result = new uint256[](agreement.parties.length);
+ }
+
+ function isAccessAllowed(address, bytes calldata) external pure override returns (bool) {
+ return true;
+ }
+
+
+ function resolveTerms(bytes calldata) external pure override returns (T.Terms memory terms) {}
+
+ function name() external pure override returns (string memory) {
+ return "PolicyBaseAuthorizerHarness";
+ }
+
+ function description() external pure override returns (string memory) {
+ return "Policy base authorizer harness";
+ }
+}
+
+contract RightsPolicyAuthorizerTest is BaseTest {
+ IRightsPolicyAuthorizer internal authorizer;
+ IPolicyAuditor internal auditor;
+ address internal holder;
+
+ mapping(address => bool) internal audited;
+
+ event RightsGranted(address indexed policy, address indexed holder, bytes data);
+ event RightsRevoked(address indexed policy, address indexed holder);
+
+ function setUp() public initialize {
+ deployRightsPolicyAuthorizer();
+ authorizer = IRightsPolicyAuthorizer(rightsPolicyAuthorizer);
+ auditor = IPolicyAuditor(policyAudit);
+ holder = user;
+ }
+
+ function _createPolicy()
+ internal
+ returns (PolicyBaseAuthorizerHarness policy, DummyAttestationProvider provider)
+ {
+ provider = new DummyAttestationProvider();
+ policy = new PolicyBaseAuthorizerHarness(address(authorizer), address(provider));
+ }
+
+ function _auditPolicy(address policy) internal {
+ if (audited[policy]) return;
+
+ vm.prank(holder);
+ try auditor.submit(policy) {} catch {}
+
+ vm.prank(admin);
+ try auditor.approve(policy) {
+ audited[policy] = true;
+ } catch {}
+
+ if (!audited[policy] && auditor.isApproved(policy)) {
+ audited[policy] = true;
+ }
+ }
+
+ function _revokeAudit(address policy) internal {
+ if (!audited[policy]) return;
+
+ vm.prank(admin);
+ try auditor.reject(policy) {
+ audited[policy] = false;
+ } catch {}
+
+ if (audited[policy] && !auditor.isApproved(policy)) {
+ audited[policy] = false;
+ }
+ }
+
+ function test_Initialize_RevertsOnSecondCall() public {
+ vm.expectRevert(Initializable.InvalidInitialization.selector);
+ vm.prank(admin);
+ RightsPolicyAuthorizer(address(authorizer)).initialize(accessManager);
+ }
+
+ function test_AuthorizePolicy_SucceedsForAuditedPolicy() public {
+ (PolicyBaseAuthorizerHarness policy, ) = _createPolicy();
+ _auditPolicy(address(policy));
+
+ bytes memory initData = abi.encode(uint256(1));
+ vm.expectEmit(true, true, true, true, address(authorizer));
+ emit RightsGranted(address(policy), holder, initData);
+
+ vm.prank(holder);
+ authorizer.authorizePolicy(address(policy), initData);
+
+ assertTrue(authorizer.isPolicyAuthorized(address(policy), holder), "Policy should be authorized");
+ assertEq(policy.lastHolder(), holder, "Holder mismatch recorded in policy setup");
+ assertEq(policy.lastInit(), initData, "Init payload mismatch in policy setup");
+ }
+
+ function test_AuthorizePolicy_RevertsWhenPolicyNotAudited() public {
+ (PolicyBaseAuthorizerHarness policy, ) = _createPolicy();
+
+ assertFalse(authorizer.isPolicyAuthorized(address(policy), holder), "Policy should start unauthorized");
+
+ vm.expectRevert(abi.encodeWithSelector(RightsPolicyAuthorizer.InvalidNotAuditedPolicy.selector, address(policy)));
+ vm.prank(holder);
+ authorizer.authorizePolicy(address(policy), "");
+ }
+
+ function test_AuthorizePolicy_RevertsWhenSetupFails() public {
+ (PolicyBaseAuthorizerHarness policy, ) = _createPolicy();
+ policy.setSetupRevert(true);
+ policy.setSetupRevertNoData(true);
+ _auditPolicy(address(policy));
+
+ vm.expectRevert(
+ abi.encodeWithSelector(
+ RightsPolicyAuthorizer.InvalidPolicyInitialization.selector,
+ "Error during policy initialization call"
+ )
+ );
+ vm.prank(holder);
+ authorizer.authorizePolicy(address(policy), "");
+ }
+
+ function test_AuthorizePolicy_ReentrancyIsBlocked() public {
+ (PolicyBaseAuthorizerHarness policy, ) = _createPolicy();
+ policy.setTriggerReentrancy(true);
+ _auditPolicy(address(policy));
+
+ vm.expectRevert(
+ abi.encodeWithSelector(
+ RightsPolicyAuthorizer.InvalidPolicyInitialization.selector,
+ "Error during policy initialization call"
+ )
+ );
+ vm.prank(holder);
+ authorizer.authorizePolicy(address(policy), "data");
+
+ address[] memory holderPolicies = authorizer.getAuthorizedPolicies(holder);
+ assertEq(holderPolicies.length, 0, "Reentrant attempt should not authorize policy");
+ assertFalse(authorizer.isPolicyAuthorized(address(policy), holder), "Policy should remain unauthorized");
+ }
+
+ function test_RevokePolicy_RemovesAuthorization() public {
+ (PolicyBaseAuthorizerHarness policy, ) = _createPolicy();
+ _auditPolicy(address(policy));
+
+ vm.prank(holder);
+ authorizer.authorizePolicy(address(policy), "");
+
+ vm.expectEmit(true, true, false, false, address(authorizer));
+ emit RightsRevoked(address(policy), holder);
+
+ vm.prank(holder);
+ authorizer.revokePolicy(address(policy));
+
+ assertFalse(authorizer.isPolicyAuthorized(address(policy), holder), "Policy should be revoked");
+ }
+
+ function test_RevokePolicy_RevertsWhenNotAuthorized() public {
+ (PolicyBaseAuthorizerHarness policy, ) = _createPolicy();
+
+ vm.expectRevert(abi.encodeWithSelector(RightsPolicyAuthorizer.RevocationFailed.selector, holder, address(policy)));
+ vm.prank(holder);
+ authorizer.revokePolicy(address(policy));
+ }
+
+ function test_AuthorizePolicy_RevertsOnDuplicate() public {
+ (PolicyBaseAuthorizerHarness policy, ) = _createPolicy();
+ _auditPolicy(address(policy));
+
+ vm.startPrank(holder);
+ authorizer.authorizePolicy(address(policy), "");
+
+ vm.expectRevert(
+ abi.encodeWithSelector(
+ RightsPolicyAuthorizer.InvalidPolicyInitialization.selector,
+ "Error during duplicated policy registration"
+ )
+ );
+ authorizer.authorizePolicy(address(policy), "");
+ vm.stopPrank();
+ }
+
+ function test_Integration_AuthorizeRevokeAndAuditFlow() public {
+ (PolicyBaseAuthorizerHarness policyA, ) = _createPolicy();
+ (PolicyBaseAuthorizerHarness policyB, ) = _createPolicy();
+ _auditPolicy(address(policyA));
+ _auditPolicy(address(policyB));
+
+ vm.startPrank(holder);
+ authorizer.authorizePolicy(address(policyA), abi.encode("A"));
+ authorizer.authorizePolicy(address(policyB), abi.encode("B"));
+ vm.stopPrank();
+
+ address[] memory list = authorizer.getAuthorizedPolicies(holder);
+ assertEq(list.length, 2, "Both policies should be present");
+ assertTrue(list[0] != list[1], "Duplicate entries detected");
+
+ vm.prank(holder);
+ authorizer.revokePolicy(address(policyB));
+
+ assertFalse(authorizer.isPolicyAuthorized(address(policyB), holder), "Policy B should be revoked");
+
+ _revokeAudit(address(policyA));
+ list = authorizer.getAuthorizedPolicies(holder);
+ assertEq(list.length, 0, "Revoked or non-audited policies must be excluded");
+ }
+
+ function testFuzz_AuthorizePoliciesMaintainsConsistency(bytes32 salt) public {
+ PolicyBaseAuthorizerHarness[3] memory policies;
+ bool[3] memory expectedAuthorized;
+ bool[3] memory expectedAudited;
+
+ for (uint256 i = 0; i < policies.length; i++) {
+ (PolicyBaseAuthorizerHarness policy, ) = _createPolicy();
+ policies[i] = policy;
+ _auditPolicy(address(policies[i]));
+ expectedAudited[i] = true;
+ }
+
+ uint256 steps = 6;
+ for (uint256 step = 0; step < steps; step++) {
+ uint8 op = uint8(uint256(keccak256(abi.encode(salt, step)))) % 3;
+ uint8 idx = uint8(uint256(keccak256(abi.encode(salt, step, "IDX")))) % uint8(policies.length);
+ address policyAddr = address(policies[idx]);
+
+ if (op == 0) {
+ if (!expectedAudited[idx] || expectedAuthorized[idx]) continue;
+ vm.prank(holder);
+ authorizer.authorizePolicy(policyAddr, abi.encode(step));
+ expectedAuthorized[idx] = true;
+ } else if (op == 1) {
+ if (!expectedAuthorized[idx]) continue;
+ vm.prank(holder);
+ authorizer.revokePolicy(policyAddr);
+ expectedAuthorized[idx] = false;
+ } else {
+ bool auditStatus = uint8(uint256(keccak256(abi.encode(salt, step, "AUDIT")))) % 2 == 0;
+ if (auditStatus) {
+ _auditPolicy(policyAddr);
+ } else {
+ _revokeAudit(policyAddr);
+ }
+ expectedAudited[idx] = audited[policyAddr];
+ }
+ }
+
+ address[] memory actual = authorizer.getAuthorizedPolicies(holder);
+ address[] memory expected = new address[](policies.length);
+ uint256 expectedLen;
+
+ for (uint256 i = 0; i < policies.length; i++) {
+ if (expectedAuthorized[i] && expectedAudited[i]) {
+ expected[expectedLen++] = address(policies[i]);
+ }
+ }
+
+ assertEq(actual.length, expectedLen, "Unexpected authorized length");
+ for (uint256 i = 0; i < expectedLen; i++) {
+ bool found;
+ for (uint256 j = 0; j < actual.length; j++) {
+ if (actual[j] == expected[i]) {
+ found = true;
+ break;
+ }
+ }
+ assertTrue(found, "Expected policy missing");
+ }
+ }
+
+}
diff --git a/test/rights/RightsPolicyManager.t.sol b/test/rights/RightsPolicyManager.t.sol
new file mode 100644
index 0000000..6a8f24a
--- /dev/null
+++ b/test/rights/RightsPolicyManager.t.sol
@@ -0,0 +1,506 @@
+// SPDX-License-Identifier: BUSL-1.1
+pragma solidity 0.8.26;
+
+import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
+
+import { AgreementSettler } from "contracts/financial/AgreementSettler.sol";
+import { RightsPolicyManager } from "contracts/rights/RightsPolicyManager.sol";
+import { PolicyBase } from "contracts/policies/PolicyBase.sol";
+
+import { BaseTest } from "test/BaseTest.t.sol";
+import { IRightsPolicyAuthorizer } from "contracts/core/interfaces/rights/IRightsPolicyAuthorizer.sol";
+import { IAgreementManager } from "contracts/core/interfaces/financial/IAgreementManager.sol";
+import { ITollgate } from "contracts/core/interfaces/economics/ITollgate.sol";
+import { ILedgerVault } from "contracts/core/interfaces/financial/ILedgerVault.sol";
+import { IPolicyAuditor } from "contracts/core/interfaces/policies/IPolicyAuditor.sol";
+import { IAttestationProvider } from "contracts/core/interfaces/base/IAttestationProvider.sol";
+import { T } from "contracts/core/primitives/Types.sol";
+
+uint256 constant DEFAULT_TOLLGATE_BPS = 500;
+uint256 constant DEFAULT_AGREEMENT_AMOUNT = 100 ether;
+uint256 constant INITIAL_LEDGER_DEPOSIT = 1_000_000 ether;
+bytes constant DEFAULT_PAYLOAD = "payload";
+
+/// @dev Attestation provider stub allowing configurable attestation ids.
+contract ConfigurableAttestationProvider is IAttestationProvider {
+ address[] internal _lastRecipients;
+ uint256 public lastExpireAt;
+ bytes public lastData;
+ uint256 public attestCalls;
+
+ uint256[] internal _nextIds;
+ uint256 private _counter = 1;
+ mapping(address => mapping(uint256 => bool)) internal _issued;
+
+ function setNextAttestationIds(uint256[] memory ids) external {
+ delete _nextIds;
+ uint256 len = ids.length;
+ for (uint256 i = 0; i < len; i++) {
+ _nextIds.push(ids[i]);
+ }
+ }
+
+ function getName() external pure returns (string memory) {
+ return "ConfigurableAttestationProvider";
+ }
+
+ function getAddress() external view returns (address) {
+ return address(this);
+ }
+
+ function attest(
+ address[] calldata recipients,
+ uint256 expireAt,
+ bytes calldata data
+ ) external override returns (uint256[] memory attestationIds) {
+ delete _lastRecipients;
+ uint256 len = recipients.length;
+ for (uint256 i = 0; i < len; i++) {
+ _lastRecipients.push(recipients[i]);
+ }
+
+ lastExpireAt = expireAt;
+ lastData = data;
+ attestCalls++;
+
+ attestationIds = new uint256[](len);
+ if (len == 0) return attestationIds;
+
+ if (_nextIds.length != 0) {
+ require(_nextIds.length == len, "Attestation length mismatch");
+ for (uint256 i = 0; i < len; i++) {
+ attestationIds[i] = _nextIds[i];
+ }
+ delete _nextIds;
+ } else {
+ for (uint256 i = 0; i < len; i++) {
+ attestationIds[i] = _counter++;
+ }
+ }
+
+ for (uint256 i = 0; i < len; i++) {
+ _issued[recipients[i]][attestationIds[i]] = true;
+ }
+ return attestationIds;
+ }
+
+ function verify(uint256 attestationId, address recipient) external view returns (bool) {
+ return _issued[recipient][attestationId];
+ }
+
+ function lastRecipientsLength() external view returns (uint256) {
+ return _lastRecipients.length;
+ }
+
+ function lastRecipientAt(uint256 index) external view returns (address) {
+ return _lastRecipients[index];
+ }
+}
+
+/// @dev Policy harness leveraging PolicyBase for realistic behaviour.
+contract PolicyBaseManagerHarness is PolicyBase {
+ address private _lastHolder;
+ bytes private _lastSetupData;
+ T.Agreement private _lastAgreement;
+
+ bool public setupShouldRevert;
+ bool public enforceShouldRevert;
+ bool public accessAllowed = true;
+ bool public accessShouldRevert;
+
+ constructor(
+ address rightsPolicyManager,
+ address rightsAuthorizer,
+ address assetRegistry,
+ address attestationProvider
+ ) PolicyBase(rightsPolicyManager, rightsAuthorizer, assetRegistry, attestationProvider) {}
+
+ function setSetupRevert(bool status) external {
+ setupShouldRevert = status;
+ }
+
+ function setAccessAllowed(bool status) external {
+ accessAllowed = status;
+ }
+
+ function setAccessRevert(bool status) external {
+ accessShouldRevert = status;
+ }
+
+ function setEnforceRevert(bool status) external {
+ enforceShouldRevert = status;
+ }
+
+ function lastHolder() external view returns (address) {
+ return _lastHolder;
+ }
+
+ function lastSetupData() external view returns (bytes memory) {
+ return _lastSetupData;
+ }
+
+ function getLastAgreement() external view returns (T.Agreement memory) {
+ return _lastAgreement;
+ }
+
+ function setup(address holder, bytes calldata init) external override {
+ if (setupShouldRevert) revert("policy setup failed");
+ _lastHolder = holder;
+ _lastSetupData = init;
+ }
+
+ function enforce(
+ address holder,
+ T.Agreement calldata agreement
+ ) external override returns (uint256[] memory attestationIds) {
+ if (enforceShouldRevert) revert("enforce failure");
+ _lastHolder = holder;
+ _lastAgreement = agreement;
+
+ uint256 expireAt = block.timestamp + 1;
+ attestationIds = _commit(holder, agreement, expireAt);
+
+ uint256 len = agreement.parties.length;
+ bytes memory context = abi.encode(holder);
+ for (uint256 i = 0; i < len; i++) {
+ _setAttestation(agreement.parties[i], context, attestationIds[i]);
+ }
+ }
+
+ function isAccessAllowed(address, bytes calldata) external view override returns (bool) {
+ if (accessShouldRevert) revert("access check failed");
+ return accessAllowed;
+ }
+
+ function resolveTerms(bytes calldata) external pure override returns (T.Terms memory terms) {}
+
+ function name() external pure override returns (string memory) {
+ return "PolicyBaseManagerHarness";
+ }
+
+ function description() external pure override returns (string memory) {
+ return "Policy base test harness";
+ }
+}
+
+contract RightsPolicyManagerTest is BaseTest {
+ RightsPolicyManager internal manager;
+ IRightsPolicyAuthorizer internal authorizer;
+ IAgreementManager internal agreements;
+ IPolicyAuditor internal auditor;
+ ITollgate internal tollgateContract;
+ ILedgerVault internal ledgerVault;
+ IERC20 internal currency;
+
+ address internal holder;
+
+ mapping(address => bool) internal auditedPolicies;
+ mapping(address => bool) internal authorizedPolicies;
+
+ event Registered(address indexed account, uint256 indexed proof, uint256 attestationId, address policy);
+
+ function setUp() public initialize {
+ holder = user;
+
+ deployRightsPolicyManager();
+
+ manager = RightsPolicyManager(rightsPolicyManager);
+ authorizer = IRightsPolicyAuthorizer(rightsPolicyAuthorizer);
+ agreements = IAgreementManager(agreementManager);
+ auditor = IPolicyAuditor(policyAudit);
+ tollgateContract = ITollgate(tollgate);
+ ledgerVault = ILedgerVault(ledger);
+ currency = IERC20(token);
+
+ _configureTollgate();
+ _seedLedgerBalance(holder, INITIAL_LEDGER_DEPOSIT);
+ }
+
+ function _configureTollgate() internal {
+ vm.prank(governor);
+ tollgateContract.setFees(T.Scheme.BPS, address(manager), DEFAULT_TOLLGATE_BPS, address(currency));
+ }
+
+ function _seedLedgerBalance(address account, uint256 amount) internal {
+ vm.startPrank(governor);
+ currency.approve(address(ledgerVault), amount);
+ ledgerVault.deposit(account, amount, address(currency));
+ vm.stopPrank();
+ }
+
+ function _deployPolicy()
+ internal
+ returns (PolicyBaseManagerHarness policy, ConfigurableAttestationProvider provider)
+ {
+ provider = new ConfigurableAttestationProvider();
+ policy = new PolicyBaseManagerHarness(address(manager), address(authorizer), assetRegistry, address(provider));
+ }
+
+ function _authorizePolicy(PolicyBaseManagerHarness policy, bytes memory data) internal {
+ _auditPolicy(address(policy));
+ if (authorizedPolicies[address(policy)]) return;
+
+ vm.prank(holder);
+ authorizer.authorizePolicy(address(policy), data);
+ authorizedPolicies[address(policy)] = true;
+ }
+
+ function _auditPolicy(address policy) internal {
+ if (auditedPolicies[policy]) return;
+
+ vm.prank(holder);
+ try auditor.submit(policy) {} catch {}
+
+ vm.prank(admin);
+ try auditor.approve(policy) {
+ auditedPolicies[policy] = true;
+ } catch {}
+
+ if (!auditedPolicies[policy] && auditor.isApproved(policy)) {
+ auditedPolicies[policy] = true;
+ }
+ }
+
+ function _createAgreement(
+ address[] memory parties
+ ) internal returns (uint256 proof, T.Agreement memory agreement) {
+ vm.roll(block.number + 1);
+ vm.startPrank(holder);
+ proof = agreements.createAgreement(
+ DEFAULT_AGREEMENT_AMOUNT,
+ address(currency),
+ address(manager),
+ parties,
+ DEFAULT_PAYLOAD
+ );
+ vm.stopPrank();
+ agreement = agreements.getAgreement(proof);
+ }
+
+ function _createAgreementWithArbiter(
+ address arbiter,
+ address[] memory parties
+ ) internal returns (uint256 proof, T.Agreement memory agreement) {
+ vm.roll(block.number + 1);
+ vm.prank(governor);
+ tollgateContract.setFees(T.Scheme.BPS, arbiter, DEFAULT_TOLLGATE_BPS, address(currency));
+
+ vm.startPrank(holder);
+ proof = agreements.createAgreement(DEFAULT_AGREEMENT_AMOUNT, address(currency), arbiter, parties, DEFAULT_PAYLOAD);
+ vm.stopPrank();
+ agreement = agreements.getAgreement(proof);
+ }
+
+ function _collectedFees(T.Agreement memory agreement) internal pure returns (uint256) {
+ return agreement.fees + (agreement.locked - agreement.total);
+ }
+
+ function test_RegisterPolicy_SucceedsForAuthorizedPolicy() public {
+ (PolicyBaseManagerHarness policy, ConfigurableAttestationProvider provider) = _deployPolicy();
+ _authorizePolicy(policy, abi.encode(uint256(1)));
+
+ address[] memory parties = new address[](2);
+ parties[0] = vm.addr(11);
+ parties[1] = vm.addr(12);
+ (uint256 proof, T.Agreement memory agreement) = _createAgreement(parties);
+
+ uint256[] memory attestationIds = new uint256[](2);
+ attestationIds[0] = 777;
+ attestationIds[1] = 888;
+ provider.setNextAttestationIds(attestationIds);
+
+ vm.expectEmit(true, true, true, true, agreementSettler);
+ emit AgreementSettler.AgreementSettled(address(manager), holder, proof, _collectedFees(agreement));
+
+ vm.expectEmit(true, true, true, true);
+ emit Registered(parties[0], proof, attestationIds[0], address(policy));
+ vm.expectEmit(true, true, true, true);
+ emit Registered(parties[1], proof, attestationIds[1], address(policy));
+
+ uint256[] memory result = manager.registerPolicy(proof, holder, address(policy));
+ assertEq(result.length, attestationIds.length, "Unexpected attestation array length");
+ for (uint256 i = 0; i < result.length; i++) {
+ assertEq(result[i], attestationIds[i], "Attestation id mismatch");
+ }
+
+ assertEq(policy.lastHolder(), holder, "Holder mismatch recorded in policy");
+ address retrievedArbiter;
+ address[] memory enforcedParties;
+ bytes memory payload;
+ T.Agreement memory enforced = policy.getLastAgreement();
+ retrievedArbiter = enforced.arbiter;
+ enforcedParties = enforced.parties;
+ payload = enforced.payload;
+ assertEq(retrievedArbiter, agreement.arbiter, "Stored agreement arbiter mismatch");
+ assertEq(enforcedParties.length, agreement.parties.length, "Stored agreement parties mismatch");
+
+ address[] memory holderPolicies = manager.getPolicies(parties[0]);
+ assertEq(holderPolicies.length, 1, "First party should have one policy");
+ assertEq(holderPolicies[0], address(policy), "Unexpected policy stored for first party");
+
+ holderPolicies = manager.getPolicies(parties[1]);
+ assertEq(holderPolicies.length, 1, "Second party should have one policy");
+ assertEq(holderPolicies[0], address(policy), "Unexpected policy stored for second party");
+ }
+
+ function test_RegisterPolicy_RevertsWhenPolicyNotAuthorized() public {
+ (PolicyBaseManagerHarness policy, ) = _deployPolicy();
+
+ address[] memory parties = new address[](1);
+ parties[0] = vm.addr(101);
+ (uint256 proof, ) = _createAgreement(parties);
+
+ vm.expectRevert(abi.encodeWithSelector(RightsPolicyManager.RightsNotDelegated.selector, address(policy), holder));
+ manager.registerPolicy(proof, holder, address(policy));
+ }
+
+ function test_RegisterPolicy_RevertsWhenAgreementHasNoParties() public {
+ (PolicyBaseManagerHarness policy, ) = _deployPolicy();
+ _authorizePolicy(policy, "");
+
+ address[] memory parties = new address[](0);
+ (uint256 proof, ) = _createAgreement(parties);
+
+ vm.expectRevert(
+ abi.encodeWithSelector(
+ RightsPolicyManager.EnforcementFailed.selector,
+ "No parties in agreement: at least one party is required."
+ )
+ );
+ manager.registerPolicy(proof, holder, address(policy));
+ }
+
+ function test_RegisterPolicy_RevertsWhenEnforceFails() public {
+ (PolicyBaseManagerHarness policy, ) = _deployPolicy();
+ policy.setEnforceRevert(true);
+ _authorizePolicy(policy, "");
+
+ address[] memory parties = new address[](1);
+ parties[0] = vm.addr(99);
+ (uint256 proof, ) = _createAgreement(parties);
+
+ vm.expectRevert(
+ abi.encodeWithSelector(
+ RightsPolicyManager.EnforcementFailed.selector,
+ "Error during policy enforcement call"
+ )
+ );
+ manager.registerPolicy(proof, holder, address(policy));
+ }
+
+ function test_RegisterPolicy_RevertsWhenSettlerFails() public {
+ (PolicyBaseManagerHarness policy, ) = _deployPolicy();
+ _authorizePolicy(policy, "");
+
+ address[] memory parties = new address[](1);
+ parties[0] = vm.addr(77);
+ (uint256 proof, ) = _createAgreementWithArbiter(address(this), parties);
+
+ vm.expectRevert(AgreementSettler.UnauthorizedEscrowAgent.selector);
+ manager.registerPolicy(proof, holder, address(policy));
+ }
+
+ function test_GetActivePolicy_ReturnsFirstMatchingPolicy() public {
+ address account = vm.addr(222);
+ address[] memory parties = new address[](1);
+ parties[0] = account;
+
+ (PolicyBaseManagerHarness inactivePolicy, ConfigurableAttestationProvider inactiveProvider) = _deployPolicy();
+ inactivePolicy.setAccessAllowed(false);
+ _authorizePolicy(inactivePolicy, "");
+ inactiveProvider.setNextAttestationIds(_singleAttestation(1));
+ (uint256 inactiveProof, ) = _createAgreement(parties);
+ manager.registerPolicy(inactiveProof, holder, address(inactivePolicy));
+
+ (PolicyBaseManagerHarness activePolicy, ConfigurableAttestationProvider activeProvider) = _deployPolicy();
+ activePolicy.setAccessAllowed(true);
+ _authorizePolicy(activePolicy, "");
+ activeProvider.setNextAttestationIds(_singleAttestation(2));
+ (uint256 activeProof, ) = _createAgreement(parties);
+ manager.registerPolicy(activeProof, holder, address(activePolicy));
+
+ (bool found, address policyAddress) = manager.getActivePolicy(account, abi.encode("criteria"));
+ assertTrue(found, "An active policy should be found");
+ assertEq(policyAddress, address(activePolicy), "Expected the active policy to be returned");
+ }
+
+ function test_GetActivePolicies_FiltersInactiveOnes() public {
+ address account = vm.addr(333);
+ address[] memory parties = new address[](1);
+ parties[0] = account;
+
+ (PolicyBaseManagerHarness policyA, ConfigurableAttestationProvider providerA) = _deployPolicy();
+ policyA.setAccessAllowed(true);
+ _authorizePolicy(policyA, "");
+ providerA.setNextAttestationIds(_singleAttestation(101));
+ (uint256 proofA, ) = _createAgreement(parties);
+ manager.registerPolicy(proofA, holder, address(policyA));
+
+ (PolicyBaseManagerHarness policyB, ConfigurableAttestationProvider providerB) = _deployPolicy();
+ policyB.setAccessAllowed(false);
+ _authorizePolicy(policyB, "");
+ providerB.setNextAttestationIds(_singleAttestation(102));
+ (uint256 proofB, ) = _createAgreement(parties);
+ manager.registerPolicy(proofB, holder, address(policyB));
+
+ (PolicyBaseManagerHarness policyC, ConfigurableAttestationProvider providerC) = _deployPolicy();
+ policyC.setAccessAllowed(true);
+ _authorizePolicy(policyC, "");
+ providerC.setNextAttestationIds(_singleAttestation(103));
+ (uint256 proofC, ) = _createAgreement(parties);
+ manager.registerPolicy(proofC, holder, address(policyC));
+
+ address[] memory activePolicies = manager.getActivePolicies(account, "");
+ assertEq(activePolicies.length, 2, "Only active policies should be returned");
+ assertEq(activePolicies[0], address(policyA), "First active policy mismatch");
+ assertEq(activePolicies[1], address(policyC), "Second active policy mismatch");
+ }
+
+ function test_IsActivePolicy_ReturnsFalseWhenAccessCheckReverts() public {
+ (PolicyBaseManagerHarness policy, ConfigurableAttestationProvider provider) = _deployPolicy();
+ _authorizePolicy(policy, "");
+ policy.setAccessRevert(true);
+
+ address[] memory parties = new address[](1);
+ parties[0] = vm.addr(404);
+ provider.setNextAttestationIds(_singleAttestation(9));
+
+ assertFalse(manager.isActivePolicy(parties[0], address(policy), ""), "Unknown policy should be inactive");
+
+ (uint256 proof, ) = _createAgreement(parties);
+ manager.registerPolicy(proof, holder, address(policy));
+
+ assertFalse(manager.isActivePolicy(parties[0], address(policy), ""), "Reverting policy should be treated as inactive");
+ }
+
+ function testFuzz_RegisterPolicy_StoresPolicyOnce(
+ address account,
+ uint256 proofSeed,
+ uint256 attestation
+ ) public {
+ vm.assume(account != address(0));
+
+ (PolicyBaseManagerHarness policy, ConfigurableAttestationProvider provider) = _deployPolicy();
+ _authorizePolicy(policy, abi.encode(proofSeed));
+
+ address[] memory parties = new address[](1);
+ parties[0] = account;
+
+ uint8 repeats = uint8(bound(proofSeed, 1, 3));
+ uint256 baseAttestation = bound(attestation, 1, type(uint256).max - repeats);
+ for (uint8 i = 0; i < repeats; i++) {
+ provider.setNextAttestationIds(_singleAttestation(baseAttestation + i));
+ (uint256 proof, ) = _createAgreement(parties);
+ manager.registerPolicy(proof, holder, address(policy));
+ }
+
+ address[] memory policies = manager.getPolicies(account);
+ assertEq(policies.length, 1, "Policy should be registered once");
+ assertEq(policies[0], address(policy), "Stored policy mismatch");
+ }
+
+ function _singleAttestation(uint256 value) internal pure returns (uint256[] memory attestationIds) {
+ attestationIds = new uint256[](1);
+ attestationIds[0] = value;
+ }
+}
diff --git a/test/shared/CustodianShared.t.sol b/test/shared/CustodianShared.t.sol
deleted file mode 100644
index b9a7cb9..0000000
--- a/test/shared/CustodianShared.t.sol
+++ /dev/null
@@ -1,69 +0,0 @@
-// SPDX-License-Identifier: BUSL-1.1
-pragma solidity 0.8.26;
-
-import { BaseTest } from "test/BaseTest.t.sol";
-import { ICustodianFactory } from "contracts/core/interfaces/custody/ICustodianFactory.sol";
-import { ICustodianRegistrable } from "contracts/core/interfaces/custody/ICustodianRegistrable.sol";
-import { IAgreementManager } from "contracts/core/interfaces/financial/IAgreementManager.sol";
-import { ITollgate } from "contracts/core/interfaces/economics/ITollgate.sol";
-import { ILedgerVault } from "contracts/core/interfaces/financial/ILedgerVault.sol";
-import { IERC20 } from "@openzeppelin/contracts/interfaces/IERC20.sol";
-import { T } from "contracts/core/primitives/Types.sol";
-
-contract CustodianShared is BaseTest {
- function setUp() public virtual initialize {
- deployCustodianReferendum();
- deployCustodianFactory();
- }
-
- function deployCustodian(string memory endpoint) public returns (address) {
- vm.prank(admin);
- ICustodianFactory custodianFactory = ICustodianFactory(custodianFactory);
- return custodianFactory.create(endpoint);
- }
-
- function _setFeesAsGovernor(uint256 fees) internal {
- vm.startPrank(governor);
- ITollgate(tollgate).setFees(T.Scheme.FLAT, custodianReferendum, fees, token);
- vm.stopPrank();
- }
-
- function _registerCustodianWithApproval(address d9r, uint256 approval) internal {
- // manager = contract deployer
- // only manager can pay enrollment..
- vm.startPrank(admin);
- // approve approval to ledger to deposit funds
- address[] memory parties = new address[](1);
- parties[0] = d9r;
-
- uint256 proof = _createAgreement(approval, parties);
- // operate over msg.sender ledger registered funds
- ICustodianRegistrable(custodianReferendum).register(proof, d9r);
- vm.stopPrank();
- }
-
- function _createAgreement(uint256 amount, address[] memory parties) internal returns (uint256) {
- IERC20(token).approve(ledger, amount);
- ILedgerVault(ledger).deposit(admin, amount, token);
-
- uint256 proof = IAgreementManager(agreementManager).createAgreement(
- amount,
- token,
- address(custodianReferendum),
- parties,
- ""
- );
-
- return proof;
- }
-
- function _registerAndApproveCustodian(address d9r) internal {
- // intially the balance = 0
- _setFeesAsGovernor(1 * 1e18);
- // register the custodian with fees = 100 MMC
- _registerCustodianWithApproval(d9r, 1 * 1e18);
- vm.prank(governor); // as governor.
- // distribuitor approved only by governor..
- ICustodianRegistrable(custodianReferendum).approve(d9r);
- }
-}