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 @@ -coveragecoverage48.91%48.91% \ No newline at end of file +coveragecoverage60.73%60.73% \ 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. + +Captura desde 2025-10-13 11-59-01 + +--- + + +### 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); - } -}