From 540f23acd87ae262ac07f1e10793a73f9eca03d9 Mon Sep 17 00:00:00 2001 From: Alex Sedighi Date: Mon, 2 Mar 2026 14:39:21 +1300 Subject: [PATCH 01/15] feat(swarms): implement BLE swarm registry system with geographic tiered bonding Core Contracts: - FleetIdentity (ERC-721): UUID-based fleet ownership with geographic registration - Two-level registration: Country (ISO 3166-1) or Local (admin area) - Geometric bond tiers: BASE_BOND * 2^tier (local), *16 for country - Operator delegation for cold/hot wallet separation - O(1) setOperator via uuidTotalTierBonds tracking - ServiceProvider (ERC-721): Service endpoint URL ownership - SwarmRegistryL1: SSTORE2 filter storage for Ethereum L1 - SwarmRegistryUniversal: Native bytes storage for ZkSync Era Registration Model: - Fresh registration: caller becomes owner+operator, pays BASE_BOND + tierBond - Owned-to-Registered: only operator can register, pays tierBond - Multi-region: same UUID can register in multiple regions at same level - Burn refunds: BASE_BOND to owner (on last token), tierBond to operator Discovery: - buildHighestBondedUuidBundle: priority-ordered bundle (up to 20 UUIDs) - XOR filter membership verification for tag validation - Inclusion hints for optimal tier selection Documentation: - Complete technical specification in src/swarms/doc/ - ISO 3166-2 admin area mappings for 18 countries --- .agent/rules/solidity_zksync.md | 33 + .cspell.json | 20 +- .github/copilot-instructions.md | 50 + .github/workflows/checks.yml | 2 +- .gitmodules | 3 + .vscode/settings.json | 3 + foundry.lock | 20 + foundry.toml | 4 + lib/solady | 1 + remappings.txt | 3 +- src/swarms/FleetIdentity.sol | 1155 +++++ src/swarms/ServiceProvider.sol | 54 + src/swarms/SwarmRegistryL1.sol | 376 ++ src/swarms/SwarmRegistryUniversal.sol | 385 ++ src/swarms/doc/README.md | 164 + src/swarms/doc/assistant-guide.md | 423 ++ src/swarms/doc/data-model.md | 173 + src/swarms/doc/discovery.md | 169 + src/swarms/doc/fleet-registration.md | 280 ++ src/swarms/doc/iso3166-2/036-Australia.md | 18 + src/swarms/doc/iso3166-2/076-Brazil.md | 37 + src/swarms/doc/iso3166-2/124-Canada.md | 23 + src/swarms/doc/iso3166-2/156-China.md | 44 + src/swarms/doc/iso3166-2/250-France.md | 28 + src/swarms/doc/iso3166-2/276-Germany.md | 26 + src/swarms/doc/iso3166-2/356-India.md | 46 + src/swarms/doc/iso3166-2/380-Italy.md | 30 + src/swarms/doc/iso3166-2/392-Japan.md | 57 + src/swarms/doc/iso3166-2/410-South_Korea.md | 27 + src/swarms/doc/iso3166-2/484-Mexico.md | 42 + src/swarms/doc/iso3166-2/566-Nigeria.md | 47 + src/swarms/doc/iso3166-2/643-Russia.md | 93 + src/swarms/doc/iso3166-2/710-South_Africa.md | 19 + src/swarms/doc/iso3166-2/724-Spain.md | 29 + src/swarms/doc/iso3166-2/756-Switzerland.md | 36 + .../doc/iso3166-2/826-United_Kingdom.md | 182 + src/swarms/doc/iso3166-2/840-United_States.md | 67 + src/swarms/doc/iso3166-reference.md | 97 + src/swarms/doc/lifecycle.md | 138 + src/swarms/doc/maintenance.md | 202 + src/swarms/doc/swarm-operations.md | 199 + test/FleetIdentity.t.sol | 3745 +++++++++++++++++ test/FleetIdentityFairness.t.sol | 562 +++ test/ServiceProvider.t.sol | 159 + test/SwarmRegistryL1.t.sol | 1066 +++++ test/SwarmRegistryUniversal.t.sol | 1205 ++++++ test/contentsign/BaseContentSign.t.sol | 2 +- test/contentsign/PaymentMiddleware.t.sol | 3 +- 48 files changed, 11540 insertions(+), 7 deletions(-) create mode 100644 .agent/rules/solidity_zksync.md create mode 100644 .github/copilot-instructions.md create mode 100644 foundry.lock create mode 160000 lib/solady create mode 100644 src/swarms/FleetIdentity.sol create mode 100644 src/swarms/ServiceProvider.sol create mode 100644 src/swarms/SwarmRegistryL1.sol create mode 100644 src/swarms/SwarmRegistryUniversal.sol create mode 100644 src/swarms/doc/README.md create mode 100644 src/swarms/doc/assistant-guide.md create mode 100644 src/swarms/doc/data-model.md create mode 100644 src/swarms/doc/discovery.md create mode 100644 src/swarms/doc/fleet-registration.md create mode 100644 src/swarms/doc/iso3166-2/036-Australia.md create mode 100644 src/swarms/doc/iso3166-2/076-Brazil.md create mode 100644 src/swarms/doc/iso3166-2/124-Canada.md create mode 100644 src/swarms/doc/iso3166-2/156-China.md create mode 100644 src/swarms/doc/iso3166-2/250-France.md create mode 100644 src/swarms/doc/iso3166-2/276-Germany.md create mode 100644 src/swarms/doc/iso3166-2/356-India.md create mode 100644 src/swarms/doc/iso3166-2/380-Italy.md create mode 100644 src/swarms/doc/iso3166-2/392-Japan.md create mode 100644 src/swarms/doc/iso3166-2/410-South_Korea.md create mode 100644 src/swarms/doc/iso3166-2/484-Mexico.md create mode 100644 src/swarms/doc/iso3166-2/566-Nigeria.md create mode 100644 src/swarms/doc/iso3166-2/643-Russia.md create mode 100644 src/swarms/doc/iso3166-2/710-South_Africa.md create mode 100644 src/swarms/doc/iso3166-2/724-Spain.md create mode 100644 src/swarms/doc/iso3166-2/756-Switzerland.md create mode 100644 src/swarms/doc/iso3166-2/826-United_Kingdom.md create mode 100644 src/swarms/doc/iso3166-2/840-United_States.md create mode 100644 src/swarms/doc/iso3166-reference.md create mode 100644 src/swarms/doc/lifecycle.md create mode 100644 src/swarms/doc/maintenance.md create mode 100644 src/swarms/doc/swarm-operations.md create mode 100644 test/FleetIdentity.t.sol create mode 100644 test/FleetIdentityFairness.t.sol create mode 100644 test/ServiceProvider.t.sol create mode 100644 test/SwarmRegistryL1.t.sol create mode 100644 test/SwarmRegistryUniversal.t.sol diff --git a/.agent/rules/solidity_zksync.md b/.agent/rules/solidity_zksync.md new file mode 100644 index 00000000..642f1082 --- /dev/null +++ b/.agent/rules/solidity_zksync.md @@ -0,0 +1,33 @@ +# Solidity & ZkSync Development Standards + +## Toolchain & Environment +- **Primary Tool**: `forge` (ZkSync fork). Use for compilation, testing, and generic scripting. +- **Secondary Tool**: `hardhat`. Use only when `forge` encounters compatibility issues (e.g., complex deployments, specific plugin needs). +- **Network Target**: ZkSync Era (Layer 2). +- **Solidity Version**: `^0.8.20` (or `0.8.24` if strictly supported by the zk-compiler). + +## Modern Solidity Best Practices +- **Safety First**: + - **Checks-Effects-Interactions (CEI)** pattern must be strictly followed. + - When a contract requires an owner (e.g., admin-configurable parameters), prefer `Ownable2Step` over `Ownable`. Do **not** add ownership to contracts that don't need it — many contracts are fully permissionless by design. + - Prefer `ReentrancyGuard` for external calls where appropriate. +- **Gas & Efficiency**: + - Use **Custom Errors** (`error MyError();`) instead of `require` strings. + - Use `mapping` over arrays for membership checks where possible. + - Minimize on-chain storage; use events for off-chain indexing. + +## Testing Standards +- **Framework**: Foundry (Forge). +- **Methodology**: + - **Unit Tests**: Comprehensive coverage for all functions. + - **Fuzz Testing**: Required for arithmetic and purely functional logic. + - **Invariant Testing**: Define invariants for stateful system properties. +- **Naming Convention**: + - `test_Description` + - `testFuzz_Description` + - `test_RevertIf_Condition` + +## ZkSync Specifics +- **System Contracts**: Be aware of ZkSync system contracts (e.g., `ContractDeployer`, `L2EthToken`) when interacting with low-level features. +- **Gas Model**: Account for ZkSync's different gas metering if performing low-level optimization. +- **Compiler Differences**: Be mindful of differences between `solc` and `zksolc` (e.g., `create2` address derivation). diff --git a/.cspell.json b/.cspell.json index c9909576..04ce2bc4 100644 --- a/.cspell.json +++ b/.cspell.json @@ -12,7 +12,8 @@ "deployments-zk", "cache_hardhat-zk", "zkout", - "clk-gateway/src/validators.test.ts" + "clk-gateway/src/validators.test.ts", + "src/swarms/doc/iso3166-2" ], "ignoreWords": [ "NODL", @@ -60,6 +61,21 @@ "Frontends", "testuser", "testhandle", - "douglasacost" + "douglasacost", + "IBEACON", + "AABBCCDD", + "SSTORE", + "Permissionless", + "Reentrancy", + "SFID", + "EXTCODECOPY", + "solady", + "SLOAD", + "Bitmask", + "mstore", + "MBOND", + "USCA", + "USNY", + "usca" ] } diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md new file mode 100644 index 00000000..37ae67c2 --- /dev/null +++ b/.github/copilot-instructions.md @@ -0,0 +1,50 @@ +# Solidity & ZkSync Development Standards + +## Toolchain & Environment + +- **Primary Tool**: `forge` (ZkSync fork). Use for compilation, testing, and generic scripting. +- **Secondary Tool**: `hardhat`. Use only when `forge` encounters compatibility issues (e.g., complex deployments, specific plugin needs). +- **Network Target**: ZkSync Era (Layer 2). +- **Solidity Version**: `^0.8.20` (or `0.8.24` if strictly supported by the zk-compiler). + +## Modern Solidity Best Practices + +- **Safety First**: + - **Checks-Effects-Interactions (CEI)** pattern must be strictly followed. + - Use `Ownable2Step` over `Ownable` for privileged access. + - Prefer `ReentrancyGuard` for external calls where appropriate. +- **Gas & Efficiency**: + - Use **Custom Errors** (`error MyError();`) instead of `require` strings. + - Use `mapping` over arrays for membership checks where possible. + - Minimize on-chain storage; use events for off-chain indexing. + +## Testing Standards + +- **Framework**: Foundry (Forge). +- **Methodology**: + - **Unit Tests**: Comprehensive coverage for all functions. + - **Fuzz Testing**: Required for arithmetic and purely functional logic. + - **Invariant Testing**: Define invariants for stateful system properties. +- **Naming Convention**: + - `test_Description` + - `testFuzz_Description` + - `test_RevertIf_Condition` + +## ZkSync Specifics + +- **System Contracts**: Be aware of ZkSync system contracts (e.g., `ContractDeployer`, `L2EthToken`) when interacting with low-level features. +- **Gas Model**: Account for ZkSync's different gas metering if performing low-level optimization. +- **Compiler Differences**: Be mindful of differences between `solc` and `zksolc` (e.g., `create2` address derivation). + +## L1-Only Contracts (No --zksync flag) + +The following contracts use opcodes/patterns incompatible with ZkSync Era and must be built/tested **without** the `--zksync` flag: + +- **SwarmRegistryL1**: Uses `SSTORE2` (relies on `EXTCODECOPY` which is unsupported on ZkSync). + +For these contracts, use: + +```bash +forge build --match-path src/swarms/SwarmRegistryL1.sol +forge test --match-path test/SwarmRegistryL1.t.sol +``` diff --git a/.github/workflows/checks.yml b/.github/workflows/checks.yml index 64ac1373..1ceaaf35 100644 --- a/.github/workflows/checks.yml +++ b/.github/workflows/checks.yml @@ -34,4 +34,4 @@ jobs: run: yarn lint - name: Run tests - run: forge test --zksync + run: forge test diff --git a/.gitmodules b/.gitmodules index 9540dda6..c6c1a45d 100644 --- a/.gitmodules +++ b/.gitmodules @@ -10,3 +10,6 @@ [submodule "lib/era-contracts"] path = lib/era-contracts url = https://github.com/matter-labs/era-contracts +[submodule "lib/solady"] + path = lib/solady + url = https://github.com/vectorized/solady diff --git a/.vscode/settings.json b/.vscode/settings.json index 4d04fd25..8ab6c216 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -13,5 +13,8 @@ "editor.formatOnSave": true, "[solidity]": { "editor.defaultFormatter": "JuanBlanco.solidity" + }, + "chat.tools.terminal.autoApprove": { + "forge": true } } diff --git a/foundry.lock b/foundry.lock new file mode 100644 index 00000000..7a3effd8 --- /dev/null +++ b/foundry.lock @@ -0,0 +1,20 @@ +{ + "lib/zksync-storage-proofs": { + "rev": "4b20401ce44c1ec966a29d893694f65db885304b" + }, + "lib/openzeppelin-contracts": { + "rev": "e4f70216d759d8e6a64144a9e1f7bbeed78e7079" + }, + "lib/solady": { + "tag": { + "name": "v0.1.26", + "rev": "acd959aa4bd04720d640bf4e6a5c71037510cc4b" + } + }, + "lib/forge-std": { + "rev": "1eea5bae12ae557d589f9f0f0edae2faa47cb262" + }, + "lib/era-contracts": { + "rev": "84d5e3716f645909e8144c7d50af9dd6dd9ded62" + } +} \ No newline at end of file diff --git a/foundry.toml b/foundry.toml index b05ff1b9..44161471 100644 --- a/foundry.toml +++ b/foundry.toml @@ -8,3 +8,7 @@ solc = "0.8.26" # necessary as some of the zksync contracts are big via_ir = true + +[lint] +# Exclude ERC20 transfer warning - false positive for ERC721.transferFrom in tests +exclude_lints = ["erc20-unchecked-transfer"] diff --git a/lib/solady b/lib/solady new file mode 160000 index 00000000..acd959aa --- /dev/null +++ b/lib/solady @@ -0,0 +1 @@ +Subproject commit acd959aa4bd04720d640bf4e6a5c71037510cc4b diff --git a/remappings.txt b/remappings.txt index 1e950773..53468b38 100644 --- a/remappings.txt +++ b/remappings.txt @@ -1 +1,2 @@ -@openzeppelin=lib/openzeppelin-contracts/ \ No newline at end of file +@openzeppelin=lib/openzeppelin-contracts/ +solady/=lib/solady/src/ \ No newline at end of file diff --git a/src/swarms/FleetIdentity.sol b/src/swarms/FleetIdentity.sol new file mode 100644 index 00000000..dcbf3bdb --- /dev/null +++ b/src/swarms/FleetIdentity.sol @@ -0,0 +1,1155 @@ +// SPDX-License-Identifier: BSD-3-Clause-Clear + +pragma solidity ^0.8.24; + +import {ERC721} from "@openzeppelin/contracts/token/ERC721/ERC721.sol"; +import {ERC721Enumerable} from "@openzeppelin/contracts/token/ERC721/extensions/ERC721Enumerable.sol"; +import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; +import {ReentrancyGuard} from "@openzeppelin/contracts/utils/ReentrancyGuard.sol"; + +/** + * @title FleetIdentity + * @notice ERC-721 with ERC721Enumerable representing ownership of a BLE fleet, + * secured by an ERC-20 bond organized into geometric tiers. + * + * @dev **Two-level geographic registration** + * + * Fleets register at exactly one level: + * - Country — regionKey = countryCode (ISO 3166-1 numeric, 1-999) + * - Admin Area — regionKey = (countryCode << 10) | adminCode (>= 1024) + * + * Each regionKey has its **own independent tier namespace** — tier indices + * start at 0 for every region. The first fleet in any region always pays + * the level-appropriate bond (LOCAL: BASE_BOND, COUNTRY: BASE_BOND * 16). + * + * **Economic Model** + * + * - Tier capacity: 10 members per tier (unified across levels) + * - Local bond: BASE_BOND * 2^tier + * - Country bond: BASE_BOND * COUNTRY_BOND_MULTIPLIER * 2^tier (16× local) + * + * Country fleets pay 16× more but appear in all admin-area bundles within + * their country. This economic difference provides locals a significant + * advantage: a local can reach tier 4 for the same cost a country player + * pays for tier 0. Bundle slots are filled by simple tier-descent priority: + * higher tier first, locals before country within each tier. + * + * EdgeBeaconScanner discovery uses 2-level fallback: + * 1. Admin area (highest priority) + * 2. Country (lower priority) + * + * On-chain indexes track which countries and admin areas have active fleets, + * enabling EdgeBeaconScanner enumeration without off-chain indexers. + * + * **TokenID Encoding** + * + * TokenID = (regionKey << 128) | uuid + * - Bits 0-127: UUID (bytes16 Proximity UUID) + * - Bits 128-159: Region key (32-bit country or admin-area code) + * + * This allows the same UUID to be registered in multiple regions, + * each with a distinct token. Region and UUID can be extracted: + * - uuid = bytes16(uint128(tokenId)) + * - region = uint32(tokenId >> 128) + */ +contract FleetIdentity is ERC721Enumerable, ReentrancyGuard { + using SafeERC20 for IERC20; + + // ────────────────────────────────────────────── + // Errors + // ────────────────────────────────────────────── + error InvalidUUID(); + error NotTokenOwner(); + error MaxTiersReached(); + error TierFull(); + error TargetTierNotHigher(); + error TargetTierNotLower(); + error TargetTierSameAsCurrent(); + error InvalidCountryCode(); + error InvalidAdminCode(); + error AdminAreaRequired(); + error UuidLevelMismatch(); + error UuidAlreadyOwned(); + error UuidNotOwned(); + error NotUuidOwner(); + error NotOperator(); + error NotOwnerOrOperator(); + + // ────────────────────────────────────────────── + // Enums + // ────────────────────────────────────────────── + + /// @notice Registration level for a UUID. + enum RegistrationLevel { + None, // 0 - not registered (default) + Owned, // 1 - owned but not registered in any region + Local, // 2 - admin area (local) level + Country // 3 - country level + } + + // ────────────────────────────────────────────── + // Constants & Immutables + // ────────────────────────────────────────────── + + /// @notice Unified tier capacity for all levels. + uint256 public constant TIER_CAPACITY = 10; + + /// @notice Bond multiplier for country-level registration (16× local). + uint256 public constant COUNTRY_BOND_MULTIPLIER = 16; + + /// @notice Hard cap on tier count per region. + /// @dev Derived from anti-spam analysis: with a bond doubling per tier + /// and capacity 4, a spammer spending half the total token supply + /// against a BASE_BOND set 10 000× too low fills ~20 tiers. + /// 24 provides comfortable headroom. + uint256 public constant MAX_TIERS = 24; + + /// @notice Maximum UUIDs returned by buildHighestBondedUuidBundle. + uint256 public constant MAX_BONDED_UUID_BUNDLE_SIZE = 20; + + /// @notice ISO 3166-1 numeric upper bound for country codes. + uint16 internal constant MAX_COUNTRY_CODE = 999; + + /// @notice Upper bound for admin-area codes within a country. + /// @dev Set to 255 to cover all real-world countries (UK has ~172, the highest). + /// Dense indices from ISO 3166-2 mappings range 0-254, stored as adminCode 1-255. + uint16 internal constant MAX_ADMIN_CODE = 255; + + /// @dev Bit shift for packing countryCode into an admin-area region key. + uint256 private constant ADMIN_SHIFT = 10; + /// @dev Bitmask for extracting adminCode from an admin-area region key. + uint32 private constant ADMIN_CODE_MASK = 0x3FF; + + /// @notice Region key for owned-only UUIDs (not registered in any region). + uint32 public constant OWNED_REGION_KEY = 0; + + /// @notice The ERC-20 token used for bonds (immutable, e.g. NODL). + IERC20 public immutable BOND_TOKEN; + + /// @notice Base bond for tier 0 in any region. Tier K requires BASE_BOND * 2^K. + uint256 public immutable BASE_BOND; + + // ────────────────────────────────────────────── + // Region-namespaced tier data + // ────────────────────────────────────────────── + + /// @notice regionKey -> number of tiers opened in that region. + mapping(uint32 => uint256) public regionTierCount; + + /// @notice regionKey -> tierIndex -> list of token IDs. + mapping(uint32 => mapping(uint256 => uint256[])) internal _regionTierMembers; + + /// @notice Token ID -> index within its tier's member array (for O(1) removal). + mapping(uint256 => uint256) internal _indexInTier; + + // ────────────────────────────────────────────── + // Fleet data + // ────────────────────────────────────────────── + + /// @notice Token ID -> tier index (within its region) the fleet belongs to. + mapping(uint256 => uint256) public fleetTier; + + // ────────────────────────────────────────────── + // UUID ownership tracking + // ────────────────────────────────────────────── + + /// @notice UUID -> address that first registered a token for this UUID. + /// All subsequent registrations for the same UUID must come from this address. + mapping(bytes16 => address) public uuidOwner; + + /// @notice UUID -> count of active tokens for this UUID (across all regions). + /// When this reaches 0, uuidOwner is cleared. + mapping(bytes16 => uint256) public uuidTokenCount; + + /// @notice UUID -> registration level. + /// All tokens for a UUID must be at the same level. + mapping(bytes16 => RegistrationLevel) public uuidLevel; + + /// @notice UUID -> operator address for tier maintenance. + /// If address(0), the uuidOwner acts as operator. + /// Operator can only be set for registered UUIDs (Local or Country level). + /// The operator pays/receives tier bond differentials; owner pays BASE_BOND. + mapping(bytes16 => address) public uuidOperator; + + /// @notice UUID -> total tier bonds across all registered regions. + /// Tracked incrementally to allow O(1) lookup for setOperator. + /// Updated on registration, burn, promote, and demote. + mapping(bytes16 => uint256) public uuidTotalTierBonds; + + // ────────────────────────────────────────────── + // On-chain region indexes + // ────────────────────────────────────────────── + + /// @dev Set of country codes with at least one active fleet (country-level or admin-area). + uint16[] internal _activeCountries; + mapping(uint16 => uint256) internal _activeCountryIndex; // value = index+1 (0 = not present) + + /// @dev Country → list of admin-area region keys with at least one active fleet. + /// Enables bounded iteration in countryInclusionHint (max 255 per country). + mapping(uint16 => uint32[]) internal _countryAdminAreas; + mapping(uint32 => uint256) internal _countryAdminAreaIndex; // adminKey → index+1 in country's array + + // ────────────────────────────────────────────── + // Events + // ────────────────────────────────────────────── + + event FleetRegistered( + address indexed owner, + bytes16 indexed uuid, + uint256 indexed tokenId, + uint32 regionKey, + uint256 tierIndex, + uint256 bondAmount, + address operator + ); + event OperatorSet( + bytes16 indexed uuid, + address indexed oldOperator, + address indexed newOperator, + uint256 tierExcessTransferred + ); + event FleetPromoted( + uint256 indexed tokenId, uint256 indexed fromTier, uint256 indexed toTier, uint256 additionalBond + ); + event FleetDemoted(uint256 indexed tokenId, uint256 indexed fromTier, uint256 indexed toTier, uint256 bondRefund); + event FleetBurned( + address indexed owner, uint256 indexed tokenId, uint32 indexed regionKey, uint256 tierIndex, uint256 bondRefund + ); + event UuidClaimed(address indexed owner, bytes16 indexed uuid, address indexed operator); + + // ────────────────────────────────────────────── + // Constructor + // ────────────────────────────────────────────── + + /// @param _bondToken Address of the ERC-20 token used for bonds. + /// @param _baseBond Base bond for tier 0 in any region. + constructor(address _bondToken, uint256 _baseBond) ERC721("Swarm Fleet Identity", "SFID") { + BOND_TOKEN = IERC20(_bondToken); + BASE_BOND = _baseBond; + } + + // ══════════════════════════════════════════════ + // Registration: Country (operator-only with tier) + // ══════════════════════════════════════════════ + + /// @notice Register a fleet under a country at a specific tier. + /// @dev Only callable by the operator (or by caller for fresh UUIDs, who becomes owner+operator). + /// For owned UUIDs: operator pays tier bond, token mints to owner. + /// For fresh UUIDs: caller becomes owner+operator, pays BASE_BOND + tier bond. + /// @param uuid The Proximity UUID to register. + /// @param countryCode ISO 3166-1 numeric country code (1-999). + /// @param targetTier The tier to register at (use countryInclusionHint for guidance). + function registerFleetCountry(bytes16 uuid, uint16 countryCode, uint256 targetTier) + external + nonReentrant + returns (uint256 tokenId) + { + if (uuid == bytes16(0)) revert InvalidUUID(); + if (countryCode == 0 || countryCode > MAX_COUNTRY_CODE) revert InvalidCountryCode(); + uint32 regionKey = uint32(countryCode); + _validateExplicitTier(regionKey, targetTier); + tokenId = _register(uuid, regionKey, targetTier); + } + + // ══════════════════════════════════════════════ + // Registration: Admin Area (local, operator-only with tier) + // ══════════════════════════════════════════════ + + /// @notice Register a fleet under a country + admin area at a specific tier. + /// @dev Only callable by the operator (or by caller for fresh UUIDs, who becomes owner+operator). + /// For owned UUIDs: operator pays tier bond, token mints to owner. + /// For fresh UUIDs: caller becomes owner+operator, pays BASE_BOND + tier bond. + /// @param uuid The Proximity UUID to register. + /// @param countryCode ISO 3166-1 numeric country code (1-999). + /// @param adminCode Admin area code within the country (1-255). + /// @param targetTier The tier to register at (use localInclusionHint for guidance). + function registerFleetLocal(bytes16 uuid, uint16 countryCode, uint16 adminCode, uint256 targetTier) + external + nonReentrant + returns (uint256 tokenId) + { + if (uuid == bytes16(0)) revert InvalidUUID(); + if (countryCode == 0 || countryCode > MAX_COUNTRY_CODE) revert InvalidCountryCode(); + if (adminCode == 0 || adminCode > MAX_ADMIN_CODE) revert InvalidAdminCode(); + uint32 regionKey = makeAdminRegion(countryCode, adminCode); + _validateExplicitTier(regionKey, targetTier); + tokenId = _register(uuid, regionKey, targetTier); + } + + // ══════════════════════════════════════════════ + // Promote / Demote (region-aware) + // ══════════════════════════════════════════════ + + /// @notice Promotes a fleet to the next tier within its region. + /// Only callable by the effective operator (or owner if no operator set). + function promote(uint256 tokenId) external nonReentrant { + _promote(tokenId, fleetTier[tokenId] + 1); + } + + /// @notice Moves a fleet to a different tier within its region. + /// If targetTier > current tier, promotes (pulls additional bond from operator). + /// If targetTier < current tier, demotes (refunds bond difference to operator). + /// Only callable by the effective operator (or owner if no operator set). + function reassignTier(uint256 tokenId, uint256 targetTier) external nonReentrant { + uint256 currentTier = fleetTier[tokenId]; + if (targetTier == currentTier) revert TargetTierSameAsCurrent(); + if (targetTier > currentTier) { + _promote(tokenId, targetTier); + } else { + _demote(tokenId, targetTier); + } + } + + // ══════════════════════════════════════════════ + // Operator Management + // ══════════════════════════════════════════════ + + /// @notice Sets or changes the operator for a UUID. + /// The operator is responsible for tier maintenance (promote/demote) and registration. + /// When changing operators, the new operator must pay the old operator + /// for all accumulated tier bonds across all registered regions. + /// @dev Only the UUID owner can call this. Can be called for any owned UUID (including owned-only). + /// Setting operator to owner address or address(0) clears the explicit operator. + /// @param uuid The UUID to set the operator for. + /// @param newOperator The new operator address. Use address(0) or owner to clear. + function setOperator(bytes16 uuid, address newOperator) external nonReentrant { + // Only owner can set operator + if (uuidOwner[uuid] != msg.sender) revert NotUuidOwner(); + + // Cannot set operator for unowned UUIDs + if (uuidLevel[uuid] == RegistrationLevel.None) { + revert UuidNotOwned(); + } + + address oldOperator = operatorOf(uuid); + + // Normalize: if newOperator is owner, store as address(0) + address storedOperator = (newOperator == msg.sender) ? address(0) : newOperator; + address effectiveNewOperator = (storedOperator == address(0)) ? msg.sender : storedOperator; + + // O(1) lookup of total tier bonds from storage + uint256 tierBonds = uuidTotalTierBonds[uuid]; + + // Effects: Update operator + uuidOperator[uuid] = storedOperator; + + // Interactions: Transfer tier bonds from new operator to old operator + if (tierBonds > 0 && oldOperator != effectiveNewOperator) { + // Pull from new operator + _pullBond(effectiveNewOperator, tierBonds); + // Refund to old operator + _refundBond(oldOperator, tierBonds); + } + + emit OperatorSet(uuid, oldOperator, effectiveNewOperator, tierBonds); + } + + // ══════════════════════════════════════════════ + // Burn + // ══════════════════════════════════════════════ + + /// @notice Burns the fleet NFT and refunds the bond. + /// + /// **Owned-only tokens (region=0):** + /// - Only the token owner can burn. + /// - Refunds BASE_BOND to owner. Clears UUID ownership completely. + /// + /// **Registered tokens (region>0):** + /// - Owner OR operator can burn. + /// - Refunds tier bond to operator. + /// - If this is the LAST registered token for the UUID: + /// transitions to owned-only mode (mints owned-only token to owner). + /// - If other tokens remain: just decrements count. + /// + /// This function subsumes the former `unregisterToOwned` (operator burns + /// last registered token) and `releaseUuid` (owner burns owned-only token). + function burn(uint256 tokenId) external nonReentrant { + address tokenHolder = ownerOf(tokenId); + + uint32 region = tokenRegion(tokenId); + bytes16 uuid = tokenUuid(tokenId); + address owner = uuidOwner[uuid]; + address operator = operatorOf(uuid); + bool isLastToken = uuidTokenCount[uuid] == 1; + + if (region == OWNED_REGION_KEY) { + // Owned-only token: only owner can burn + if (tokenHolder != msg.sender) revert NotTokenOwner(); + + _burn(tokenId); + _clearUuidOwnership(uuid); + _refundBond(owner, BASE_BOND); + + emit FleetBurned(tokenHolder, tokenId, region, 0, BASE_BOND); + } else { + // Registered fleet: only operator can burn + if (msg.sender != operator) { + revert NotOperator(); + } + + uint256 tier = fleetTier[tokenId]; + uint256 tierBondAmount = tierBond(tier, _isCountryRegion(region)); + + // Update tracked tier bonds before cleanup + uuidTotalTierBonds[uuid] -= tierBondAmount; + + _cleanupFleetFromTier(tokenId, region, tier); + _burn(tokenId); + + if (isLastToken) { + // Transition to owned-only: mint owned-only token to owner + uuidLevel[uuid] = RegistrationLevel.Owned; + uint256 ownedTokenId = uint256(uint128(uuid)); + _mint(owner, ownedTokenId); + // Note: uuidTokenCount stays 1, operator is preserved + } else { + // Just decrement count + uuidTokenCount[uuid]--; + } + + // Refund tier bond to operator + _refundBond(operator, tierBondAmount); + + emit FleetBurned(tokenHolder, tokenId, region, tier, tierBondAmount); + } + } + + // ══════════════════════════════════════════════ + // UUID Ownership (Owned-Only Mode) + // ══════════════════════════════════════════════ + + /// @notice Claim ownership of a UUID without registering in any region. + /// Costs BASE_BOND. The UUID can later be registered via registerFleetLocal/Country. + /// @param uuid The Proximity UUID to claim. + /// @param operator Optional operator address for future tier management. Use address(0) for owner as operator. + /// @return tokenId The token ID for the owned-only UUID (region=0). + function claimUuid(bytes16 uuid, address operator) external nonReentrant returns (uint256 tokenId) { + if (uuid == bytes16(0)) revert InvalidUUID(); + if (uuidOwner[uuid] != address(0)) revert UuidAlreadyOwned(); + + // Set ownership + uuidOwner[uuid] = msg.sender; + uuidLevel[uuid] = RegistrationLevel.Owned; + uuidTokenCount[uuid] = 1; + // Normalize operator: address(0) or msg.sender means owner is operator + uuidOperator[uuid] = (operator == address(0) || operator == msg.sender) ? address(0) : operator; + + // Mint token with region=0 + tokenId = uint256(uint128(uuid)); + _mint(msg.sender, tokenId); + + _pullBond(msg.sender, BASE_BOND); + + emit UuidClaimed(msg.sender, uuid, operatorOf(uuid)); + } + + // ══════════════════════════════════════════════ + // Views: Bond & tier helpers + // ══════════════════════════════════════════════ + + /// @notice Bond required for tier K. + /// Local (admin area): BASE_BOND * 2^K + /// Country: BASE_BOND * COUNTRY_BOND_MULTIPLIER * 2^K (16× local) + function tierBond(uint256 tier, bool isCountry) public view returns (uint256) { + uint256 base = BASE_BOND << tier; + return isCountry ? base * COUNTRY_BOND_MULTIPLIER : base; + } + + /// @notice Returns the cheapest tier that guarantees a **local** fleet + /// appears in `buildHighestBondedUuidBundle` for (countryCode, adminCode). + /// Bounded: O(MAX_TIERS). + function localInclusionHint(uint16 countryCode, uint16 adminCode) + external + view + returns (uint256 inclusionTier, uint256 bond) + { + if (countryCode == 0 || countryCode > MAX_COUNTRY_CODE) revert InvalidCountryCode(); + if (adminCode == 0 || adminCode > MAX_ADMIN_CODE) revert InvalidAdminCode(); + inclusionTier = _findCheapestInclusionTier(countryCode, adminCode, false); + bond = tierBond(inclusionTier, false); + } + + /// @notice Returns the cheapest tier that guarantees a **country** fleet + /// appears in every `buildHighestBondedUuidBundle` query within + /// the country (across all active admin areas). + /// @dev Bounded view — iterates only over active admin areas in the + /// specific country (max 255). Safe for RPC calls. + function countryInclusionHint(uint16 countryCode) external view returns (uint256 inclusionTier, uint256 bond) { + if (countryCode == 0 || countryCode > MAX_COUNTRY_CODE) revert InvalidCountryCode(); + + // Check the country-only location (no admin area active). + inclusionTier = _findCheapestInclusionTier(countryCode, 0, true); + + // Scan only admin areas belonging to this specific country (bounded by MAX_ADMIN_CODE=255). + uint32[] storage countryAreas = _countryAdminAreas[countryCode]; + uint256 len = countryAreas.length; + for (uint256 i = 0; i < len; ++i) { + uint16 admin = _adminFromRegion(countryAreas[i]); + uint256 t = _findCheapestInclusionTier(countryCode, admin, true); + if (t > inclusionTier) inclusionTier = t; + } + bond = tierBond(inclusionTier, true); + } + + /// @notice Highest non-empty tier in a region, or 0 if none. + function highestActiveTier(uint32 regionKey) external view returns (uint256) { + uint256 tierCount = regionTierCount[regionKey]; + if (tierCount == 0) return 0; + return tierCount - 1; + } + + /// @notice Number of members in a specific tier of a region. + function tierMemberCount(uint32 regionKey, uint256 tier) external view returns (uint256) { + return _regionTierMembers[regionKey][tier].length; + } + + /// @notice All token IDs in a specific tier of a region. + function getTierMembers(uint32 regionKey, uint256 tier) external view returns (uint256[] memory) { + return _regionTierMembers[regionKey][tier]; + } + + /// @notice All UUIDs in a specific tier of a region. + function getTierUuids(uint32 regionKey, uint256 tier) external view returns (bytes16[] memory uuids) { + uint256[] storage members = _regionTierMembers[regionKey][tier]; + uuids = new bytes16[](members.length); + for (uint256 i = 0; i < members.length; ++i) { + uuids[i] = tokenUuid(members[i]); + } + } + + /// @notice UUID for a token ID (extracts lower 128 bits). + function tokenUuid(uint256 tokenId) public pure returns (bytes16) { + return bytes16(uint128(tokenId)); + } + + /// @notice Region key encoded in a token ID (extracts bits 128-159). + function tokenRegion(uint256 tokenId) public pure returns (uint32) { + return uint32(tokenId >> 128); + } + + /// @notice Computes the deterministic token ID for a uuid+region pair. + function computeTokenId(bytes16 uuid, uint32 regionKey) public pure returns (uint256) { + return (uint256(regionKey) << 128) | uint256(uint128(uuid)); + } + + /// @notice Bond amount for a token. Returns 0 for nonexistent tokens. + function bonds(uint256 tokenId) external view returns (uint256) { + if (_ownerOf(tokenId) == address(0)) return 0; + uint32 region = tokenRegion(tokenId); + if (region == OWNED_REGION_KEY) return BASE_BOND; + return tierBond(fleetTier[tokenId], _isCountryRegion(region)); + } + + /// @notice Returns true if the UUID is in owned-only state (claimed but not registered). + function isOwnedOnly(bytes16 uuid) external view returns (bool) { + return uuidLevel[uuid] == RegistrationLevel.Owned; + } + + /// @notice Returns the effective operator for a UUID. + /// If no explicit operator is set, returns the uuidOwner (owner acts as operator). + /// Returns address(0) if UUID is not registered. + /// @param uuid The UUID to query. + /// @return operator The effective operator address responsible for tier maintenance. + function operatorOf(bytes16 uuid) public view returns (address operator) { + operator = uuidOperator[uuid]; + if (operator == address(0)) { + operator = uuidOwner[uuid]; + } + } + + // ══════════════════════════════════════════════ + // Views: EdgeBeaconScanner discovery + // ══════════════════════════════════════════════ + + /// @notice Builds a priority-ordered bundle of up to 20 UUIDs for an EdgeBeaconScanner, + /// merging the highest-bonded tiers across admin-area and country levels. + /// + /// @dev **Priority Rules:** + /// 1. Higher bond tier always beats lower bond tier + /// 2. Within same tier: local (admin area) beats country + /// 3. Within same tier + level: earlier registration wins + /// + /// **Economic Fairness:** Country fleets pay 16× more (COUNTRY_BOND_MULTIPLIER) + /// than local fleets at the same tier. This means a local can reach tier 4 + /// for the same cost a country player pays for tier 0, giving locals a + /// significant economic advantage when competing for bundle slots. + /// + /// @param countryCode EdgeBeaconScanner country (must be > 0). + /// @param adminCode EdgeBeaconScanner admin area (must be > 0). + /// @return uuids The merged UUID bundle (up to 20). + /// @return count Actual number of UUIDs returned. + function buildHighestBondedUuidBundle(uint16 countryCode, uint16 adminCode) + external + view + returns (bytes16[] memory uuids, uint256 count) + { + if (countryCode == 0) revert InvalidCountryCode(); + if (adminCode == 0) revert AdminAreaRequired(); + + uint32 countryKey = uint32(countryCode); + uint32 adminKey = makeAdminRegion(countryCode, adminCode); + + (uuids, count, , ) = _buildHighestBondedUuidBundle(countryKey, adminKey); + } + + /// @notice Builds a bundle containing ONLY country-level fleets for a country. + /// Use this when no admin areas are active to verify country fleet positions. + /// + /// @dev When no admin areas exist in a country, EdgeBeaconScanners are not yet + /// active there. This function lets country fleet owners inspect their + /// competitive position before scanners come online. + /// + /// The returned bundle represents the country-only contribution to any + /// future admin-area bundle. Local fleets (when they appear) will have + /// priority over country fleets at the same tier. + /// + /// @param countryCode ISO 3166-1 numeric country code (1-999). + /// @return uuids The country-only UUID bundle (up to 20). + /// @return count Actual number of UUIDs returned. + function buildCountryOnlyBundle(uint16 countryCode) + external + view + returns (bytes16[] memory uuids, uint256 count) + { + if (countryCode == 0 || countryCode > MAX_COUNTRY_CODE) revert InvalidCountryCode(); + + uint32 countryKey = uint32(countryCode); + // Use a virtual admin region with no members (adminCode=0) + uint32 adminKey = makeAdminRegion(countryCode, 0); + + (uuids, count, , ) = _buildHighestBondedUuidBundle(countryKey, adminKey); + } + + /// @dev Internal bundle builder that returns additional state for `_findCheapestInclusionTier`. + /// + /// Builds a priority-ordered bundle by descending from highestTier to tier 0, + /// including admin-area members before country members at each tier. + /// + /// @return uuids The UUIDs included in the bundle (trimmed to actual count). + /// @return count Number of UUIDs in the bundle. + /// @return highestTier The highest tier with any registered members. + /// @return lowestTier The lowest tier processed (may be > 0 if bundle filled early). + function _buildHighestBondedUuidBundle(uint32 countryKey, uint32 adminKey) + internal + view + returns (bytes16[] memory uuids, uint256 count, uint256 highestTier, uint256 lowestTier) + { + highestTier = _findMaxTierIndex(countryKey, adminKey); + + uuids = new bytes16[](MAX_BONDED_UUID_BUNDLE_SIZE); + + // Simple tier-descent: at each tier, locals first, then country + for (lowestTier = highestTier + 1; lowestTier > 0 && count < MAX_BONDED_UUID_BUNDLE_SIZE;) { + unchecked { --lowestTier; } + + // Include local (admin area) members first + count = _appendTierUuids(adminKey, lowestTier, uuids, count); + + // Include country members + count = _appendTierUuids(countryKey, lowestTier, uuids, count); + } + + // Trim array to actual size + assembly { + mstore(uuids, count) + } + } + + /// @dev Appends UUIDs from a region's tier to the bundle array. + /// If the tier has no members (empty region or tier beyond regionTierCount), + /// this is a no-op. Returns the updated count. + function _appendTierUuids( + uint32 regionKey, + uint256 tier, + bytes16[] memory uuids, + uint256 count + ) internal view returns (uint256) { + uint256[] storage members = _regionTierMembers[regionKey][tier]; + uint256 len = members.length; + uint256 room = MAX_BONDED_UUID_BUNDLE_SIZE - count; + uint256 toInclude = len < room ? len : room; + + for (uint256 i = 0; i < toInclude; ++i) { + uuids[count] = tokenUuid(members[i]); + unchecked { ++count; } + } + return count; + } + + // ══════════════════════════════════════════════ + // Views: Region indexes + // ══════════════════════════════════════════════ + + /// @notice Returns all country codes with at least one active fleet. + function getActiveCountries() external view returns (uint16[] memory) { + return _activeCountries; + } + + /// @notice Returns all admin-area region keys with at least one active fleet. + /// @dev Computed by iterating active countries. O(countries × avg_admins). + function getActiveAdminAreas() external view returns (uint32[] memory) { + // Count total admin areas across all countries + uint256 total = 0; + uint256 countryCount = _activeCountries.length; + for (uint256 i = 0; i < countryCount; ++i) { + total += _countryAdminAreas[_activeCountries[i]].length; + } + + // Build result array + uint32[] memory result = new uint32[](total); + uint256 idx = 0; + for (uint256 i = 0; i < countryCount; ++i) { + uint32[] storage areas = _countryAdminAreas[_activeCountries[i]]; + uint256 areaCount = areas.length; + for (uint256 j = 0; j < areaCount; ++j) { + result[idx++] = areas[j]; + } + } + return result; + } + + /// @notice Returns the admin areas active in a specific country. + /// @dev Useful for off-chain enumeration without full index scan. + function getCountryAdminAreas(uint16 countryCode) external view returns (uint32[] memory) { + return _countryAdminAreas[countryCode]; + } + + /// @notice Builds an admin-area region key from country + admin codes. + /// @dev Country region key is simply uint32(countryCode) - no helper needed. + function makeAdminRegion(uint16 countryCode, uint16 adminCode) public pure returns (uint32) { + return (uint32(countryCode) << uint32(ADMIN_SHIFT)) | uint32(adminCode); + } + + // ══════════════════════════════════════════════ + // Internals + // ══════════════════════════════════════════════ + + // -- Region key encoding -- + + /// @dev Extracts the country code from an admin-area region key. + function _countryFromRegion(uint32 adminRegion) internal pure returns (uint16) { + return uint16(adminRegion >> uint32(ADMIN_SHIFT)); + } + + /// @dev Extracts the admin code from an admin-area region key. + function _adminFromRegion(uint32 adminRegion) internal pure returns (uint16) { + return uint16(adminRegion & ADMIN_CODE_MASK); + } + + /// @dev Returns true if the region key represents a country-level registration. + /// Region 0 (owned-only) is not a country region. + function _isCountryRegion(uint32 regionKey) internal pure returns (bool) { + return regionKey > 0 && regionKey <= MAX_COUNTRY_CODE; + } + + // -- Bond transfer helpers -- + + /// @dev Pulls bond tokens from an address (CEI: call after state changes). + function _pullBond(address from, uint256 amount) internal { + if (amount > 0) { + BOND_TOKEN.safeTransferFrom(from, address(this), amount); + } + } + + /// @dev Refunds bond tokens to an address (CEI: call after state changes). + function _refundBond(address to, uint256 amount) internal { + if (amount > 0) { + BOND_TOKEN.safeTransfer(to, amount); + } + } + + // -- UUID ownership helpers -- + + /// @dev Clears all UUID ownership state. Used when last token for a UUID is burned. + function _clearUuidOwnership(bytes16 uuid) internal { + delete uuidOwner[uuid]; + delete uuidTokenCount[uuid]; + delete uuidLevel[uuid]; + delete uuidOperator[uuid]; + delete uuidTotalTierBonds[uuid]; + } + + /// @dev Decrements UUID token count. Clears ownership if count reaches zero. + /// @return newCount The new token count after decrement. + function _decrementUuidCount(bytes16 uuid) internal returns (uint256 newCount) { + newCount = uuidTokenCount[uuid] - 1; + if (newCount == 0) { + _clearUuidOwnership(uuid); + } else { + uuidTokenCount[uuid] = newCount; + } + } + + // -- Tier cleanup helpers -- + + /// @dev Removes a fleet from its tier and cleans up associated state. + /// Does NOT burn the token - caller must handle that. + function _cleanupFleetFromTier(uint256 tokenId, uint32 region, uint256 tier) internal { + _removeFromTier(tokenId, region, tier); + delete fleetTier[tokenId]; + delete _indexInTier[tokenId]; + _trimTierCount(region); + _removeFromRegionIndex(region); + } + + // -- Registration helpers -- + + /// @dev Mints a fleet token to msg.sender. Used for fresh registrations. + /// @return tokenId The newly minted token ID. + function _mintFleetToken(bytes16 uuid, uint32 region, uint256 tier) internal returns (uint256 tokenId) { + tokenId = computeTokenId(uuid, region); + fleetTier[tokenId] = tier; + _addToTier(tokenId, region, tier); + _addToRegionIndex(region); + _mint(msg.sender, tokenId); + } + + /// @dev Mints a fleet token to a specific owner. Used when operator registers for an owner. + /// @return tokenId The newly minted token ID. + function _mintFleetTokenTo(address to, bytes16 uuid, uint32 region, uint256 tier) internal returns (uint256 tokenId) { + tokenId = computeTokenId(uuid, region); + fleetTier[tokenId] = tier; + _addToTier(tokenId, region, tier); + _addToRegionIndex(region); + _mint(to, tokenId); + } + + /// @dev Shared registration logic. Handles fresh, Owned → Registered, and multi-region registrations. + /// Only operator can register. Operator pays tier bond; owner pays BASE_BOND. + /// For fresh UUIDs, caller becomes both owner and operator. + /// @param uuid The Proximity UUID to register. + /// @param region The region key (country or admin area). + /// @param targetTier The tier to register at. + function _register(bytes16 uuid, uint32 region, uint256 targetTier) internal returns (uint256 tokenId) { + RegistrationLevel existingLevel = uuidLevel[uuid]; + bool isCountry = _isCountryRegion(region); + RegistrationLevel targetLevel = isCountry ? RegistrationLevel.Country : RegistrationLevel.Local; + uint256 targetTierBond = tierBond(targetTier, isCountry); + + if (existingLevel == RegistrationLevel.Owned) { + // Owned → Registered transition: only operator can register + address operator = operatorOf(uuid); + if (operator != msg.sender) revert NotOperator(); + address owner = uuidOwner[uuid]; + + _burn(uint256(uint128(uuid))); // Burn owned-only token + uuidLevel[uuid] = targetLevel; + uuidTotalTierBonds[uuid] = targetTierBond; + + tokenId = _mintFleetTokenTo(owner, uuid, region, targetTier); + + // Operator pays full tier bond (owner already paid BASE_BOND via claimUuid) + _pullBond(operator, targetTierBond); + + emit FleetRegistered(owner, uuid, tokenId, region, targetTier, targetTierBond, operator); + } else if (existingLevel == RegistrationLevel.None) { + // Fresh registration: caller becomes owner+operator, pays BASE_BOND + tier bond + uuidOwner[uuid] = msg.sender; + uuidLevel[uuid] = targetLevel; + uuidTokenCount[uuid] = 1; + uuidTotalTierBonds[uuid] = targetTierBond; + // uuidOperator stays address(0) - caller acts as operator via operatorOf() + + tokenId = _mintFleetToken(uuid, region, targetTier); + + // Caller pays BASE_BOND (ownership) + tier bond (registration) + _pullBond(msg.sender, BASE_BOND + targetTierBond); + + emit FleetRegistered(msg.sender, uuid, tokenId, region, targetTier, BASE_BOND + targetTierBond, msg.sender); + } else { + // Multi-region registration: only operator can register additional regions + address operator = operatorOf(uuid); + if (operator != msg.sender) revert NotOperator(); + if (existingLevel != targetLevel) revert UuidLevelMismatch(); + address owner = uuidOwner[uuid]; + + uuidTokenCount[uuid]++; + uuidTotalTierBonds[uuid] += targetTierBond; + + tokenId = _mintFleetTokenTo(owner, uuid, region, targetTier); + + // Operator pays tier bond + _pullBond(operator, targetTierBond); + + emit FleetRegistered(owner, uuid, tokenId, region, targetTier, targetTierBond, operator); + } + } + + /// @dev Shared promotion logic. Only operator can call. + function _promote(uint256 tokenId, uint256 targetTier) internal { + bytes16 uuid = tokenUuid(tokenId); + address operator = operatorOf(uuid); + if (operator != msg.sender) revert NotOperator(); + + uint32 region = tokenRegion(tokenId); + uint256 currentTier = fleetTier[tokenId]; + if (targetTier <= currentTier) revert TargetTierNotHigher(); + if (targetTier >= MAX_TIERS) revert MaxTiersReached(); + if (_regionTierMembers[region][targetTier].length >= TIER_CAPACITY) revert TierFull(); + + bool isCountry = _isCountryRegion(region); + uint256 currentBond = tierBond(currentTier, isCountry); + uint256 targetBond = tierBond(targetTier, isCountry); + uint256 additionalBond = targetBond - currentBond; + + // Effects + uuidTotalTierBonds[uuid] += additionalBond; + _removeFromTier(tokenId, region, currentTier); + fleetTier[tokenId] = targetTier; + _addToTier(tokenId, region, targetTier); + + // Interaction: pull from operator + _pullBond(operator, additionalBond); + + emit FleetPromoted(tokenId, currentTier, targetTier, additionalBond); + } + + /// @dev Shared demotion logic. Refunds bond difference to operator. + function _demote(uint256 tokenId, uint256 targetTier) internal { + bytes16 uuid = tokenUuid(tokenId); + address operator = operatorOf(uuid); + if (operator != msg.sender) revert NotOperator(); + + uint32 region = tokenRegion(tokenId); + uint256 currentTier = fleetTier[tokenId]; + if (targetTier >= currentTier) revert TargetTierNotLower(); + if (_regionTierMembers[region][targetTier].length >= TIER_CAPACITY) revert TierFull(); + + bool isCountry = _isCountryRegion(region); + uint256 currentBond = tierBond(currentTier, isCountry); + uint256 targetBond = tierBond(targetTier, isCountry); + uint256 refund = currentBond - targetBond; + + // Effects + uuidTotalTierBonds[uuid] -= refund; + _removeFromTier(tokenId, region, currentTier); + fleetTier[tokenId] = targetTier; + _addToTier(tokenId, region, targetTier); + _trimTierCount(region); + + // Interaction: refund to operator + _refundBond(operator, refund); + + emit FleetDemoted(tokenId, currentTier, targetTier, refund); + } + + /// @dev Validates that a tier is available for registration (pure validation, no state changes). + function _validateExplicitTier(uint32 region, uint256 targetTier) internal view { + if (targetTier >= MAX_TIERS) revert MaxTiersReached(); + if (_regionTierMembers[region][targetTier].length >= TIER_CAPACITY) revert TierFull(); + } + + // -- Bundle-level helpers (shared by buildHighestBondedUuidBundle & inclusion hints) -- + + /// @dev Finds the highest active tier index across both bundle levels. + function _findMaxTierIndex(uint32 countryKey, uint32 adminKey) + internal + view + returns (uint256 maxTierIndex) + { + uint256 adminTiers = regionTierCount[adminKey]; + uint256 countryTiers = regionTierCount[countryKey]; + + uint256 maxTier = adminTiers > 0 ? adminTiers - 1 : 0; + if (countryTiers > 0 && countryTiers - 1 > maxTier) maxTier = countryTiers - 1; + return maxTier; + } + + // -- Inclusion-tier logic -- + + /// @dev Uses `_buildHighestBondedUuidBundle` to determine the cheapest tier at + /// `candidateRegion` that guarantees bundle inclusion. Bounded: O(MAX_TIERS). + /// + /// Walks from the bundle's lowestTier upward, "unwinding" the bundle count + /// by subtracting both regions' contributions at each tier. Returns the first + /// tier where: + /// (a) The tier has capacity (< TIER_CAPACITY members). + /// (b) The unwound count shows room in the bundle (< MAX_BONDED_UUID_BUNDLE_SIZE). + /// + /// If no existing tier qualifies and highestTier + 1 < MAX_TIERS, returns + /// highestTier + 1 (joining above current max guarantees inclusion). + /// + /// @param countryCode The country code for the bundle location. + /// @param adminCode The admin area code (0 for country-only bundles). + /// @param isCountry True if candidate is joining country region, false for admin. + function _findCheapestInclusionTier(uint16 countryCode, uint16 adminCode, bool isCountry) + internal + view + returns (uint256) + { + uint32 countryKey = uint32(countryCode); + uint32 adminKey = makeAdminRegion(countryCode, adminCode); + uint32 candidateRegion = isCountry ? countryKey : adminKey; + + (, uint256 count, uint256 highestTier, uint256 lowestTier) = _buildHighestBondedUuidBundle(countryKey, adminKey); + + // Walk from lowestTier upward, unwinding the bundle count at each tier. + // Subtracting both regions' contributions simulates "what if we built the + // bundle stopping at this tier instead". + for (uint256 tier = lowestTier; tier <= highestTier; ++tier) { + bool tierHasCapacity = _regionTierMembers[candidateRegion][tier].length < TIER_CAPACITY; + bool bundleHasRoom = count < MAX_BONDED_UUID_BUNDLE_SIZE; + + if (tierHasCapacity && bundleHasRoom) { + return tier; + } + + // Unwind: subtract both regions' contributions at this tier. + // Use saturating subtraction to handle edge cases gracefully. + uint256 adminMembers = _regionTierMembers[adminKey][tier].length; + uint256 countryMembers = _regionTierMembers[countryKey][tier].length; + uint256 tierTotal = adminMembers + countryMembers; + count = tierTotal > count ? 0 : count - tierTotal; + } + + // No fit in existing tiers — try joining above current max. + if (highestTier < MAX_TIERS - 1) { + return highestTier + 1; + } + + revert MaxTiersReached(); + } + + /// @dev Appends a token to a region's tier member array and records its index. + /// Updates regionTierCount if this opens a new highest tier. + function _addToTier(uint256 tokenId, uint32 region, uint256 tier) internal { + _regionTierMembers[region][tier].push(tokenId); + _indexInTier[tokenId] = _regionTierMembers[region][tier].length - 1; + + // Update tier count if we're opening a new tier + if (tier >= regionTierCount[region]) { + regionTierCount[region] = tier + 1; + } + } + + /// @dev Swap-and-pop removal from a region's tier member array. + function _removeFromTier(uint256 tokenId, uint32 region, uint256 tier) internal { + uint256[] storage members = _regionTierMembers[region][tier]; + uint256 idx = _indexInTier[tokenId]; + uint256 lastIdx = members.length - 1; + + if (idx != lastIdx) { + uint256 lastTokenId = members[lastIdx]; + members[idx] = lastTokenId; + _indexInTier[lastTokenId] = idx; + } + members.pop(); + } + + /// @dev Shrinks regionTierCount so the top tier is always non-empty. + function _trimTierCount(uint32 region) internal { + uint256 tierCount = regionTierCount[region]; + while (tierCount > 0 && _regionTierMembers[region][tierCount - 1].length == 0) { + tierCount--; + } + regionTierCount[region] = tierCount; + } + + // -- Region index maintenance -- + + /// @dev Adds a region to the appropriate index set if not already present. + function _addToRegionIndex(uint32 region) internal { + if (_isCountryRegion(region)) { + // Country + uint16 cc = uint16(region); + if (_activeCountryIndex[cc] == 0) { + _activeCountries.push(cc); + _activeCountryIndex[cc] = _activeCountries.length; // 1-indexed + } + } else { + // Admin area: add to country's list + if (_countryAdminAreaIndex[region] == 0) { + uint16 cc = _countryFromRegion(region); + // Ensure country is in active list (for getActiveAdminAreas iteration) + if (_activeCountryIndex[cc] == 0) { + _activeCountries.push(cc); + _activeCountryIndex[cc] = _activeCountries.length; + } + _countryAdminAreas[cc].push(region); + _countryAdminAreaIndex[region] = _countryAdminAreas[cc].length; + } + } + } + + /// @dev Removes a region from the index set if the region is now completely empty. + function _removeFromRegionIndex(uint32 region) internal { + if (regionTierCount[region] > 0) return; // still has fleets + + if (_isCountryRegion(region)) { + uint16 cc = uint16(region); + uint256 oneIdx = _activeCountryIndex[cc]; + if (oneIdx > 0) { + // Only remove country if it has no admin areas + if (_countryAdminAreas[cc].length > 0) return; + + uint256 lastIdx = _activeCountries.length - 1; + uint256 removeIdx = oneIdx - 1; + if (removeIdx != lastIdx) { + uint16 lastCountryCode = _activeCountries[lastIdx]; + _activeCountries[removeIdx] = lastCountryCode; + _activeCountryIndex[lastCountryCode] = oneIdx; + } + _activeCountries.pop(); + delete _activeCountryIndex[cc]; + } + } else { + // Admin area: remove from country's list + uint256 oneIdx = _countryAdminAreaIndex[region]; + if (oneIdx > 0) { + uint16 cc = _countryFromRegion(region); + uint32[] storage countryAreas = _countryAdminAreas[cc]; + uint256 lastIdx = countryAreas.length - 1; + uint256 removeIdx = oneIdx - 1; + if (removeIdx != lastIdx) { + uint32 lastArea = countryAreas[lastIdx]; + countryAreas[removeIdx] = lastArea; + _countryAdminAreaIndex[lastArea] = oneIdx; + } + countryAreas.pop(); + delete _countryAdminAreaIndex[region]; + + // Remove country from active list if no more admin areas AND no country fleets + if (countryAreas.length == 0 && regionTierCount[uint32(cc)] == 0) { + uint256 countryOneIdx = _activeCountryIndex[cc]; + if (countryOneIdx > 0) { + uint256 countryLastIdx = _activeCountries.length - 1; + uint256 countryRemoveIdx = countryOneIdx - 1; + if (countryRemoveIdx != countryLastIdx) { + uint16 lastCountryCode = _activeCountries[countryLastIdx]; + _activeCountries[countryRemoveIdx] = lastCountryCode; + _activeCountryIndex[lastCountryCode] = countryOneIdx; + } + _activeCountries.pop(); + delete _activeCountryIndex[cc]; + } + } + } + } + } + + // ────────────────────────────────────────────── + // Overrides required by ERC721Enumerable + // ────────────────────────────────────────────── + + function _update(address to, uint256 tokenId, address auth) internal override(ERC721Enumerable) returns (address) { + address from = super._update(to, tokenId, auth); + + // For owned-only tokens, transfer uuidOwner when the token is transferred + // This allows marketplace trading of owned-only UUIDs + uint32 region = tokenRegion(tokenId); + if (region == OWNED_REGION_KEY && from != address(0) && to != address(0)) { + uuidOwner[tokenUuid(tokenId)] = to; + } + + return from; + } + + function _increaseBalance(address account, uint128 value) internal override(ERC721Enumerable) { + super._increaseBalance(account, value); + } + + function supportsInterface(bytes4 interfaceId) public view override(ERC721Enumerable) returns (bool) { + return super.supportsInterface(interfaceId); + } +} diff --git a/src/swarms/ServiceProvider.sol b/src/swarms/ServiceProvider.sol new file mode 100644 index 00000000..e4a777b7 --- /dev/null +++ b/src/swarms/ServiceProvider.sol @@ -0,0 +1,54 @@ +// SPDX-License-Identifier: BSD-3-Clause-Clear + +pragma solidity ^0.8.24; + +import {ERC721} from "@openzeppelin/contracts/token/ERC721/ERC721.sol"; + +/** + * @title ServiceProvider + * @notice Permissionless ERC-721 representing ownership of a service endpoint URL. + * @dev TokenID = keccak256(url), guaranteeing one owner per URL. + */ +contract ServiceProvider is ERC721 { + error EmptyURL(); + error NotTokenOwner(); + + // Maps TokenID -> Provider URL + mapping(uint256 => string) public providerUrls; + + event ProviderRegistered(address indexed owner, string url, uint256 indexed tokenId); + event ProviderBurned(address indexed owner, uint256 indexed tokenId); + + constructor() ERC721("Swarm Service Provider", "SSV") {} + + /// @notice Mints a new provider NFT for the given URL. + /// @param url The backend service URL (must be unique). + /// @return tokenId The deterministic token ID derived from `url`. + function registerProvider(string calldata url) external returns (uint256 tokenId) { + if (bytes(url).length == 0) { + revert EmptyURL(); + } + + tokenId = uint256(keccak256(bytes(url))); + + providerUrls[tokenId] = url; + + _mint(msg.sender, tokenId); + + emit ProviderRegistered(msg.sender, url, tokenId); + } + + /// @notice Burns the provider NFT. Caller must be the token owner. + /// @param tokenId The provider token ID to burn. + function burn(uint256 tokenId) external { + if (ownerOf(tokenId) != msg.sender) { + revert NotTokenOwner(); + } + + delete providerUrls[tokenId]; + + _burn(tokenId); + + emit ProviderBurned(msg.sender, tokenId); + } +} diff --git a/src/swarms/SwarmRegistryL1.sol b/src/swarms/SwarmRegistryL1.sol new file mode 100644 index 00000000..05cbaf6c --- /dev/null +++ b/src/swarms/SwarmRegistryL1.sol @@ -0,0 +1,376 @@ +// SPDX-License-Identifier: BSD-3-Clause-Clear + +pragma solidity ^0.8.24; + +// NOTE: SSTORE2 is not compatible with ZkSync Era due to EXTCODECOPY limitation. +// For ZkSync deployment, consider using chunked storage or calldata alternatives. +import {SSTORE2} from "solady/utils/SSTORE2.sol"; +import {ReentrancyGuard} from "@openzeppelin/contracts/utils/ReentrancyGuard.sol"; +import {FleetIdentity} from "./FleetIdentity.sol"; +import {ServiceProvider} from "./ServiceProvider.sol"; + +/** + * @title SwarmRegistryL1 + * @notice Permissionless BLE swarm registry optimized for Ethereum L1 (uses SSTORE2 for filter storage). + * @dev Not compatible with ZkSync Era — use SwarmRegistryUniversal instead. + * + * Swarms are defined for a **fleet UUID** (not a token ID), allowing swarms to be + * registered for any UUID that has been claimed/registered in FleetIdentity, + * regardless of whether it's assigned to a region or is in "owned-only" mode. + * This decouples swarm management from geographic tier placement. + */ +contract SwarmRegistryL1 is ReentrancyGuard { + error InvalidFingerprintSize(); + error InvalidFilterSize(); + error InvalidUuid(); + error NotUuidOwner(); + error ProviderDoesNotExist(); + error NotProviderOwner(); + error SwarmNotFound(); + error InvalidSwarmData(); + error SwarmAlreadyExists(); + error SwarmNotOrphaned(); + error SwarmOrphaned(); + + enum SwarmStatus { + REGISTERED, + ACCEPTED, + REJECTED + } + + // Internal Schema version for Tag ID construction + enum TagType { + IBEACON_PAYLOAD_ONLY, // 0x00: proxUUID || major || minor + IBEACON_INCLUDES_MAC, // 0x01: proxUUID || major || minor || MAC (Normalized) + VENDOR_ID, // 0x02: companyID || hash(vendorBytes) + GENERIC // 0x03 + + } + + struct Swarm { + bytes16 fleetUuid; // Fleet UUID (not token ID) - allows swarms for any registered UUID + uint256 providerId; // The Service Provider TokenID + address filterPointer; // SSTORE2 pointer + uint8 fingerprintSize; + TagType tagType; + SwarmStatus status; + } + + uint8 public constant MAX_FINGERPRINT_SIZE = 16; + + FleetIdentity public immutable FLEET_CONTRACT; + + ServiceProvider public immutable PROVIDER_CONTRACT; + + // SwarmID -> Swarm + mapping(uint256 => Swarm) public swarms; + + // UUID -> List of SwarmIDs (keyed by fleet UUID, not token ID) + mapping(bytes16 => uint256[]) public uuidSwarms; + + // SwarmID -> index in uuidSwarms[fleetUuid] (for O(1) removal) + mapping(uint256 => uint256) public swarmIndexInUuid; + + event SwarmRegistered(uint256 indexed swarmId, bytes16 indexed fleetUuid, uint256 indexed providerId, address owner); + event SwarmStatusChanged(uint256 indexed swarmId, SwarmStatus status); + event SwarmFilterUpdated(uint256 indexed swarmId, address indexed owner, uint32 filterSize); + event SwarmProviderUpdated(uint256 indexed swarmId, uint256 indexed oldProvider, uint256 indexed newProvider); + event SwarmDeleted(uint256 indexed swarmId, bytes16 indexed fleetUuid, address indexed owner); + event SwarmPurged(uint256 indexed swarmId, bytes16 indexed fleetUuid, address indexed purgedBy); + + /// @notice Derives a deterministic swarm ID. Callable off-chain to predict IDs before registration. + /// @return swarmId keccak256(fleetUuid, providerId, filterData) + function computeSwarmId(bytes16 fleetUuid, uint256 providerId, bytes calldata filterData) + public + pure + returns (uint256) + { + return uint256(keccak256(abi.encode(fleetUuid, providerId, filterData))); + } + + constructor(address _fleetContract, address _providerContract) { + if (_fleetContract == address(0) || _providerContract == address(0)) { + revert InvalidSwarmData(); + } + FLEET_CONTRACT = FleetIdentity(_fleetContract); + PROVIDER_CONTRACT = ServiceProvider(_providerContract); + } + + /// @notice Registers a new swarm. Caller must own the fleet UUID (via FleetIdentity.uuidOwner). + /// @param fleetUuid Fleet UUID (bytes16) - the UUID must be registered in FleetIdentity. + /// @param providerId Service provider token ID. + /// @param filterData XOR filter blob (1–24 576 bytes). + /// @param fingerprintSize Fingerprint width in bits (1–16). + /// @param tagType Tag identity schema. + /// @return swarmId Deterministic ID for this swarm. + function registerSwarm( + bytes16 fleetUuid, + uint256 providerId, + bytes calldata filterData, + uint8 fingerprintSize, + TagType tagType + ) external nonReentrant returns (uint256 swarmId) { + if (fleetUuid == bytes16(0)) { + revert InvalidUuid(); + } + if (fingerprintSize == 0 || fingerprintSize > MAX_FINGERPRINT_SIZE) { + revert InvalidFingerprintSize(); + } + if (filterData.length == 0 || filterData.length > 24576) { + revert InvalidFilterSize(); + } + + // Check UUID ownership - works for any registered UUID regardless of region + if (FLEET_CONTRACT.uuidOwner(fleetUuid) != msg.sender) { + revert NotUuidOwner(); + } + if (PROVIDER_CONTRACT.ownerOf(providerId) == address(0)) { + revert ProviderDoesNotExist(); + } + + swarmId = computeSwarmId(fleetUuid, providerId, filterData); + + if (swarms[swarmId].filterPointer != address(0)) { + revert SwarmAlreadyExists(); + } + + Swarm storage s = swarms[swarmId]; + s.fleetUuid = fleetUuid; + s.providerId = providerId; + s.fingerprintSize = fingerprintSize; + s.tagType = tagType; + s.status = SwarmStatus.REGISTERED; + + uuidSwarms[fleetUuid].push(swarmId); + swarmIndexInUuid[swarmId] = uuidSwarms[fleetUuid].length - 1; + + s.filterPointer = SSTORE2.write(filterData); + + emit SwarmRegistered(swarmId, fleetUuid, providerId, msg.sender); + } + + /// @notice Approves a swarm. Caller must own the provider NFT. + /// @param swarmId The swarm to accept. + function acceptSwarm(uint256 swarmId) external { + Swarm storage s = swarms[swarmId]; + if (s.filterPointer == address(0)) revert SwarmNotFound(); + + (bool fleetValid, bool providerValid) = isSwarmValid(swarmId); + if (!fleetValid || !providerValid) revert SwarmOrphaned(); + + if (PROVIDER_CONTRACT.ownerOf(s.providerId) != msg.sender) { + revert NotProviderOwner(); + } + s.status = SwarmStatus.ACCEPTED; + emit SwarmStatusChanged(swarmId, SwarmStatus.ACCEPTED); + } + + /// @notice Rejects a swarm. Caller must own the provider NFT. + /// @param swarmId The swarm to reject. + function rejectSwarm(uint256 swarmId) external { + Swarm storage s = swarms[swarmId]; + if (s.filterPointer == address(0)) revert SwarmNotFound(); + + (bool fleetValid, bool providerValid) = isSwarmValid(swarmId); + if (!fleetValid || !providerValid) revert SwarmOrphaned(); + + if (PROVIDER_CONTRACT.ownerOf(s.providerId) != msg.sender) { + revert NotProviderOwner(); + } + s.status = SwarmStatus.REJECTED; + emit SwarmStatusChanged(swarmId, SwarmStatus.REJECTED); + } + + /// @notice Replaces the XOR filter. Resets status to REGISTERED. Caller must own the fleet UUID. + /// @param swarmId The swarm to update. + /// @param newFilterData Replacement filter blob. + function updateSwarmFilter(uint256 swarmId, bytes calldata newFilterData) external nonReentrant { + Swarm storage s = swarms[swarmId]; + if (s.filterPointer == address(0)) { + revert SwarmNotFound(); + } + if (FLEET_CONTRACT.uuidOwner(s.fleetUuid) != msg.sender) { + revert NotUuidOwner(); + } + if (newFilterData.length == 0 || newFilterData.length > 24576) { + revert InvalidFilterSize(); + } + + s.status = SwarmStatus.REGISTERED; + + s.filterPointer = SSTORE2.write(newFilterData); + + emit SwarmFilterUpdated(swarmId, msg.sender, uint32(newFilterData.length)); + } + + /// @notice Reassigns the service provider. Resets status to REGISTERED. Caller must own the fleet UUID. + /// @param swarmId The swarm to update. + /// @param newProviderId New provider token ID. + function updateSwarmProvider(uint256 swarmId, uint256 newProviderId) external { + Swarm storage s = swarms[swarmId]; + if (s.filterPointer == address(0)) { + revert SwarmNotFound(); + } + if (FLEET_CONTRACT.uuidOwner(s.fleetUuid) != msg.sender) { + revert NotUuidOwner(); + } + if (PROVIDER_CONTRACT.ownerOf(newProviderId) == address(0)) { + revert ProviderDoesNotExist(); + } + + uint256 oldProvider = s.providerId; + + s.providerId = newProviderId; + + s.status = SwarmStatus.REGISTERED; + + emit SwarmProviderUpdated(swarmId, oldProvider, newProviderId); + } + + /// @notice Permanently deletes a swarm. Caller must own the fleet UUID. + /// @param swarmId The swarm to delete. + function deleteSwarm(uint256 swarmId) external { + Swarm storage s = swarms[swarmId]; + if (s.filterPointer == address(0)) { + revert SwarmNotFound(); + } + if (FLEET_CONTRACT.uuidOwner(s.fleetUuid) != msg.sender) { + revert NotUuidOwner(); + } + + bytes16 fleetUuid = s.fleetUuid; + + _removeFromUuidSwarms(fleetUuid, swarmId); + + delete swarms[swarmId]; + + emit SwarmDeleted(swarmId, fleetUuid, msg.sender); + } + + /// @notice Returns whether the swarm's fleet UUID and provider NFT are still valid. + /// @param swarmId The swarm to check. + /// @return fleetValid True if the fleet UUID is still owned (uuidOwner != address(0)). + /// @return providerValid True if the provider NFT exists. + function isSwarmValid(uint256 swarmId) public view returns (bool fleetValid, bool providerValid) { + Swarm storage s = swarms[swarmId]; + if (s.filterPointer == address(0)) revert SwarmNotFound(); + + // Fleet is valid if UUID is still owned (not released) + fleetValid = FLEET_CONTRACT.uuidOwner(s.fleetUuid) != address(0); + + try PROVIDER_CONTRACT.ownerOf(s.providerId) returns (address) { + providerValid = true; + } catch { + providerValid = false; + } + } + + /// @notice Permissionless-ly removes a swarm whose fleet UUID has been released or provider NFT has been burned. + /// @param swarmId The orphaned swarm to purge. + function purgeOrphanedSwarm(uint256 swarmId) external { + Swarm storage s = swarms[swarmId]; + if (s.filterPointer == address(0)) revert SwarmNotFound(); + + (bool fleetValid, bool providerValid) = isSwarmValid(swarmId); + if (fleetValid && providerValid) revert SwarmNotOrphaned(); + + bytes16 fleetUuid = s.fleetUuid; + + _removeFromUuidSwarms(fleetUuid, swarmId); + + delete swarms[swarmId]; + + emit SwarmPurged(swarmId, fleetUuid, msg.sender); + } + + /// @notice Tests tag membership against the swarm's XOR filter. + /// @param swarmId The swarm to query. + /// @param tagHash keccak256 of the tag identity bytes (caller must pre-normalize per tagType). + /// @return isValid True if the tag passes the XOR filter check. + function checkMembership(uint256 swarmId, bytes32 tagHash) external view returns (bool isValid) { + Swarm storage s = swarms[swarmId]; + if (s.filterPointer == address(0)) { + revert SwarmNotFound(); + } + + // Reject queries against orphaned swarms + (bool fleetValid, bool providerValid) = isSwarmValid(swarmId); + if (!fleetValid || !providerValid) revert SwarmOrphaned(); + + uint256 dataLen; + address pointer = s.filterPointer; + assembly { + dataLen := extcodesize(pointer) + } + + // SSTORE2 adds 1 byte overhead (0x00), So actual data length = codeSize - 1. + if (dataLen > 0) { + unchecked { + --dataLen; + } + } + + // 2. Calculate M (number of slots) + uint256 m = (dataLen * 8) / s.fingerprintSize; + if (m == 0) return false; + + bytes32 h = tagHash; + + uint32 h1 = uint32(uint256(h)) % uint32(m); + uint32 h2 = uint32(uint256(h) >> 32) % uint32(m); + uint32 h3 = uint32(uint256(h) >> 64) % uint32(m); + + uint256 fpMask = (uint256(1) << s.fingerprintSize) - 1; + uint256 expectedFp = (uint256(h) >> 96) & fpMask; + + uint256 f1 = _readFingerprint(pointer, h1, s.fingerprintSize); + uint256 f2 = _readFingerprint(pointer, h2, s.fingerprintSize); + uint256 f3 = _readFingerprint(pointer, h3, s.fingerprintSize); + + return (f1 ^ f2 ^ f3) == expectedFp; + } + + /** + * @dev O(1) removal of a swarm from its UUID's swarm list using index tracking. + */ + function _removeFromUuidSwarms(bytes16 fleetUuid, uint256 swarmId) internal { + uint256[] storage arr = uuidSwarms[fleetUuid]; + uint256 index = swarmIndexInUuid[swarmId]; + uint256 lastId = arr[arr.length - 1]; + + arr[index] = lastId; + swarmIndexInUuid[lastId] = index; + arr.pop(); + delete swarmIndexInUuid[swarmId]; + } + + /** + * @dev Reads a packed fingerprint of arbitrary bit size from SSTORE2 blob. + * @param pointer The contract address storing data. + * @param index The slot index. + * @param bits The bit size of the fingerprint. + */ + function _readFingerprint(address pointer, uint256 index, uint8 bits) internal view returns (uint256) { + uint256 bitOffset = index * bits; + uint256 startByte = bitOffset / 8; + uint256 endByte = (bitOffset + bits - 1) / 8; + + // Read raw bytes. SSTORE2 uses 0-based index relative to data. + bytes memory chunk = SSTORE2.read(pointer, startByte, endByte + 1); + + // Convert chunk to uint256 + uint256 raw; + for (uint256 i = 0; i < chunk.length;) { + raw = (raw << 8) | uint8(chunk[i]); + unchecked { + ++i; + } + } + + uint256 totalBitsRead = chunk.length * 8; + uint256 localStart = bitOffset % 8; + uint256 shiftRight = totalBitsRead - (localStart + bits); + + return (raw >> shiftRight) & ((uint256(1) << bits) - 1); + } +} diff --git a/src/swarms/SwarmRegistryUniversal.sol b/src/swarms/SwarmRegistryUniversal.sol new file mode 100644 index 00000000..34f3216f --- /dev/null +++ b/src/swarms/SwarmRegistryUniversal.sol @@ -0,0 +1,385 @@ +// SPDX-License-Identifier: BSD-3-Clause-Clear + +pragma solidity ^0.8.24; + +import {ReentrancyGuard} from "@openzeppelin/contracts/utils/ReentrancyGuard.sol"; +import {FleetIdentity} from "./FleetIdentity.sol"; +import {ServiceProvider} from "./ServiceProvider.sol"; + +/** + * @title SwarmRegistryUniversal + * @notice Permissionless BLE swarm registry compatible with all EVM chains (including ZkSync Era). + * @dev Uses native `bytes` storage for cross-chain compatibility. + * + * Swarms are defined for a **fleet UUID** (not a token ID), allowing swarms to be + * registered for any UUID that has been claimed/registered in FleetIdentity, + * regardless of whether it's assigned to a region or is in "owned-only" mode. + * This decouples swarm management from geographic tier placement. + */ +contract SwarmRegistryUniversal is ReentrancyGuard { + error InvalidFingerprintSize(); + error InvalidFilterSize(); + error InvalidUuid(); + error NotUuidOwner(); + error ProviderDoesNotExist(); + error NotProviderOwner(); + error SwarmNotFound(); + error InvalidSwarmData(); + error FilterTooLarge(); + error SwarmAlreadyExists(); + error SwarmNotOrphaned(); + error SwarmOrphaned(); + + enum SwarmStatus { + REGISTERED, + ACCEPTED, + REJECTED + } + + enum TagType { + IBEACON_PAYLOAD_ONLY, // 0x00: proxUUID || major || minor + IBEACON_INCLUDES_MAC, // 0x01: proxUUID || major || minor || MAC (Normalized) + VENDOR_ID, // 0x02: companyID || hash(vendorBytes) + GENERIC // 0x03 + + } + + struct Swarm { + bytes16 fleetUuid; // Fleet UUID (not token ID) - allows swarms for any registered UUID + uint256 providerId; + uint32 filterLength; // Length of filter in bytes (max ~4GB, practically limited) + uint8 fingerprintSize; + TagType tagType; + SwarmStatus status; + } + + uint8 public constant MAX_FINGERPRINT_SIZE = 16; + + /// @notice Maximum filter size per swarm (24KB - fits in ~15M gas on cold write) + uint32 public constant MAX_FILTER_SIZE = 24576; + + FleetIdentity public immutable FLEET_CONTRACT; + + ServiceProvider public immutable PROVIDER_CONTRACT; + + /// @notice SwarmID -> Swarm metadata + mapping(uint256 => Swarm) public swarms; + + /// @notice SwarmID -> XOR filter data (stored as bytes) + mapping(uint256 => bytes) internal filterData; + + /// @notice UUID -> List of SwarmIDs (keyed by fleet UUID, not token ID) + mapping(bytes16 => uint256[]) public uuidSwarms; + + /// @notice SwarmID -> index in uuidSwarms[fleetUuid] (for O(1) removal) + mapping(uint256 => uint256) public swarmIndexInUuid; + + event SwarmRegistered( + uint256 indexed swarmId, bytes16 indexed fleetUuid, uint256 indexed providerId, address owner, uint32 filterSize + ); + + event SwarmStatusChanged(uint256 indexed swarmId, SwarmStatus status); + event SwarmFilterUpdated(uint256 indexed swarmId, address indexed owner, uint32 filterSize); + event SwarmProviderUpdated(uint256 indexed swarmId, uint256 indexed oldProvider, uint256 indexed newProvider); + event SwarmDeleted(uint256 indexed swarmId, bytes16 indexed fleetUuid, address indexed owner); + event SwarmPurged(uint256 indexed swarmId, bytes16 indexed fleetUuid, address indexed purgedBy); + + /// @notice Derives a deterministic swarm ID. Callable off-chain to predict IDs before registration. + /// @return swarmId keccak256(fleetUuid, providerId, filter) + function computeSwarmId(bytes16 fleetUuid, uint256 providerId, bytes calldata filter) public pure returns (uint256) { + return uint256(keccak256(abi.encode(fleetUuid, providerId, filter))); + } + + constructor(address _fleetContract, address _providerContract) { + if (_fleetContract == address(0) || _providerContract == address(0)) { + revert InvalidSwarmData(); + } + FLEET_CONTRACT = FleetIdentity(_fleetContract); + PROVIDER_CONTRACT = ServiceProvider(_providerContract); + } + + /// @notice Registers a new swarm. Caller must own the fleet UUID (via FleetIdentity.uuidOwner). + /// @param fleetUuid Fleet UUID (bytes16) - the UUID must be registered in FleetIdentity. + /// @param providerId Service provider token ID. + /// @param filter XOR filter blob (1–24 576 bytes). + /// @param fingerprintSize Fingerprint width in bits (1–16). + /// @param tagType Tag identity schema. + /// @return swarmId Deterministic ID for this swarm. + function registerSwarm( + bytes16 fleetUuid, + uint256 providerId, + bytes calldata filter, + uint8 fingerprintSize, + TagType tagType + ) external nonReentrant returns (uint256 swarmId) { + if (fleetUuid == bytes16(0)) { + revert InvalidUuid(); + } + if (fingerprintSize == 0 || fingerprintSize > MAX_FINGERPRINT_SIZE) { + revert InvalidFingerprintSize(); + } + if (filter.length == 0) { + revert InvalidFilterSize(); + } + if (filter.length > MAX_FILTER_SIZE) { + revert FilterTooLarge(); + } + + // Check UUID ownership - works for any registered UUID regardless of region + if (FLEET_CONTRACT.uuidOwner(fleetUuid) != msg.sender) { + revert NotUuidOwner(); + } + if (PROVIDER_CONTRACT.ownerOf(providerId) == address(0)) { + revert ProviderDoesNotExist(); + } + + swarmId = computeSwarmId(fleetUuid, providerId, filter); + + if (swarms[swarmId].filterLength != 0) { + revert SwarmAlreadyExists(); + } + + Swarm storage s = swarms[swarmId]; + s.fleetUuid = fleetUuid; + s.providerId = providerId; + s.filterLength = uint32(filter.length); + s.fingerprintSize = fingerprintSize; + s.tagType = tagType; + s.status = SwarmStatus.REGISTERED; + + filterData[swarmId] = filter; + + uuidSwarms[fleetUuid].push(swarmId); + swarmIndexInUuid[swarmId] = uuidSwarms[fleetUuid].length - 1; + + emit SwarmRegistered(swarmId, fleetUuid, providerId, msg.sender, uint32(filter.length)); + } + + /// @notice Approves a swarm. Caller must own the provider NFT. + /// @param swarmId The swarm to accept. + function acceptSwarm(uint256 swarmId) external { + Swarm storage s = swarms[swarmId]; + if (s.filterLength == 0) revert SwarmNotFound(); + + (bool fleetValid, bool providerValid) = isSwarmValid(swarmId); + if (!fleetValid || !providerValid) revert SwarmOrphaned(); + + if (PROVIDER_CONTRACT.ownerOf(s.providerId) != msg.sender) { + revert NotProviderOwner(); + } + s.status = SwarmStatus.ACCEPTED; + emit SwarmStatusChanged(swarmId, SwarmStatus.ACCEPTED); + } + + /// @notice Rejects a swarm. Caller must own the provider NFT. + /// @param swarmId The swarm to reject. + function rejectSwarm(uint256 swarmId) external { + Swarm storage s = swarms[swarmId]; + if (s.filterLength == 0) revert SwarmNotFound(); + + (bool fleetValid, bool providerValid) = isSwarmValid(swarmId); + if (!fleetValid || !providerValid) revert SwarmOrphaned(); + + if (PROVIDER_CONTRACT.ownerOf(s.providerId) != msg.sender) { + revert NotProviderOwner(); + } + s.status = SwarmStatus.REJECTED; + emit SwarmStatusChanged(swarmId, SwarmStatus.REJECTED); + } + + /// @notice Replaces the XOR filter. Resets status to REGISTERED. Caller must own the fleet UUID. + /// @param swarmId The swarm to update. + /// @param newFilterData Replacement filter blob. + function updateSwarmFilter(uint256 swarmId, bytes calldata newFilterData) external nonReentrant { + Swarm storage s = swarms[swarmId]; + if (s.filterLength == 0) { + revert SwarmNotFound(); + } + if (FLEET_CONTRACT.uuidOwner(s.fleetUuid) != msg.sender) { + revert NotUuidOwner(); + } + if (newFilterData.length == 0) { + revert InvalidFilterSize(); + } + if (newFilterData.length > MAX_FILTER_SIZE) { + revert FilterTooLarge(); + } + + s.filterLength = uint32(newFilterData.length); + s.status = SwarmStatus.REGISTERED; + filterData[swarmId] = newFilterData; + + emit SwarmFilterUpdated(swarmId, msg.sender, uint32(newFilterData.length)); + } + + /// @notice Reassigns the service provider. Resets status to REGISTERED. Caller must own the fleet UUID. + /// @param swarmId The swarm to update. + /// @param newProviderId New provider token ID. + function updateSwarmProvider(uint256 swarmId, uint256 newProviderId) external { + Swarm storage s = swarms[swarmId]; + if (s.filterLength == 0) { + revert SwarmNotFound(); + } + if (FLEET_CONTRACT.uuidOwner(s.fleetUuid) != msg.sender) { + revert NotUuidOwner(); + } + if (PROVIDER_CONTRACT.ownerOf(newProviderId) == address(0)) { + revert ProviderDoesNotExist(); + } + + uint256 oldProvider = s.providerId; + + // Effects — update provider and reset status + s.providerId = newProviderId; + s.status = SwarmStatus.REGISTERED; + + emit SwarmProviderUpdated(swarmId, oldProvider, newProviderId); + } + + /// @notice Permanently deletes a swarm. Caller must own the fleet UUID. + /// @param swarmId The swarm to delete. + function deleteSwarm(uint256 swarmId) external { + Swarm storage s = swarms[swarmId]; + if (s.filterLength == 0) { + revert SwarmNotFound(); + } + if (FLEET_CONTRACT.uuidOwner(s.fleetUuid) != msg.sender) { + revert NotUuidOwner(); + } + + bytes16 fleetUuid = s.fleetUuid; + + _removeFromUuidSwarms(fleetUuid, swarmId); + + delete swarms[swarmId]; + delete filterData[swarmId]; + + emit SwarmDeleted(swarmId, fleetUuid, msg.sender); + } + + /// @notice Returns whether the swarm's fleet UUID and provider NFT are still valid. + /// @param swarmId The swarm to check. + /// @return fleetValid True if the fleet UUID is still owned (uuidOwner != address(0)). + /// @return providerValid True if the provider NFT exists. + function isSwarmValid(uint256 swarmId) public view returns (bool fleetValid, bool providerValid) { + Swarm storage s = swarms[swarmId]; + if (s.filterLength == 0) revert SwarmNotFound(); + + // Fleet is valid if UUID is still owned (not released) + fleetValid = FLEET_CONTRACT.uuidOwner(s.fleetUuid) != address(0); + + try PROVIDER_CONTRACT.ownerOf(s.providerId) returns (address) { + providerValid = true; + } catch { + providerValid = false; + } + } + + /// @notice Permissionless-ly removes a swarm whose fleet UUID has been released or provider NFT has been burned. + /// @param swarmId The orphaned swarm to purge. + function purgeOrphanedSwarm(uint256 swarmId) external { + Swarm storage s = swarms[swarmId]; + if (s.filterLength == 0) revert SwarmNotFound(); + + (bool fleetValid, bool providerValid) = isSwarmValid(swarmId); + if (fleetValid && providerValid) revert SwarmNotOrphaned(); + + bytes16 fleetUuid = s.fleetUuid; + + _removeFromUuidSwarms(fleetUuid, swarmId); + + delete swarms[swarmId]; + delete filterData[swarmId]; + + emit SwarmPurged(swarmId, fleetUuid, msg.sender); + } + + /// @notice Tests tag membership against the swarm's XOR filter. + /// @param swarmId The swarm to query. + /// @param tagHash keccak256 of the tag identity bytes (caller must pre-normalize per tagType). + /// @return isValid True if the tag passes the XOR filter check. + function checkMembership(uint256 swarmId, bytes32 tagHash) external view returns (bool isValid) { + Swarm storage s = swarms[swarmId]; + if (s.filterLength == 0) { + revert SwarmNotFound(); + } + + // Reject queries against orphaned swarms + (bool fleetValid, bool providerValid) = isSwarmValid(swarmId); + if (!fleetValid || !providerValid) revert SwarmOrphaned(); + + bytes storage filter = filterData[swarmId]; + uint256 dataLen = s.filterLength; + + // Calculate M (number of fingerprint slots) + uint256 m = (dataLen * 8) / s.fingerprintSize; + if (m == 0) return false; + + // Derive 3 indices and expected fingerprint from hash + uint32 h1 = uint32(uint256(tagHash)) % uint32(m); + uint32 h2 = uint32(uint256(tagHash) >> 32) % uint32(m); + uint32 h3 = uint32(uint256(tagHash) >> 64) % uint32(m); + + uint256 fpMask = (uint256(1) << s.fingerprintSize) - 1; + uint256 expectedFp = (uint256(tagHash) >> 96) & fpMask; + + // Read and XOR fingerprints + uint256 f1 = _readFingerprint(filter, h1, s.fingerprintSize); + uint256 f2 = _readFingerprint(filter, h2, s.fingerprintSize); + uint256 f3 = _readFingerprint(filter, h3, s.fingerprintSize); + + return (f1 ^ f2 ^ f3) == expectedFp; + } + + /// @notice Returns the raw XOR filter bytes for a swarm. + /// @param swarmId The swarm to query. + /// @return filter The XOR filter blob. + function getFilterData(uint256 swarmId) external view returns (bytes memory filter) { + if (swarms[swarmId].filterLength == 0) { + revert SwarmNotFound(); + } + return filterData[swarmId]; + } + + /** + * @dev O(1) removal of a swarm from its UUID's swarm list using index tracking. + */ + function _removeFromUuidSwarms(bytes16 fleetUuid, uint256 swarmId) internal { + uint256[] storage arr = uuidSwarms[fleetUuid]; + uint256 index = swarmIndexInUuid[swarmId]; + uint256 lastId = arr[arr.length - 1]; + + arr[index] = lastId; + swarmIndexInUuid[lastId] = index; + arr.pop(); + delete swarmIndexInUuid[swarmId]; + } + + /** + * @dev Reads a packed fingerprint from storage bytes. + * @param filter The filter bytes in storage. + * @param index The fingerprint slot index. + * @param bits The fingerprint size in bits. + */ + function _readFingerprint(bytes storage filter, uint256 index, uint8 bits) internal view returns (uint256) { + uint256 bitOffset = index * bits; + uint256 startByte = bitOffset / 8; + uint256 endByte = (bitOffset + bits - 1) / 8; + + // Read bytes and assemble into uint256 + uint256 raw; + for (uint256 i = startByte; i <= endByte;) { + raw = (raw << 8) | uint8(filter[i]); + unchecked { + ++i; + } + } + + // Extract the fingerprint bits + uint256 totalBitsRead = (endByte - startByte + 1) * 8; + uint256 localStart = bitOffset % 8; + uint256 shiftRight = totalBitsRead - (localStart + bits); + + return (raw >> shiftRight) & ((uint256(1) << bits) - 1); + } +} diff --git a/src/swarms/doc/README.md b/src/swarms/doc/README.md new file mode 100644 index 00000000..1b56c3dc --- /dev/null +++ b/src/swarms/doc/README.md @@ -0,0 +1,164 @@ +# Swarm System Technical Specification + +BLE tag registry enabling decentralized device discovery using cryptographic membership proofs. Individual tags within a swarm are not enumerated on-chain. + +## Architecture + +```mermaid +graph TB + subgraph NFTs["Identity Layer (ERC-721)"] + FI["FleetIdentity
SFID
tokenId = (regionKey << 128) | uuid"] + SP["ServiceProvider
SSV
tokenId = keccak256(url)"] + end + + subgraph Registries["Registry Layer"] + REG["SwarmRegistry
L1: SSTORE2 filter storage
Universal: native bytes storage"] + end + + subgraph Actors + FO(("Fleet
Owner")) + PRV(("Service
Provider")) + ANY(("Client /
Purger")) + end + + FO -- "registerFleet* / claimUuid" --> FI + FO -- "registerSwarm / update / delete" --> REG + PRV -- "registerProvider(url)" --> SP + PRV -- "acceptSwarm / rejectSwarm" --> REG + ANY -- "buildHighestBondedUuidBundle /
checkMembership / purge" --> REG + + REG -. "uuidOwner(fleetUuid)" .-> FI + REG -. "ownerOf(providerId)" .-> SP + + style FI fill:#4a9eff,color:#fff + style SP fill:#4a9eff,color:#fff + style REG fill:#ff9f43,color:#fff + style FO fill:#2ecc71,color:#fff + style PRV fill:#2ecc71,color:#fff + style ANY fill:#95a5a6,color:#fff +``` + +## Core Components + +| Contract | Role | Identity | Token | +| :------------------------- | :----------------------------- | :----------------------------------------- | :---- | +| **FleetIdentity** | Fleet registry (ERC-721) | `(regionKey << 128) \| uuid` | SFID | +| **ServiceProvider** | Backend URL registry (ERC-721) | `keccak256(url)` | SSV | +| **SwarmRegistryL1** | Tag group registry (L1) | `keccak256(fleetUuid, providerId, filter)` | — | +| **SwarmRegistryUniversal** | Tag group registry (ZkSync+) | `keccak256(fleetUuid, providerId, filter)` | — | + +All contracts are **permissionless**—access control via NFT ownership. FleetIdentity requires ERC-20 bond (anti-spam). + +## Key Concepts + +### Swarm + +A group of ~10k-20k BLE tags represented by an XOR filter. Tags are never enumerated on-chain; membership is verified via cryptographic filter. + +### UUID Ownership + +UUIDs (iBeacon Proximity UUID) have ownership levels: + +| Level | Region Key | Bond | Description | +| :------ | :--------- | :---------------------- | :-------------------------- | +| Owned | 0 | BASE_BOND | Reserved, not in any region | +| Local | ≥1024 | BASE_BOND × 2^tier | Registered in admin area | +| Country | 1-999 | BASE_BOND × 16 × 2^tier | Registered at country level | + +### Geographic Tiers + +Each region has independent tier competition: + +- **Tier capacity**: 10 members per tier +- **Max tiers**: 24 per region +- **Bundle size**: Up to 20 UUIDs returned to clients + +Country fleets pay 16× more but appear in all admin-area bundles within their country. + +### Operator Delegation + +UUID owners can delegate tier maintenance to an **operator**: + +- **Default**: `operatorOf(uuid)` returns the UUID owner +- **Delegation**: Owner calls `claimUuid(uuid, operator)` or `setOperator(uuid, operator)` +- **Registration**: Only operator can register owned UUIDs to regions +- **Bond Split**: Owner pays BASE_BOND once; operator pays all tier bonds +- **Permissions**: Only operator can promote/demote/register; owner retains burn rights +- **Transfer**: `setOperator` transfers total tier bonds atomically (O(1) via `uuidTotalTierBonds`) + +This enables cold-wallet ownership with hot-wallet tier management. + +### Token ID Encoding + +``` +tokenId = (regionKey << 128) | uint256(uint128(uuid)) +``` + +- Bits 0-127: UUID +- Bits 128-159: Region key + +## Privacy Model + +The system provides **non-enumerating** tag verification—individual tags aren't listed on-chain; membership is proven via XOR filter. + +| Data | Visibility | Notes | +| :---------- | :--------------- | :------------------------------------------- | +| UUID | Public | Required for iOS background beacon detection | +| Major/Minor | Filter-protected | Hashed, not enumerated | +| MAC address | Android-only | iOS does not expose BLE MAC addresses | + +**Limitation**: UUID must be public for iOS `CLBeaconRegion` background monitoring. The system protects the specific Major/Minor combinations within that UUID's swarm. + +## Documentation + +| Document | Description | +| :--------------------------------------------- | :------------------------------------------------ | +| [data-model.md](data-model.md) | Contract interfaces, enums, storage layout | +| [fleet-registration.md](fleet-registration.md) | Fleet & UUID registration, tier economics | +| [swarm-operations.md](swarm-operations.md) | Swarm registration, filters, provider approval | +| [lifecycle.md](lifecycle.md) | State machines, updates, deletion, orphan cleanup | +| [discovery.md](discovery.md) | Client discovery flows, tag hash construction | +| [maintenance.md](maintenance.md) | Bundle inclusion monitoring, tier optimization | +| [iso3166-reference.md](iso3166-reference.md) | ISO 3166-1/2 codes and admin area mappings | + +## End-to-End Flow + +```mermaid +sequenceDiagram + participant FO as Fleet Owner + participant PO as Provider Owner + participant FI as FleetIdentity + participant SR as SwarmRegistry + participant SP as ServiceProvider + participant Client as EdgeBeaconScanner + + Note over FO: 1. Register fleet + FO->>FI: registerFleetLocal(uuid, cc, admin, tier) + + Note over FO: 2. Register provider + PO->>SP: registerProvider(url) + + Note over FO: 3. Register swarm + FO->>SR: registerSwarm(uuid, providerId, filter, ...) + + Note over FO: 4. Provider approves + PO->>SR: acceptSwarm(swarmId) + + Note over Client: 5. Client discovers + Client->>FI: buildHighestBondedUuidBundle(cc, admin) + Client->>SR: uuidSwarms(uuid, 0) + Client->>SR: checkMembership(swarmId, tagHash) + Client->>SP: providerUrls(providerId) + Note over Client: Connect to service URL +``` + +## Storage Variants + +| Variant | Chain | Filter Storage | Deletion Behavior | +| :------------------------- | :------------------ | :-------------------------- | :-------------------------------- | +| **SwarmRegistryL1** | Ethereum L1 | SSTORE2 (contract bytecode) | Struct cleared; bytecode persists | +| **SwarmRegistryUniversal** | ZkSync Era, all EVM | `mapping(uint256 => bytes)` | Full deletion, gas refund | + +--- + +_For implementation details, see individual documentation pages._ diff --git a/src/swarms/doc/assistant-guide.md b/src/swarms/doc/assistant-guide.md new file mode 100644 index 00000000..070602d6 --- /dev/null +++ b/src/swarms/doc/assistant-guide.md @@ -0,0 +1,423 @@ +# Swarm System Architecture & Implementation Guide + +> **Context for AI Agents**: This document outlines the architecture, constraints, and operational logic of the Swarm Smart Contract system. Use this context when modifying contracts, writing SDKs, or debugging verifiers. + +## 1. System Overview + +The Swarm System is a **non-enumerating** registry for **BLE (Bluetooth Low Energy)** tag swarms. It allows Fleet Owners to manage large sets of tags (~10k-20k) and link them to Service Providers (Backend URLs) using cryptographic membership proofs—individual tags are never listed on-chain. + +Two registry variants exist for different deployment targets: + +- **`SwarmRegistryL1`** — Ethereum L1, uses SSTORE2 (contract bytecode) for gas-efficient filter storage. Not compatible with ZkSync Era. +- **`SwarmRegistryUniversal`** — All EVM chains including ZkSync Era, uses native `bytes` storage. + +### Core Components + +| Contract | Role | Key Identity | Token | +| :--------------------------- | :---------------------------------- | :----------------------------------------- | :---- | +| **`FleetIdentity`** | Fleet Registry (ERC-721 Enumerable) | `(regionKey << 128) \| uint128(uuid)` | SFID | +| **`ServiceProvider`** | Service Registry (ERC-721) | `keccak256(url)` | SSV | +| **`SwarmRegistryL1`** | Swarm Registry (L1) | `keccak256(fleetUuid, providerId, filter)` | — | +| **`SwarmRegistryUniversal`** | Swarm Registry (Universal) | `keccak256(fleetUuid, providerId, filter)` | — | + +All contracts are **permissionless** — access control is enforced through NFT ownership rather than admin roles. `FleetIdentity` additionally requires an ERC-20 bond (e.g. NODL) to register a fleet, acting as an anti-spam / anti-abuse mechanism. + +Both NFT contracts support **burning**. For `FleetIdentity`, owned-only tokens can be burned by the owner (refunds BASE*BOND), while registered tokens can only be burned by the operator (refunds tier bond). Burning a `ServiceProvider` token requires owner rights. Burning either NFT makes any swarms referencing that token \_orphaned*. + +### FleetIdentity: Two-Level Geographic Registration + +`FleetIdentity` implements a **two-level geographic registration** system: + +- **Country Level** — `regionKey = countryCode` (ISO 3166-1 numeric, 1-999) +- **Admin Area (Local) Level** — `regionKey = (countryCode << 10) | adminCode` (>= 1024) + +Each region has its own independent tier namespace. The first fleet in any region always pays the level-appropriate base bond. + +**TokenID Encoding:** + +``` +tokenId = (regionKey << 128) | uint256(uint128(uuid)) +``` + +- Bits 0-127: UUID (Proximity UUID as bytes16) +- Bits 128-159: Region key (country or admin-area code) + +This allows the same UUID to be registered in multiple regions, each with a distinct token. + +### Economic Model (Tier System) + +| Parameter | Value | +| :------------------ | :--------------------------------------------------------- | +| **Tier Capacity** | 4 members per tier | +| **Max Tiers** | 24 per region | +| **Local Bond** | `BASE_BOND * 2^tier` | +| **Country Bond** | `BASE_BOND * COUNTRY_BOND_MULTIPLIER * 2^tier` (16× local) | +| **Max Bundle Size** | 20 UUIDs | + +Country fleets pay 16× more but appear in all admin-area bundles within their country. This economic difference provides locals a significant advantage: a local can reach tier 3 for the same cost a country player pays for tier 0. + +### UUID Ownership Model + +UUIDs have an ownership model with registration levels: + +| Level | Value | Description | +| :-------- | :---- | :--------------------------------------- | +| `None` | 0 | Not registered (default) | +| `Owned` | 1 | Claimed but not registered in any region | +| `Local` | 2 | Registered at admin area level | +| `Country` | 3 | Registered at country level | + +- **UUID Owner**: The address that first registered a token for a UUID. All subsequent registrations must come from this address. +- **Multi-Region**: The same UUID can have multiple tokens in different regions (all at the same level, all by the same owner). +- **Transfer**: Owned-only tokens transfer `uuidOwner` when the NFT is transferred. + +--- + +## 2. Operational Workflows + +### A. Provider Setup (One-Time) + +**Service Provider** calls `ServiceProvider.registerProvider("https://cms.example.com")`. Receives `providerTokenId` (= `keccak256(url)`). + +### B. Fleet Registration Options + +Fleet Owners have multiple paths to register fleets: + +#### B1. Direct Registration (Country Level) + +```solidity +// 1. Approve bond token +NODL.approve(fleetIdentityAddress, requiredBond); + +// 2. Get inclusion hint (off-chain call - free) +(uint256 tier, uint256 bond) = fleetIdentity.countryInclusionHint(840); // US = 840 + +// 3. Register at the recommended tier +uint256 tokenId = fleetIdentity.registerFleetCountry(uuid, 840, tier); +// Returns tokenId = (840 << 128) | uint128(uuid) +``` + +#### B2. Direct Registration (Local/Admin Area Level) + +```solidity +// 1. Approve bond token +NODL.approve(fleetIdentityAddress, requiredBond); + +// 2. Get inclusion hint (off-chain call - free) +(uint256 tier, uint256 bond) = fleetIdentity.localInclusionHint(840, 5); // US, California + +// 3. Register at the recommended tier +uint256 tokenId = fleetIdentity.registerFleetLocal(uuid, 840, 5, tier); +// Returns tokenId = ((840 << 10 | 5) << 128) | uint128(uuid) +``` + +#### B3. Claim-First Flow (Reserve UUID, Register Later) + +```solidity +// 1. Claim UUID ownership (costs BASE_BOND) +NODL.approve(fleetIdentityAddress, BASE_BOND); +uint256 ownedTokenId = fleetIdentity.claimUuid(uuid); +// Returns tokenId = uint128(uuid) (regionKey = 0) + +// 2. Later: Register from owned state (burns owned token, mints regional token) +// Only pays incremental bond (tier bond - BASE_BOND already paid) +uint256 tokenId = fleetIdentity.registerFleetLocal(uuid, 840, 5, targetTier); +``` + +### C. Fleet Tier Management + +Fleets can promote or demote within their region: + +```solidity +// Promote to next tier (pulls additional bond) +fleetIdentity.promote(tokenId); + +// Reassign to any tier (promotes or demotes) +fleetIdentity.reassignTier(tokenId, targetTier); +// If targetTier > current: pulls additional bond +// If targetTier < current: refunds bond difference +``` + +### D. Operator Delegation + +UUID owners can delegate tier management to an operator wallet: + +```solidity +// Set operator at registration time (owner pays BASE_BOND, operator pays tier excess) +fleetIdentity.registerFleetLocalWithOperator(uuid, 840, 5, tier, operatorAddress); + +// Or set operator after registration (transfers tier bonds atomically) +fleetIdentity.setOperator(uuid, operatorAddress); + +// Check current operator (returns owner if none set) +address manager = fleetIdentity.operatorOf(uuid); + +// Clear operator (reverts to owner-managed) +fleetIdentity.setOperator(uuid, address(0)); +``` + +**Key Points:** + +- Operator handles `promote()`, `reassignTier()`, and `burn()` calls for registered tokens +- Owner retains `setOperator()` control and `burn()` rights for owned-only tokens +- Tier excess bonds transfer between operators when changing +- Cannot set operator for owned-only UUIDs (must be registered) + +### E. Burn Fleet Token + +**Owned-Only Tokens (Owner Burns):** + +```solidity +// Only owner can burn owned-only tokens +fleetIdentity.burn(ownedTokenId); +// Refunds BASE_BOND to owner, clears UUID ownership +``` + +**Registered Tokens (Operator Burns):** + +```solidity +// Only operator can burn registered tokens +fleetIdentity.burn(tokenId); +// Refunds tier bond to operator +// If last token: mints owned-only token to owner (preserves ownership) +// Owner must burn owned-only token separately to fully release UUID +``` + +### F. Swarm Registration (Per Batch of Tags) + +A Fleet Owner groups tags into a "Swarm" (chunk of ~10k-20k tags) and registers them. + +1. **Construct `TagID`s**: Generate the unique ID for every tag in the swarm (see "Tag Schemas" below). +2. **Build XOR Filter**: Create a binary XOR filter (Peeling Algorithm) containing the hashes of all `TagID`s. +3. **(Optional) Predict Swarm ID**: Call `computeSwarmId(fleetUuid, providerId, filterData)` off-chain to obtain the deterministic ID before submitting the transaction. +4. **Register**: + ```solidity + swarmRegistry.registerSwarm( + fleetUuid, + providerId, + filterData, + 16, // Fingerprint size in bits (1–16) + TagType.IBEACON_INCLUDES_MAC // or PAYLOAD_ONLY, VENDOR_ID, GENERIC + ); + // Returns the deterministic swarmId + ``` + +### G. Swarm Approval Flow + +After registration a swarm starts in `REGISTERED` status and requires provider approval: + +1. **Provider approves**: `swarmRegistry.acceptSwarm(swarmId)` → status becomes `ACCEPTED`. +2. **Provider rejects**: `swarmRegistry.rejectSwarm(swarmId)` → status becomes `REJECTED`. + +Only the owner of the provider NFT (`providerId`) can accept or reject. + +### H. Swarm Updates + +The fleet owner can modify a swarm at any time. Both operations reset status to `REGISTERED`, requiring fresh provider approval: + +- **Replace the XOR filter**: `swarmRegistry.updateSwarmFilter(swarmId, newFilterData)` +- **Change service provider**: `swarmRegistry.updateSwarmProvider(swarmId, newProviderId)` + +### I. Swarm Deletion + +The fleet owner can permanently remove a swarm: + +```solidity +swarmRegistry.deleteSwarm(swarmId); +``` + +### J. Orphan Detection & Cleanup + +When a fleet or provider NFT is burned, swarms referencing it become _orphaned_: + +- **Check validity**: `swarmRegistry.isSwarmValid(swarmId)` returns `(fleetValid, providerValid)`. +- **Purge**: Anyone can call `swarmRegistry.purgeOrphanedSwarm(swarmId)` to remove stale state. The caller receives the SSTORE gas refund as an incentive. +- **Guards**: `acceptSwarm`, `rejectSwarm`, and `checkMembership` all revert with `SwarmOrphaned()` if the swarm's NFTs have been burned. + +--- + +## 3. Off-Chain Logic: Filter & Tag Construction + +### Tag Schemas (`TagType`) + +The system supports different ways of constructing the unique `TagID` based on the hardware capabilities. + +**Enum: `TagType`** + +- **`0x00`: IBEACON_PAYLOAD_ONLY** + - **Format**: `UUID (16b) || Major (2b) || Minor (2b)` + - **Use Case**: When Major/Minor pairs are globally unique (standard iBeacon). +- **`0x01`: IBEACON_INCLUDES_MAC** + - **Format**: `UUID (16b) || Major (2b) || Minor (2b) || MAC (6b)` + - **Use Case**: Anti-spoofing logic or Shared Major/Minor fleets. + - **CRITICAL: MAC Normalization Rule**: + - If MAC is **Public/Static** (Address Type bits `00`): Use the **Real MAC Address**. + - If MAC is **Random/Private** (Address Type bits `01` or `11`): Replace with `FF:FF:FF:FF:FF:FF`. + - _Why?_ To support rotating privacy MACs while still validating "It's a privacy tag". +- **`0x02`: VENDOR_ID** + - **Format**: `companyID || hash(vendorBytes)` + - **Use Case**: Non-iBeacon BLE devices identified by Bluetooth SIG company ID. +- **`0x03`: GENERIC** + - **Use Case**: Catch-all for custom tag identity schemes. + +### Filter Construction (The Math) + +To verify membership on-chain, the contract uses **3-hash XOR logic**. + +1. **Input**: `h = keccak256(TagID)` (where TagID is constructed via schema above). +2. **Indices** (M = number of fingerprint slots = `filterLength * 8 / fingerprintSize`): + - `h1 = uint32(h) % M` + - `h2 = uint32(h >> 32) % M` + - `h3 = uint32(h >> 64) % M` +3. **Fingerprint**: `fp = (h >> 96) & ((1 << fingerprintSize) - 1)` +4. **Verification**: `Filter[h1] ^ Filter[h2] ^ Filter[h3] == fp` + +### Swarm ID Derivation + +Swarm IDs are **deterministic** — derived from the swarm's core identity: + +``` +swarmId = uint256(keccak256(abi.encode(fleetUuid, providerId, filterData))) +``` + +This means the same (UUID, provider, filter) triple always produces the same ID, and duplicate registrations revert with `SwarmAlreadyExists()`. The `computeSwarmId` function is `public pure`, so it can be called off-chain at zero cost via `eth_call`. + +--- + +## 4. Client Discovery Flow (The "EdgeBeaconScanner" Perspective) + +A client (mobile phone or gateway) scans a BLE beacon and wants to find its owner and backend service. + +### Discovery Option A: Geographic Bundle Discovery (Recommended) + +Use the priority-ordered bundle based on EdgeBeaconScanner location. + +#### Step 1: Get Priority Bundle + +```solidity +// EdgeBeaconScanner knows its location: US, California (country=840, admin=5) +(bytes16[] memory uuids, uint256 count) = fleetIdentity.buildHighestBondedUuidBundle(840, 5); +// Returns up to 20 UUIDs, priority-ordered: +// 1. Higher tier first +// 2. Local (admin area) before country within same tier +// 3. Earlier registration within same tier+level +``` + +#### Step 2: Match Detected Beacon UUID + +```solidity +bytes16 detectedUUID = ...; // From iBeacon advertisement + +for (uint256 i = 0; i < count; i++) { + if (uuids[i] == detectedUUID) { + // Found! Now find the token ID + // Try local region first, then country + uint32 localRegion = (840 << 10) | 5; + uint256 tokenId = fleetIdentity.computeTokenId(detectedUUID, localRegion); + if (fleetIdentity.ownerOf(tokenId) exists) { ... } + // else try country region + uint256 tokenId = fleetIdentity.computeTokenId(detectedUUID, 840); + } +} +``` + +#### Step 3: Enumerate Swarms & Check Membership + +Same as Option B Steps 3-5. + +### Discovery Option B: Direct Fleet Lookup + +For when you know the UUID and want to find its fleet directly. + +#### Step 1: Enumerate Active Regions + +```solidity +// Get all countries with active fleets +uint16[] memory countries = fleetIdentity.getActiveCountries(); + +// Get all admin areas with active fleets +uint32[] memory adminAreas = fleetIdentity.getActiveAdminAreas(); +``` + +#### Step 2: Find Fleet Token + +```solidity +bytes16 uuid = ...; // From iBeacon + +// Try each potential region (start with user's location) +uint32 region = (840 << 10) | 5; // US-CA +uint256 tokenId = fleetIdentity.computeTokenId(uuid, region); + +try fleetIdentity.ownerOf(tokenId) returns (address owner) { + // Found the fleet! +} catch { + // Try country-level + tokenId = fleetIdentity.computeTokenId(uuid, 840); +} +``` + +#### Step 3: Find Swarms + +```solidity +// Enumerate swarms for this UUID +uint256[] memory swarmIds = new uint256[](100); // estimate +for (uint256 i = 0; ; i++) { + try swarmRegistry.uuidSwarms(detectedUUID, i) returns (uint256 swarmId) { + swarmIds[i] = swarmId; + } catch { + break; // End of array + } +} +``` + +#### Step 4: Membership Check + +```solidity +// Construct tagHash based on swarm's tagType +(bytes16 fleetUuid, uint256 providerId, uint32 filterLen, uint8 fpSize, + SwarmStatus status, TagType tagType) = swarmRegistry.swarms(swarmId); + +// Build tagId per schema (see Section 3) +bytes memory tagId; +if (tagType == TagType.IBEACON_PAYLOAD_ONLY) { + tagId = abi.encodePacked(uuid, major, minor); +} else if (tagType == TagType.IBEACON_INCLUDES_MAC) { + bytes6 normalizedMac = isRandomMac ? bytes6(0xFFFFFFFFFFFF) : realMac; + tagId = abi.encodePacked(uuid, major, minor, normalizedMac); +} + +bytes32 tagHash = keccak256(tagId); +bool isMember = swarmRegistry.checkMembership(swarmId, tagHash); +``` + +#### Step 5: Service Discovery + +```solidity +if (isMember && status == SwarmStatus.ACCEPTED) { + string memory url = serviceProvider.providerUrls(providerId); + // Connect to url +} +``` + +--- + +## 5. Storage & Deletion Notes + +### SwarmRegistryL1 (SSTORE2) + +- Filter data is stored as **immutable contract bytecode** via SSTORE2. +- On `deleteSwarm` / `purgeOrphanedSwarm`, the struct is cleared but the deployed bytecode **cannot be erased** (accepted trade-off of the SSTORE2 pattern). + +### SwarmRegistryUniversal (native bytes) + +- Filter data is stored in a `mapping(uint256 => bytes)`. +- On `deleteSwarm` / `purgeOrphanedSwarm`, both the struct and the filter bytes are fully deleted (`delete filterData[swarmId]`), reclaiming storage. +- Exposes `getFilterData(swarmId)` for off-chain filter retrieval. + +### Deletion Performance + +Both registries use an **O(1) swap-and-pop** strategy for removing swarms from the `uuidSwarms` array, tracked via the `swarmIndexInUuid` mapping. + +--- + +**Note**: This architecture ensures that an EdgeBeaconScanner can go from **Raw Signal** → **Verified Service URL** entirely on-chain (data-wise), without a centralized indexer, while privacy of the 10,000 other tags in the swarm is preserved. diff --git a/src/swarms/doc/data-model.md b/src/swarms/doc/data-model.md new file mode 100644 index 00000000..5fd583b8 --- /dev/null +++ b/src/swarms/doc/data-model.md @@ -0,0 +1,173 @@ +# Data Model & Contract Interfaces + +## Contract Classes + +```mermaid +classDiagram + class FleetIdentity { + +IERC20 BOND_TOKEN + +uint256 BASE_BOND + +uint256 TIER_CAPACITY = 10 + +uint256 MAX_TIERS = 24 + +uint256 COUNTRY_BOND_MULTIPLIER = 16 + +uint256 MAX_BONDED_UUID_BUNDLE_SIZE = 20 + +mapping uuidOwner : bytes16 → address + +mapping uuidOperator : bytes16 → address + +mapping uuidLevel : bytes16 → RegistrationLevel + +mapping uuidTokenCount : bytes16 → uint256 + +mapping uuidTotalTierBonds : bytes16 → uint256 + +mapping regionTierCount : uint32 → uint256 + +mapping fleetTier : uint256 → uint256 + -- + +claimUuid(uuid, operator) → tokenId + +registerFleetLocal(uuid, cc, admin, tier) → tokenId + +registerFleetCountry(uuid, cc, tier) → tokenId + +promote(tokenId) + +reassignTier(tokenId, targetTier) + +burn(tokenId) + +setOperator(uuid, newOperator) + +operatorOf(uuid) → address + -- + +localInclusionHint(cc, admin) → tier, bond + +countryInclusionHint(cc) → tier, bond + +buildHighestBondedUuidBundle(cc, admin) → uuids[], count + +buildCountryOnlyBundle(cc) → uuids[], count + +getActiveCountries() → uint16[] + +getActiveAdminAreas() → uint32[] + +tokenUuid(tokenId) → bytes16 + +tokenRegion(tokenId) → uint32 + +computeTokenId(uuid, region) → uint256 + +tierBond(tier, isCountry) → uint256 + } + + class ServiceProvider { + +mapping providerUrls : uint256 → string + -- + +registerProvider(url) → tokenId + +burn(tokenId) + } + + class SwarmRegistry { + +mapping swarms : uint256 → Swarm + +mapping uuidSwarms : bytes16 → uint256[] + +mapping swarmIndexInUuid : uint256 → uint256 + -- + +computeSwarmId(fleetUuid, providerId, filter) → swarmId + +registerSwarm(fleetUuid, providerId, filter, fpSize, tagType) → swarmId + +acceptSwarm(swarmId) + +rejectSwarm(swarmId) + +updateSwarmFilter(swarmId, newFilter) + +updateSwarmProvider(swarmId, newProviderId) + +deleteSwarm(swarmId) + +isSwarmValid(swarmId) → fleetValid, providerValid + +purgeOrphanedSwarm(swarmId) + +checkMembership(swarmId, tagHash) → bool + } +``` + +## Struct: Swarm + +```solidity +struct Swarm { + bytes16 fleetUuid; // UUID that owns this swarm + uint256 providerId; // ServiceProvider token ID + uint32 filterLength; // XOR filter byte length + uint8 fingerprintSize; // Fingerprint bits (1-16) + SwarmStatus status; // Registration state + TagType tagType; // Tag identity scheme +} +``` + +## Enumerations + +### SwarmStatus + +| Value | Description | +| :----------- | :------------------------- | +| `REGISTERED` | Awaiting provider approval | +| `ACCEPTED` | Provider approved; active | +| `REJECTED` | Provider rejected | + +### TagType + +| Value | Format | Use Case | +| :--------------------- | :------------------------------- | :--------------- | +| `IBEACON_PAYLOAD_ONLY` | UUID ∥ Major ∥ Minor (20B) | Standard iBeacon | +| `IBEACON_INCLUDES_MAC` | UUID ∥ Major ∥ Minor ∥ MAC (26B) | Anti-spoofing | +| `VENDOR_ID` | companyID ∥ hash(vendorBytes) | Non-iBeacon BLE | +| `GENERIC` | Custom | Extensible | + +### RegistrationLevel + +| Value | Region Key | Description | +| :------------ | :--------- | :----------------- | +| `None` (0) | — | Not registered | +| `Owned` (1) | 0 | Claimed, no region | +| `Local` (2) | ≥1024 | Admin area | +| `Country` (3) | 1-999 | Country-wide | + +## Region Key Encoding + +``` +Country: regionKey = countryCode (1-999) +Admin Area: regionKey = (countryCode << 10) | adminCode (≥1024) +``` + +**Token ID:** + +``` +tokenId = (regionKey << 128) | uint256(uint128(uuid)) +``` + +**Helper functions:** + +```solidity +bytes16 uuid = fleetIdentity.tokenUuid(tokenId); +uint32 region = fleetIdentity.tokenRegion(tokenId); +uint256 tokenId = fleetIdentity.computeTokenId(uuid, regionKey); +uint32 adminRegion = fleetIdentity.makeAdminRegion(countryCode, adminCode); +``` + +## Swarm ID Derivation + +Deterministic and collision-free: + +```solidity +swarmId = uint256(keccak256(abi.encode(fleetUuid, providerId, filterData))) +``` + +Duplicate registration reverts with `SwarmAlreadyExists()`. + +## XOR Filter Membership + +3-hash XOR verification: + +``` +Input: h = keccak256(tagId) +M = filterLength * 8 / fingerprintSize // slots + +h1 = uint32(h) % M +h2 = uint32(h >> 32) % M +h3 = uint32(h >> 64) % M +fp = (h >> 96) & ((1 << fingerprintSize) - 1) + +Valid if: Filter[h1] ^ Filter[h2] ^ Filter[h3] == fp +``` + +## Storage Notes + +### SwarmRegistryL1 + +- Filter stored as **contract bytecode** via SSTORE2 +- Gas-efficient reads (EXTCODECOPY) +- Bytecode persists after deletion (immutable) + +### SwarmRegistryUniversal + +- Filter stored in `mapping(uint256 => bytes)` +- Full deletion reclaims storage +- `getFilterData(swarmId)` for off-chain retrieval + +### Deletion Performance + +O(1) swap-and-pop via `swarmIndexInUuid` mapping. diff --git a/src/swarms/doc/discovery.md b/src/swarms/doc/discovery.md new file mode 100644 index 00000000..a71d0ef4 --- /dev/null +++ b/src/swarms/doc/discovery.md @@ -0,0 +1,169 @@ +# Client Discovery + +## Overview + +Clients (mobile apps, gateways) discover BLE tags and resolve them to backend services entirely on-chain. + +``` +BLE Signal → UUID Match → Swarm Lookup → Membership Check → Service URL +``` + +## Geographic Bundle Discovery (Recommended) + +Use location-based priority bundles for efficient discovery. + +```mermaid +sequenceDiagram + actor Client as EdgeBeaconScanner + participant FI as FleetIdentity + participant SR as SwarmRegistry + participant SP as ServiceProvider + + Note over Client: Location: US-California (840, 5)
Detected: UUID, Major, Minor, MAC + + Client->>+FI: buildHighestBondedUuidBundle(840, 5) + FI-->>-Client: (uuids[], count) — up to 20 UUIDs + + Note over Client: Check if detectedUUID in bundle + + Client->>+SR: uuidSwarms(uuid, 0) + SR-->>-Client: swarmId + Note over Client: Iterate until revert + + Note over Client: Build tagHash per TagType + Client->>+SR: checkMembership(swarmId, tagHash) + SR-->>-Client: true + + Client->>+SR: swarms(swarmId) + SR-->>-Client: {providerId, status: ACCEPTED, ...} + + Client->>+SP: providerUrls(providerId) + SP-->>-Client: "https://api.example.com" + + Note over Client: Connect to service +``` + +### Bundle Priority + +1. **Tier**: Higher tier first +2. **Level**: Local before country (same tier) +3. **Time**: Earlier registration (same tier+level) + +## Direct UUID Lookup + +When UUID is known but location isn't: + +```solidity +// Try regions +uint32 localRegion = (840 << 10) | 5; +uint256 tokenId = fleetIdentity.computeTokenId(uuid, localRegion); +try fleetIdentity.ownerOf(tokenId) { /* found */ } +catch { /* try country: computeTokenId(uuid, 840) */ } + +// Enumerate swarms +for (uint i = 0; ; i++) { + try swarmRegistry.uuidSwarms(uuid, i) returns (uint256 swarmId) { + // process swarmId + } catch { break; } +} +``` + +## Tag Hash Construction + +```mermaid +flowchart TD + A[Read swarm.tagType] --> B{TagType?} + + B -->|IBEACON_PAYLOAD_ONLY| C["UUID ∥ Major ∥ Minor (20B)"] + B -->|IBEACON_INCLUDES_MAC| D{MAC type?} + B -->|VENDOR_ID| E["companyID ∥ hash(vendorBytes)"] + B -->|GENERIC| F["custom scheme"] + + D -->|Public| G["UUID ∥ Major ∥ Minor ∥ realMAC (26B)"] + D -->|Random| H["UUID ∥ Major ∥ Minor ∥ FF:FF:FF:FF:FF:FF"] + + C --> I["tagHash = keccak256(tagId)"] + G --> I + H --> I + E --> I + F --> I + + I --> J["checkMembership(swarmId, tagHash)"] + + style I fill:#4a9eff,color:#fff + style J fill:#2ecc71,color:#fff +``` + +### MAC Address Types + +| Address Type Bits | MAC Type | Action | +| :---------------- | :------------- | :---------------------- | +| `00` | Public | Use real MAC | +| `01`, `11` | Random/Private | Use `FF:FF:FF:FF:FF:FF` | + +## Region Enumeration (Indexers) + +```solidity +// Active countries +uint16[] memory countries = fleetIdentity.getActiveCountries(); +// [840, 276, 392, ...] + +// Active admin areas +uint32[] memory adminAreas = fleetIdentity.getActiveAdminAreas(); +// [860165, 282629, ...] → (cc << 10) | admin + +// Tier data +uint256 tierCount = fleetIdentity.regionTierCount(regionKey); +uint256[] memory tokenIds = fleetIdentity.getTierMembers(regionKey, tier); +bytes16[] memory uuids = fleetIdentity.getTierUuids(regionKey, tier); +``` + +## Complete Discovery Example + +```solidity +function discoverService( + bytes16 uuid, + uint16 major, + uint16 minor, + bytes6 mac, + uint16 countryCode, + uint8 adminCode +) external view returns (string memory serviceUrl, bool found) { + // 1. Check bundle + (bytes16[] memory uuids, uint256 count) = + fleetIdentity.buildHighestBondedUuidBundle(countryCode, adminCode); + + for (uint i = 0; i < count; i++) { + if (uuids[i] != uuid) continue; + + // 2. Find swarms + for (uint j = 0; ; j++) { + uint256 swarmId; + try swarmRegistry.uuidSwarms(uuid, j) returns (uint256 id) { + swarmId = id; + } catch { break; } + + // 3. Get swarm data + (,uint256 providerId,,,SwarmStatus status, TagType tagType) = + swarmRegistry.swarms(swarmId); + + if (status != SwarmStatus.ACCEPTED) continue; + + // 4. Build tagId + bytes memory tagId; + if (tagType == TagType.IBEACON_PAYLOAD_ONLY) { + tagId = abi.encodePacked(uuid, major, minor); + } else if (tagType == TagType.IBEACON_INCLUDES_MAC) { + tagId = abi.encodePacked(uuid, major, minor, mac); + } + + // 5. Check membership + if (swarmRegistry.checkMembership(swarmId, keccak256(tagId))) { + return (serviceProvider.providerUrls(providerId), true); + } + } + } + + return ("", false); +} +``` diff --git a/src/swarms/doc/fleet-registration.md b/src/swarms/doc/fleet-registration.md new file mode 100644 index 00000000..b6536883 --- /dev/null +++ b/src/swarms/doc/fleet-registration.md @@ -0,0 +1,280 @@ +# Fleet Registration + +## Registration Paths + +```mermaid +stateDiagram-v2 + [*] --> None : (default) + + None --> Owned : claimUuid() + None --> Local : registerFleetLocal() + None --> Country : registerFleetCountry() + + Owned --> Local : registerFleetLocal() + Owned --> Country : registerFleetCountry() + Owned --> [*] : burn() [owner] + + Local --> Owned : burn() [operator, last token] + Local --> Local : burn() [operator, not last] + + Country --> Owned : burn() [operator, last token] + Country --> Country : burn() [operator, not last] +``` + +## Direct Registration + +### Local (Admin Area) + +```solidity +// 1. Approve bond +NODL.approve(fleetIdentityAddress, requiredBond); + +// 2. Get recommended tier (free off-chain call) +(uint256 tier, uint256 bond) = fleetIdentity.localInclusionHint(840, 5); + +// 3. Register +uint256 tokenId = fleetIdentity.registerFleetLocal(uuid, 840, 5, tier); +// tokenId = ((840 << 10 | 5) << 128) | uint128(uuid) +``` + +### Country + +```solidity +// 1. Approve bond +NODL.approve(fleetIdentityAddress, requiredBond); + +// 2. Get recommended tier +(uint256 tier, uint256 bond) = fleetIdentity.countryInclusionHint(840); + +// 3. Register +uint256 tokenId = fleetIdentity.registerFleetCountry(uuid, 840, tier); +// tokenId = (840 << 128) | uint128(uuid) +``` + +## Claim-First Flow (with Operator Delegation) + +Reserve UUID with cold wallet, delegate to hot wallet operator: + +```solidity +// 1. Owner claims UUID, designates operator (costs BASE_BOND) +NODL.approve(fleetIdentityAddress, BASE_BOND); +uint256 ownedTokenId = fleetIdentity.claimUuid(uuid, operatorAddress); +// tokenId = uint128(uuid), regionKey = 0 + +// 2. Operator registers later (pays full tier bond) +// Operator must call - owner cannot register for owned UUIDs +NODL.approve(fleetIdentityAddress, tierBond); // as operator +uint256 tokenId = fleetIdentity.registerFleetLocal(uuid, 840, 5, tier); +// Burns owned token, mints regional token to owner +``` + +```mermaid +sequenceDiagram + actor Owner + actor Operator + participant FI as FleetIdentity + participant TOKEN as BOND_TOKEN + + Owner->>TOKEN: approve(FleetIdentity, BASE_BOND) + Owner->>+FI: claimUuid(uuid, operatorAddress) + FI->>TOKEN: transferFrom(owner, this, BASE_BOND) + FI-->>-Owner: tokenId = uint128(uuid) + + Note over Operator: Later (only operator can register)... + + Operator->>TOKEN: approve(FleetIdentity, tierBond) + Operator->>+FI: registerFleetLocal(uuid, cc, admin, tier) + Note over FI: Burns owned token, mints to owner + FI->>TOKEN: transferFrom(operator, this, tierBond) + FI-->>-Operator: tokenId = ((cc<<10|admin)<<128) | uuid +``` + +## Operator Model + +### Key Principles + +- **Only operator can register** for owned UUIDs (owner cannot register directly) +- **Fresh registration**: caller becomes both owner + operator, pays `BASE_BOND + tierBond` +- **Owned → Registered**: operator pays full `tierBond` (BASE_BOND already paid via claimUuid) +- **Multi-region**: operator pays `tierBond` for each additional region + +### Fresh Registration (Owner = Operator) + +```solidity +// Caller becomes owner and operator +NODL.approve(fleetIdentityAddress, BASE_BOND + tierBond); +uint256 tokenId = fleetIdentity.registerFleetLocal(uuid, 840, 5, tier); +// msg.sender is now uuidOwner AND operatorOf(uuid) +``` + +### Set or Change Operator + +```solidity +// Owner sets operator (transfers ALL tier bonds atomically) +fleetIdentity.setOperator(uuid, newOperator); +// - Pulls total tier bonds from new operator +// - Refunds total tier bonds to old operator +// - Uses O(1) storage lookup (uuidTotalTierBonds) +// - Emits OperatorSet(uuid, oldOperator, newOperator, tierBondsTransferred) +``` + +### Clear Operator + +```solidity +// Owner clears operator (reverts to owner-managed) +fleetIdentity.setOperator(uuid, address(0)); +// - Refunds all tier bonds to old operator +// - Pulls all tier bonds from owner +// - operatorOf(uuid) returns owner again +``` + +## Tier Economics + +### Bond Formula + +| Level | Formula | Who Pays | +| :------ | :------------------------ | :------------------------------ | ------------------------------- | +| Owned | `BASE_BOND` | Owner | +| Local | `BASE_BOND × 2^tier` | Operator (owner paid BASE_BOND) | Operator (owner paid BASE_BOND) | +| Country | `BASE_BOND × 16 × 2^tier` | Operator (owner paid BASE_BOND) | + +**Example (BASE_BOND = 100):** + +| Tier | Local | Country | +| :--- | ----: | ------: | +| 0 | 100 | 1,600 | +| 1 | 200 | 3,200 | +| 2 | 400 | 6,400 | +| 3 | 800 | 12,800 | + +### Economic Design + +- **Tier capacity**: 10 members per tier +- **Max tiers**: 24 per region +- **Bundle limit**: 20 UUIDs per location + +Country fleets pay 16× but appear in **all** admin-area bundles. Locals have cost advantage within their area. + +## Tier Management + +### Promote + +```solidity +// Approve additional bond +fleetIdentity.promote(tokenId); +// Moves to currentTier + 1 +``` + +### Reassign + +```solidity +// Move to any tier +fleetIdentity.reassignTier(tokenId, targetTier); +// Promotion: pulls difference +// Demotion: refunds difference +``` + +```mermaid +sequenceDiagram + actor FO as Fleet Owner + participant FI as FleetIdentity + participant TOKEN as BOND_TOKEN + + alt Promote + FO->>TOKEN: approve(additionalBond) + FO->>+FI: reassignTier(tokenId, higherTier) + FI->>TOKEN: transferFrom(owner, this, diff) + FI-->>-FO: FleetPromoted + else Demote + FO->>+FI: reassignTier(tokenId, lowerTier) + FI->>TOKEN: transfer(owner, refund) + FI-->>-FO: FleetDemoted + end +``` + +## Multi-Region Registration + +Same UUID can have multiple tokens at the **same level**: + +```mermaid +sequenceDiagram + actor FO as Fleet Owner + participant FI as FleetIdentity + + FO->>+FI: registerFleetLocal(uuid, 840, 5, 0) + Note over FI: uuidLevel = Local, tokenCount = 1 + FI-->>-FO: tokenId_US + + FO->>+FI: registerFleetLocal(uuid, 276, 1, 0) + Note over FI: Same owner, same level, tokenCount = 2 + FI-->>-FO: tokenId_DE + + FO->>+FI: registerFleetCountry(uuid, 392, 0) + FI-->>-FO: ❌ UuidLevelMismatch() +``` + +**Constraints:** + +- All tokens must be same level (Local or Country) +- Each region pays its own tier bond + +## Burning + +### Owned-Only Tokens (Owner Burns) + +```solidity +// Only owner can burn owned-only tokens +fleetIdentity.burn(ownedTokenId); +// Refunds BASE_BOND to owner +// Clears uuidOwner - UUID can be claimed by anyone +``` + +### Registered Tokens (Operator Burns) + +```solidity +// Only operator can burn registered tokens +fleetIdentity.burn(tokenId); +// Refunds tier bond to operator +// If last token: mints owned-only token to owner (preserves ownership) +// Owner must burn owned-only token separately to fully release UUID +``` + +**State Transitions:** + +| State | Who Burns | Last Token? | Result | +| :--------- | :-------- | :---------- | :-------------------------------------- | +| Owned | Owner | - | Refunds BASE_BOND, clears UUID | +| Registered | Operator | No | Refunds tier bond, stays registered | +| Registered | Operator | Yes | Refunds tier bond, transitions to Owned | + +## Owned Token Transfer + +Owned-only tokens transfer UUID ownership: + +```solidity +// ERC-721 transfer +fleetIdentity.transferFrom(alice, bob, tokenId); +// uuidOwner[uuid] = bob +// Bob can now register to regions +``` + +Registered tokens can also transfer but do not change `uuidOwner`. + +## Inclusion Hints + +View functions that recommend cheapest tier guaranteeing bundle inclusion. + +### Local Hint + +```solidity +(uint256 tier, uint256 bond) = fleetIdentity.localInclusionHint(cc, admin); +// Simulates bundle for specific admin area +``` + +### Country Hint + +```solidity +(uint256 tier, uint256 bond) = fleetIdentity.countryInclusionHint(cc); +// Scans ALL active admin areas (unbounded, free off-chain) +// Returns tier guaranteeing inclusion everywhere +``` diff --git a/src/swarms/doc/iso3166-2/036-Australia.md b/src/swarms/doc/iso3166-2/036-Australia.md new file mode 100644 index 00000000..1e00f105 --- /dev/null +++ b/src/swarms/doc/iso3166-2/036-Australia.md @@ -0,0 +1,18 @@ +# Australia (036) + +ISO 3166-1 numeric: **036** + +## Admin Area Mappings + +| Admin Code | ISO 3166-2 | Name | +|-------------|------------|------| +| 1 | ACT | Australian Capital Territory | +| 2 | NSW | New South Wales | +| 3 | NT | Northern Territory | +| 4 | QLD | Queensland | +| 5 | SA | South Australia | +| 6 | TAS | Tasmania | +| 7 | VIC | Victoria | +| 8 | WA | Western Australia | + +**Total subdivisions:** 8 diff --git a/src/swarms/doc/iso3166-2/076-Brazil.md b/src/swarms/doc/iso3166-2/076-Brazil.md new file mode 100644 index 00000000..665733d7 --- /dev/null +++ b/src/swarms/doc/iso3166-2/076-Brazil.md @@ -0,0 +1,37 @@ +# Brazil (076) + +ISO 3166-1 numeric: **076** + +## Admin Area Mappings + +| Admin Code | ISO 3166-2 | Name | +|-------------|------------|------| +| 1 | AC | Acre | +| 2 | AL | Alagoas | +| 3 | AP | Amapá | +| 4 | AM | Amazonas | +| 5 | BA | Bahia | +| 6 | CE | Ceará | +| 7 | DF | Federal District | +| 8 | ES | Espírito Santo | +| 9 | GO | Goiás | +| 10 | MA | Maranhão | +| 11 | MT | Mato Grosso | +| 12 | MS | Mato Grosso do Sul | +| 13 | MG | Minas Gerais | +| 14 | PA | Pará | +| 15 | PB | Paraíba | +| 16 | PR | Paraná | +| 17 | PE | Pernambuco | +| 18 | PI | Piauí | +| 19 | RJ | Rio de Janeiro | +| 20 | RN | Rio Grande do Norte | +| 21 | RS | Rio Grande do Sul | +| 22 | RO | Rondônia | +| 23 | RR | Roraima | +| 24 | SC | Santa Catarina | +| 25 | SP | São Paulo | +| 26 | SE | Sergipe | +| 27 | TO | Tocantins | + +**Total subdivisions:** 27 diff --git a/src/swarms/doc/iso3166-2/124-Canada.md b/src/swarms/doc/iso3166-2/124-Canada.md new file mode 100644 index 00000000..f55ed1e8 --- /dev/null +++ b/src/swarms/doc/iso3166-2/124-Canada.md @@ -0,0 +1,23 @@ +# Canada (124) + +ISO 3166-1 numeric: **124** + +## Admin Area Mappings + +| Admin Code | ISO 3166-2 | Name | +|-------------|------------|------| +| 1 | AB | Alberta | +| 2 | BC | British Columbia | +| 3 | MB | Manitoba | +| 4 | NB | New Brunswick | +| 5 | NL | Newfoundland and Labrador | +| 6 | NT | Northwest Territories | +| 7 | NS | Nova Scotia | +| 8 | NU | Nunavut | +| 9 | ON | Ontario | +| 10 | PE | Prince Edward Island | +| 11 | QC | Quebec | +| 12 | SK | Saskatchewan | +| 13 | YT | Yukon | + +**Total subdivisions:** 13 diff --git a/src/swarms/doc/iso3166-2/156-China.md b/src/swarms/doc/iso3166-2/156-China.md new file mode 100644 index 00000000..c934c1cc --- /dev/null +++ b/src/swarms/doc/iso3166-2/156-China.md @@ -0,0 +1,44 @@ +# China (156) + +ISO 3166-1 numeric: **156** + +## Admin Area Mappings + +| Admin Code | ISO 3166-2 | Name | +|-------------|------------|------| +| 1 | AH | Anhui | +| 2 | BJ | Beijing | +| 3 | CQ | Chongqing | +| 4 | FJ | Fujian | +| 5 | GS | Gansu | +| 6 | GD | Guangdong | +| 7 | GX | Guangxi | +| 8 | GZ | Guizhou | +| 9 | HI | Hainan | +| 10 | HE | Hebei | +| 11 | HL | Heilongjiang | +| 12 | HA | Henan | +| 13 | HB | Hubei | +| 14 | HN | Hunan | +| 15 | JS | Jiangsu | +| 16 | JX | Jiangxi | +| 17 | JL | Jilin | +| 18 | LN | Liaoning | +| 19 | NM | Inner Mongolia | +| 20 | NX | Ningxia | +| 21 | QH | Qinghai | +| 22 | SN | Shaanxi | +| 23 | SD | Shandong | +| 24 | SH | Shanghai | +| 25 | SX | Shanxi | +| 26 | SC | Sichuan | +| 27 | TJ | Tianjin | +| 28 | XJ | Xinjiang | +| 29 | XZ | Tibet | +| 30 | YN | Yunnan | +| 31 | ZJ | Zhejiang | +| 32 | HK | Hong Kong | +| 33 | MO | Macao | +| 34 | TW | Taiwan | + +**Total subdivisions:** 34 diff --git a/src/swarms/doc/iso3166-2/250-France.md b/src/swarms/doc/iso3166-2/250-France.md new file mode 100644 index 00000000..1d253878 --- /dev/null +++ b/src/swarms/doc/iso3166-2/250-France.md @@ -0,0 +1,28 @@ +# France (250) + +ISO 3166-1 numeric: **250** + +## Admin Area Mappings + +| Admin Code | ISO 3166-2 | Name | +|-------------|------------|------| +| 1 | ARA | Auvergne-Rhône-Alpes | +| 2 | BFC | Bourgogne-Franche-Comté | +| 3 | BRE | Brittany | +| 4 | CVL | Centre-Val de Loire | +| 5 | COR | Corsica | +| 6 | GES | Grand Est | +| 7 | HDF | Hauts-de-France | +| 8 | IDF | Île-de-France | +| 9 | NOR | Normandy | +| 10 | NAQ | Nouvelle-Aquitaine | +| 11 | OCC | Occitanie | +| 12 | PDL | Pays de la Loire | +| 13 | PAC | Provence-Alpes-Côte d'Azur | +| 14 | GP | Guadeloupe | +| 15 | MQ | Martinique | +| 16 | GF | French Guiana | +| 17 | RE | Réunion | +| 18 | YT | Mayotte | + +**Total subdivisions:** 18 diff --git a/src/swarms/doc/iso3166-2/276-Germany.md b/src/swarms/doc/iso3166-2/276-Germany.md new file mode 100644 index 00000000..dea73125 --- /dev/null +++ b/src/swarms/doc/iso3166-2/276-Germany.md @@ -0,0 +1,26 @@ +# Germany (276) + +ISO 3166-1 numeric: **276** + +## Admin Area Mappings + +| Admin Code | ISO 3166-2 | Name | +|-------------|------------|------| +| 1 | BW | Baden-Württemberg | +| 2 | BY | Bavaria | +| 3 | BE | Berlin | +| 4 | BB | Brandenburg | +| 5 | HB | Bremen | +| 6 | HH | Hamburg | +| 7 | HE | Hesse | +| 8 | MV | Mecklenburg-Vorpommern | +| 9 | NI | Lower Saxony | +| 10 | NW | North Rhine-Westphalia | +| 11 | RP | Rhineland-Palatinate | +| 12 | SL | Saarland | +| 13 | SN | Saxony | +| 14 | ST | Saxony-Anhalt | +| 15 | SH | Schleswig-Holstein | +| 16 | TH | Thuringia | + +**Total subdivisions:** 16 diff --git a/src/swarms/doc/iso3166-2/356-India.md b/src/swarms/doc/iso3166-2/356-India.md new file mode 100644 index 00000000..b6ec245a --- /dev/null +++ b/src/swarms/doc/iso3166-2/356-India.md @@ -0,0 +1,46 @@ +# India (356) + +ISO 3166-1 numeric: **356** + +## Admin Area Mappings + +| Admin Code | ISO 3166-2 | Name | +|-------------|------------|------| +| 1 | AN | Andaman and Nicobar Islands | +| 2 | AP | Andhra Pradesh | +| 3 | AR | Arunachal Pradesh | +| 4 | AS | Assam | +| 5 | BR | Bihar | +| 6 | CH | Chandigarh | +| 7 | CT | Chhattisgarh | +| 8 | DH | Dadra and Nagar Haveli and Daman and Diu | +| 9 | DL | Delhi | +| 10 | GA | Goa | +| 11 | GJ | Gujarat | +| 12 | HR | Haryana | +| 13 | HP | Himachal Pradesh | +| 14 | JK | Jammu and Kashmir | +| 15 | JH | Jharkhand | +| 16 | KA | Karnataka | +| 17 | KL | Kerala | +| 18 | LA | Ladakh | +| 19 | LD | Lakshadweep | +| 20 | MP | Madhya Pradesh | +| 21 | MH | Maharashtra | +| 22 | MN | Manipur | +| 23 | ML | Meghalaya | +| 24 | MZ | Mizoram | +| 25 | NL | Nagaland | +| 26 | OR | Odisha | +| 27 | PY | Puducherry | +| 28 | PB | Punjab | +| 29 | RJ | Rajasthan | +| 30 | SK | Sikkim | +| 31 | TN | Tamil Nadu | +| 32 | TG | Telangana | +| 33 | TR | Tripura | +| 34 | UP | Uttar Pradesh | +| 35 | UT | Uttarakhand | +| 36 | WB | West Bengal | + +**Total subdivisions:** 36 diff --git a/src/swarms/doc/iso3166-2/380-Italy.md b/src/swarms/doc/iso3166-2/380-Italy.md new file mode 100644 index 00000000..06d5c5f5 --- /dev/null +++ b/src/swarms/doc/iso3166-2/380-Italy.md @@ -0,0 +1,30 @@ +# Italy (380) + +ISO 3166-1 numeric: **380** + +## Admin Area Mappings + +| Admin Code | ISO 3166-2 | Name | +|-------------|------------|------| +| 1 | 65 | Abruzzo | +| 2 | 77 | Basilicata | +| 3 | 78 | Calabria | +| 4 | 72 | Campania | +| 5 | 45 | Emilia-Romagna | +| 6 | 36 | Friuli-Venezia Giulia | +| 7 | 62 | Lazio | +| 8 | 42 | Liguria | +| 9 | 25 | Lombardy | +| 10 | 57 | Marche | +| 11 | 67 | Molise | +| 12 | 21 | Piedmont | +| 13 | 75 | Apulia | +| 14 | 88 | Sardinia | +| 15 | 82 | Sicily | +| 16 | 52 | Tuscany | +| 17 | 32 | Trentino-South Tyrol | +| 18 | 55 | Umbria | +| 19 | 23 | Aosta Valley | +| 20 | 34 | Veneto | + +**Total subdivisions:** 20 diff --git a/src/swarms/doc/iso3166-2/392-Japan.md b/src/swarms/doc/iso3166-2/392-Japan.md new file mode 100644 index 00000000..d7952e81 --- /dev/null +++ b/src/swarms/doc/iso3166-2/392-Japan.md @@ -0,0 +1,57 @@ +# Japan (392) + +ISO 3166-1 numeric: **392** + +## Admin Area Mappings + +| Admin Code | ISO 3166-2 | Name | +|-------------|------------|------| +| 1 | 01 | Hokkaido | +| 2 | 02 | Aomori | +| 3 | 03 | Iwate | +| 4 | 04 | Miyagi | +| 5 | 05 | Akita | +| 6 | 06 | Yamagata | +| 7 | 07 | Fukushima | +| 8 | 08 | Ibaraki | +| 9 | 09 | Tochigi | +| 10 | 10 | Gunma | +| 11 | 11 | Saitama | +| 12 | 12 | Chiba | +| 13 | 13 | Tokyo | +| 14 | 14 | Kanagawa | +| 15 | 15 | Niigata | +| 16 | 16 | Toyama | +| 17 | 17 | Ishikawa | +| 18 | 18 | Fukui | +| 19 | 19 | Yamanashi | +| 20 | 20 | Nagano | +| 21 | 21 | Gifu | +| 22 | 22 | Shizuoka | +| 23 | 23 | Aichi | +| 24 | 24 | Mie | +| 25 | 25 | Shiga | +| 26 | 26 | Kyoto | +| 27 | 27 | Osaka | +| 28 | 28 | Hyogo | +| 29 | 29 | Nara | +| 30 | 30 | Wakayama | +| 31 | 31 | Tottori | +| 32 | 32 | Shimane | +| 33 | 33 | Okayama | +| 34 | 34 | Hiroshima | +| 35 | 35 | Yamaguchi | +| 36 | 36 | Tokushima | +| 37 | 37 | Kagawa | +| 38 | 38 | Ehime | +| 39 | 39 | Kochi | +| 40 | 40 | Fukuoka | +| 41 | 41 | Saga | +| 42 | 42 | Nagasaki | +| 43 | 43 | Kumamoto | +| 44 | 44 | Oita | +| 45 | 45 | Miyazaki | +| 46 | 46 | Kagoshima | +| 47 | 47 | Okinawa | + +**Total subdivisions:** 47 diff --git a/src/swarms/doc/iso3166-2/410-South_Korea.md b/src/swarms/doc/iso3166-2/410-South_Korea.md new file mode 100644 index 00000000..fc145bc4 --- /dev/null +++ b/src/swarms/doc/iso3166-2/410-South_Korea.md @@ -0,0 +1,27 @@ +# South Korea (410) + +ISO 3166-1 numeric: **410** + +## Admin Area Mappings + +| Admin Code | ISO 3166-2 | Name | +|-------------|------------|------| +| 1 | 11 | Seoul | +| 2 | 26 | Busan | +| 3 | 27 | Daegu | +| 4 | 28 | Incheon | +| 5 | 29 | Gwangju | +| 6 | 30 | Daejeon | +| 7 | 31 | Ulsan | +| 8 | 41 | Gyeonggi | +| 9 | 42 | Gangwon | +| 10 | 43 | North Chungcheong | +| 11 | 44 | South Chungcheong | +| 12 | 45 | North Jeolla | +| 13 | 46 | South Jeolla | +| 14 | 47 | North Gyeongsang | +| 15 | 48 | South Gyeongsang | +| 16 | 49 | Jeju | +| 17 | 50 | Sejong | + +**Total subdivisions:** 17 diff --git a/src/swarms/doc/iso3166-2/484-Mexico.md b/src/swarms/doc/iso3166-2/484-Mexico.md new file mode 100644 index 00000000..61b384f3 --- /dev/null +++ b/src/swarms/doc/iso3166-2/484-Mexico.md @@ -0,0 +1,42 @@ +# Mexico (484) + +ISO 3166-1 numeric: **484** + +## Admin Area Mappings + +| Admin Code | ISO 3166-2 | Name | +|-------------|------------|------| +| 1 | AGU | Aguascalientes | +| 2 | BCN | Baja California | +| 3 | BCS | Baja California Sur | +| 4 | CAM | Campeche | +| 5 | CHP | Chiapas | +| 6 | CHH | Chihuahua | +| 7 | CMX | Mexico City | +| 8 | COA | Coahuila | +| 9 | COL | Colima | +| 10 | DUR | Durango | +| 11 | GUA | Guanajuato | +| 12 | GRO | Guerrero | +| 13 | HID | Hidalgo | +| 14 | JAL | Jalisco | +| 15 | MEX | State of Mexico | +| 16 | MIC | Michoacán | +| 17 | MOR | Morelos | +| 18 | NAY | Nayarit | +| 19 | NLE | Nuevo León | +| 20 | OAX | Oaxaca | +| 21 | PUE | Puebla | +| 22 | QUE | Querétaro | +| 23 | ROO | Quintana Roo | +| 24 | SLP | San Luis Potosí | +| 25 | SIN | Sinaloa | +| 26 | SON | Sonora | +| 27 | TAB | Tabasco | +| 28 | TAM | Tamaulipas | +| 29 | TLA | Tlaxcala | +| 30 | VER | Veracruz | +| 31 | YUC | Yucatán | +| 32 | ZAC | Zacatecas | + +**Total subdivisions:** 32 diff --git a/src/swarms/doc/iso3166-2/566-Nigeria.md b/src/swarms/doc/iso3166-2/566-Nigeria.md new file mode 100644 index 00000000..83b523c2 --- /dev/null +++ b/src/swarms/doc/iso3166-2/566-Nigeria.md @@ -0,0 +1,47 @@ +# Nigeria (566) + +ISO 3166-1 numeric: **566** + +## Admin Area Mappings + +| Admin Code | ISO 3166-2 | Name | +|-------------|------------|------| +| 1 | AB | Abia | +| 2 | FC | Abuja Federal Capital Territory | +| 3 | AD | Adamawa | +| 4 | AK | Akwa Ibom | +| 5 | AN | Anambra | +| 6 | BA | Bauchi | +| 7 | BY | Bayelsa | +| 8 | BE | Benue | +| 9 | BO | Borno | +| 10 | CR | Cross River | +| 11 | DE | Delta | +| 12 | EB | Ebonyi | +| 13 | ED | Edo | +| 14 | EK | Ekiti | +| 15 | EN | Enugu | +| 16 | GO | Gombe | +| 17 | IM | Imo | +| 18 | JI | Jigawa | +| 19 | KD | Kaduna | +| 20 | KN | Kano | +| 21 | KT | Katsina | +| 22 | KE | Kebbi | +| 23 | KO | Kogi | +| 24 | KW | Kwara | +| 25 | LA | Lagos | +| 26 | NA | Nasarawa | +| 27 | NI | Niger | +| 28 | OG | Ogun | +| 29 | ON | Ondo | +| 30 | OS | Osun | +| 31 | OY | Oyo | +| 32 | PL | Plateau | +| 33 | RI | Rivers | +| 34 | SO | Sokoto | +| 35 | TA | Taraba | +| 36 | YO | Yobe | +| 37 | ZA | Zamfara | + +**Total subdivisions:** 37 diff --git a/src/swarms/doc/iso3166-2/643-Russia.md b/src/swarms/doc/iso3166-2/643-Russia.md new file mode 100644 index 00000000..0705c6c6 --- /dev/null +++ b/src/swarms/doc/iso3166-2/643-Russia.md @@ -0,0 +1,93 @@ +# Russia (643) + +ISO 3166-1 numeric: **643** + +## Admin Area Mappings + +| Admin Code | ISO 3166-2 | Name | +|-------------|------------|------| +| 1 | AD | Adygea, Republic of | +| 2 | AL | Altai Republic | +| 3 | ALT | Altai Krai | +| 4 | AMU | Amur Oblast | +| 5 | ARK | Arkhangelsk Oblast | +| 6 | AST | Astrakhan Oblast | +| 7 | BA | Bashkortostan, Republic of | +| 8 | BEL | Belgorod Oblast | +| 9 | BRY | Bryansk Oblast | +| 10 | BU | Buryatia, Republic of | +| 11 | CE | Chechen Republic | +| 12 | CHE | Chelyabinsk Oblast | +| 13 | CHU | Chukotka Autonomous Okrug | +| 14 | CU | Chuvash Republic | +| 15 | DA | Dagestan, Republic of | +| 16 | IN | Ingushetia, Republic of | +| 17 | IRK | Irkutsk Oblast | +| 18 | IVA | Ivanovo Oblast | +| 19 | KB | Kabardino-Balkar Republic | +| 20 | KGD | Kaliningrad Oblast | +| 21 | KL | Kalmykia, Republic of | +| 22 | KLU | Kaluga Oblast | +| 23 | KAM | Kamchatka Krai | +| 24 | KC | Karachay-Cherkess Republic | +| 25 | KR | Karelia, Republic of | +| 26 | KEM | Kemerovo Oblast | +| 27 | KHA | Khabarovsk Krai | +| 28 | KK | Khakassia, Republic of | +| 29 | KHM | Khanty-Mansi Autonomous Okrug | +| 30 | KIR | Kirov Oblast | +| 31 | KO | Komi Republic | +| 32 | KOS | Kostroma Oblast | +| 33 | KDA | Krasnodar Krai | +| 34 | KYA | Krasnoyarsk Krai | +| 35 | KGN | Kurgan Oblast | +| 36 | KRS | Kursk Oblast | +| 37 | LEN | Leningrad Oblast | +| 38 | LIP | Lipetsk Oblast | +| 39 | MAG | Magadan Oblast | +| 40 | ME | Mari El Republic | +| 41 | MO | Mordovia, Republic of | +| 42 | MOS | Moscow Oblast | +| 43 | MOW | Moscow | +| 44 | MUR | Murmansk Oblast | +| 45 | NEN | Nenets Autonomous Okrug | +| 46 | NIZ | Nizhny Novgorod Oblast | +| 47 | NGR | Novgorod Oblast | +| 48 | NVS | Novosibirsk Oblast | +| 49 | OMS | Omsk Oblast | +| 50 | ORE | Orenburg Oblast | +| 51 | ORL | Oryol Oblast | +| 52 | PNZ | Penza Oblast | +| 53 | PER | Perm Krai | +| 54 | PRI | Primorsky Krai | +| 55 | PSK | Pskov Oblast | +| 56 | ROS | Rostov Oblast | +| 57 | RYA | Ryazan Oblast | +| 58 | SA | Sakha (Yakutia), Republic of | +| 59 | SAK | Sakhalin Oblast | +| 60 | SAM | Samara Oblast | +| 61 | SPE | Saint Petersburg | +| 62 | SAR | Saratov Oblast | +| 63 | SE | North Ossetia-Alania, Republic of | +| 64 | SMO | Smolensk Oblast | +| 65 | STA | Stavropol Krai | +| 66 | SVE | Sverdlovsk Oblast | +| 67 | TAM | Tambov Oblast | +| 68 | TA | Tatarstan, Republic of | +| 69 | TOM | Tomsk Oblast | +| 70 | TUL | Tula Oblast | +| 71 | TVE | Tver Oblast | +| 72 | TY | Tuva Republic | +| 73 | TYU | Tyumen Oblast | +| 74 | UD | Udmurt Republic | +| 75 | ULY | Ulyanovsk Oblast | +| 76 | VLA | Vladimir Oblast | +| 77 | VGG | Volgograd Oblast | +| 78 | VLG | Vologda Oblast | +| 79 | VOR | Voronezh Oblast | +| 80 | YAN | Yamalo-Nenets Autonomous Okrug | +| 81 | YAR | Yaroslavl Oblast | +| 82 | YEV | Jewish Autonomous Oblast | +| 83 | ZAB | Zabaykalsky Krai | + +**Total subdivisions:** 83 diff --git a/src/swarms/doc/iso3166-2/710-South_Africa.md b/src/swarms/doc/iso3166-2/710-South_Africa.md new file mode 100644 index 00000000..99a19bd7 --- /dev/null +++ b/src/swarms/doc/iso3166-2/710-South_Africa.md @@ -0,0 +1,19 @@ +# South Africa (710) + +ISO 3166-1 numeric: **710** + +## Admin Area Mappings + +| Admin Code | ISO 3166-2 | Name | +|-------------|------------|------| +| 1 | EC | Eastern Cape | +| 2 | FS | Free State | +| 3 | GT | Gauteng | +| 4 | NL | KwaZulu-Natal | +| 5 | LP | Limpopo | +| 6 | MP | Mpumalanga | +| 7 | NW | North West | +| 8 | NC | Northern Cape | +| 9 | WC | Western Cape | + +**Total subdivisions:** 9 diff --git a/src/swarms/doc/iso3166-2/724-Spain.md b/src/swarms/doc/iso3166-2/724-Spain.md new file mode 100644 index 00000000..6c435504 --- /dev/null +++ b/src/swarms/doc/iso3166-2/724-Spain.md @@ -0,0 +1,29 @@ +# Spain (724) + +ISO 3166-1 numeric: **724** + +## Admin Area Mappings + +| Admin Code | ISO 3166-2 | Name | +|-------------|------------|------| +| 1 | AN | Andalusia | +| 2 | AR | Aragon | +| 3 | AS | Asturias, Principality of | +| 4 | CN | Canary Islands | +| 5 | CB | Cantabria | +| 6 | CL | Castile and León | +| 7 | CM | Castilla-La Mancha | +| 8 | CT | Catalonia | +| 9 | CE | Ceuta | +| 10 | EX | Extremadura | +| 11 | GA | Galicia | +| 12 | IB | Balearic Islands | +| 13 | RI | La Rioja | +| 14 | MD | Community of Madrid | +| 15 | ML | Melilla | +| 16 | MC | Murcia, Region of | +| 17 | NC | Navarre, Chartered Community of | +| 18 | PV | Basque Country | +| 19 | VC | Valencian Community | + +**Total subdivisions:** 19 diff --git a/src/swarms/doc/iso3166-2/756-Switzerland.md b/src/swarms/doc/iso3166-2/756-Switzerland.md new file mode 100644 index 00000000..978590ee --- /dev/null +++ b/src/swarms/doc/iso3166-2/756-Switzerland.md @@ -0,0 +1,36 @@ +# Switzerland (756) + +ISO 3166-1 numeric: **756** + +## Admin Area Mappings + +| Admin Code | ISO 3166-2 | Name | +|-------------|------------|------| +| 1 | AG | Aargau | +| 2 | AI | Appenzell Innerrhoden | +| 3 | AR | Appenzell Ausserrhoden | +| 4 | BE | Bern | +| 5 | BL | Basel-Landschaft | +| 6 | BS | Basel-Stadt | +| 7 | FR | Fribourg | +| 8 | GE | Geneva | +| 9 | GL | Glarus | +| 10 | GR | Graubünden | +| 11 | JU | Jura | +| 12 | LU | Lucerne | +| 13 | NE | Neuchâtel | +| 14 | NW | Nidwalden | +| 15 | OW | Obwalden | +| 16 | SG | St. Gallen | +| 17 | SH | Schaffhausen | +| 18 | SO | Solothurn | +| 19 | SZ | Schwyz | +| 20 | TG | Thurgau | +| 21 | TI | Ticino | +| 22 | UR | Uri | +| 23 | VD | Vaud | +| 24 | VS | Valais | +| 25 | ZG | Zug | +| 26 | ZH | Zurich | + +**Total subdivisions:** 26 diff --git a/src/swarms/doc/iso3166-2/826-United_Kingdom.md b/src/swarms/doc/iso3166-2/826-United_Kingdom.md new file mode 100644 index 00000000..96eea194 --- /dev/null +++ b/src/swarms/doc/iso3166-2/826-United_Kingdom.md @@ -0,0 +1,182 @@ +# United Kingdom (826) + +ISO 3166-1 numeric: **826** + +## Admin Area Mappings + +| Admin Code | ISO 3166-2 | Name | +|-------------|------------|------| +| 1 | ENG | England | +| 2 | NIR | Northern Ireland | +| 3 | SCT | Scotland | +| 4 | WLS | Wales | +| 5 | BKM | Buckinghamshire | +| 6 | CAM | Cambridgeshire | +| 7 | CMA | Cumbria | +| 8 | DBY | Derbyshire | +| 9 | DEV | Devon | +| 10 | DOR | Dorset | +| 11 | ESX | East Sussex | +| 12 | ESS | Essex | +| 13 | GLS | Gloucestershire | +| 14 | HAM | Hampshire | +| 15 | HRT | Hertfordshire | +| 16 | KEN | Kent | +| 17 | LAN | Lancashire | +| 18 | LEC | Leicestershire | +| 19 | LIN | Lincolnshire | +| 20 | NFK | Norfolk | +| 21 | NYK | North Yorkshire | +| 22 | NTH | Northamptonshire | +| 23 | NTT | Nottinghamshire | +| 24 | OXF | Oxfordshire | +| 25 | SOM | Somerset | +| 26 | STS | Staffordshire | +| 27 | SFK | Suffolk | +| 28 | SRY | Surrey | +| 29 | WAR | Warwickshire | +| 30 | WSX | West Sussex | +| 31 | WOR | Worcestershire | +| 32 | LND | London, City of | +| 33 | BDG | Barking and Dagenham | +| 34 | BNE | Barnet | +| 35 | BEX | Bexley | +| 36 | BEN | Brent | +| 37 | BRY | Bromley | +| 38 | CMD | Camden | +| 39 | CRY | Croydon | +| 40 | EAL | Ealing | +| 41 | ENF | Enfield | +| 42 | GRE | Greenwich | +| 43 | HCK | Hackney | +| 44 | HMF | Hammersmith and Fulham | +| 45 | HRY | Haringey | +| 46 | HRW | Harrow | +| 47 | HAV | Havering | +| 48 | HIL | Hillingdon | +| 49 | HNS | Hounslow | +| 50 | ISL | Islington | +| 51 | KEC | Kensington and Chelsea | +| 52 | KTT | Kingston upon Thames | +| 53 | LBH | Lambeth | +| 54 | LEW | Lewisham | +| 55 | MRT | Merton | +| 56 | NWM | Newham | +| 57 | RDB | Redbridge | +| 58 | RIC | Richmond upon Thames | +| 59 | SWK | Southwark | +| 60 | STN | Sutton | +| 61 | TWH | Tower Hamlets | +| 62 | WFT | Waltham Forest | +| 63 | WND | Wandsworth | +| 64 | WSM | Westminster | +| 65 | BNS | Barnsley | +| 66 | BIR | Birmingham | +| 67 | BOL | Bolton | +| 68 | BRD | Bradford | +| 69 | BRI | Brighton and Hove | +| 70 | BST | Bristol, City of | +| 71 | CAL | Calderdale | +| 72 | COV | Coventry | +| 73 | DER | Derby | +| 74 | DUD | Dudley | +| 75 | GAT | Gateshead | +| 76 | KIR | Kirklees | +| 77 | KWL | Knowsley | +| 78 | LDS | Leeds | +| 79 | LCE | Leicester | +| 80 | LIV | Liverpool | +| 81 | MAN | Manchester | +| 82 | NET | Newcastle upon Tyne | +| 83 | NTY | North Tyneside | +| 84 | OLD | Oldham | +| 85 | PTE | Peterborough | +| 86 | PLY | Plymouth | +| 87 | RCH | Rochdale | +| 88 | ROT | Rotherham | +| 89 | SLF | Salford | +| 90 | SAW | Sandwell | +| 91 | SFT | Sefton | +| 92 | SHF | Sheffield | +| 93 | SOL | Solihull | +| 94 | STY | South Tyneside | +| 95 | SHN | Southampton | +| 96 | SGC | South Gloucestershire | +| 97 | STH | Southend-on-Sea | +| 98 | SKP | Stockport | +| 99 | STE | Stoke-on-Trent | +| 100 | SND | Sunderland | +| 101 | TAM | Tameside | +| 102 | TRF | Trafford | +| 103 | WKF | Wakefield | +| 104 | WLL | Walsall | +| 105 | WGN | Wigan | +| 106 | WRL | Wirral | +| 107 | WLV | Wolverhampton | +| 108 | ABE | Aberdeen City | +| 109 | ABD | Aberdeenshire | +| 110 | ANS | Angus | +| 111 | AGB | Argyll and Bute | +| 112 | CLK | Clackmannanshire | +| 113 | DGY | Dumfries and Galloway | +| 114 | DND | Dundee City | +| 115 | EAY | East Ayrshire | +| 116 | EDU | East Dunbartonshire | +| 117 | ELN | East Lothian | +| 118 | ERW | East Renfrewshire | +| 119 | EDH | Edinburgh, City of | +| 120 | ELS | Eilean Siar | +| 121 | FAL | Falkirk | +| 122 | FIF | Fife | +| 123 | GLG | Glasgow City | +| 124 | HLD | Highland | +| 125 | IVC | Inverclyde | +| 126 | MLN | Midlothian | +| 127 | MRY | Moray | +| 128 | NAY | North Ayrshire | +| 129 | NLK | North Lanarkshire | +| 130 | ORK | Orkney Islands | +| 131 | PKN | Perth and Kinross | +| 132 | RFW | Renfrewshire | +| 133 | SCB | Scottish Borders | +| 134 | ZET | Shetland Islands | +| 135 | SAY | South Ayrshire | +| 136 | SLK | South Lanarkshire | +| 137 | STG | Stirling | +| 138 | WDU | West Dunbartonshire | +| 139 | WLN | West Lothian | +| 140 | BGW | Blaenau Gwent | +| 141 | BGE | Bridgend | +| 142 | CAY | Caerphilly | +| 143 | CRF | Cardiff | +| 144 | CMN | Carmarthenshire | +| 145 | CGN | Ceredigion | +| 146 | CWY | Conwy | +| 147 | DEN | Denbighshire | +| 148 | FLN | Flintshire | +| 149 | GWN | Gwynedd | +| 150 | AGY | Isle of Anglesey | +| 151 | MTY | Merthyr Tydfil | +| 152 | MON | Monmouthshire | +| 153 | NTL | Neath Port Talbot | +| 154 | NWP | Newport | +| 155 | PEM | Pembrokeshire | +| 156 | POW | Powys | +| 157 | RCT | Rhondda Cynon Taf | +| 158 | SWA | Swansea | +| 159 | TOF | Torfaen | +| 160 | VGL | Vale of Glamorgan | +| 161 | WRX | Wrexham | +| 162 | ANT | Antrim and Newtownabbey | +| 163 | ARD | Ards and North Down | +| 164 | ABC | Armagh City, Banbridge and Craigavon | +| 165 | BFS | Belfast | +| 166 | CCG | Causeway Coast and Glens | +| 167 | DRS | Derry City and Strabane | +| 168 | FMO | Fermanagh and Omagh | +| 169 | LBC | Lisburn and Castlereagh | +| 170 | MEA | Mid and East Antrim | +| 171 | MUL | Mid Ulster | +| 172 | NMD | Newry, Mourne and Down | + +**Total subdivisions:** 172 diff --git a/src/swarms/doc/iso3166-2/840-United_States.md b/src/swarms/doc/iso3166-2/840-United_States.md new file mode 100644 index 00000000..1259bd49 --- /dev/null +++ b/src/swarms/doc/iso3166-2/840-United_States.md @@ -0,0 +1,67 @@ +# United States (840) + +ISO 3166-1 numeric: **840** + +## Admin Area Mappings + +| Admin Code | ISO 3166-2 | Name | +|-------------|------------|------| +| 1 | AL | Alabama | +| 2 | AK | Alaska | +| 3 | AZ | Arizona | +| 4 | AR | Arkansas | +| 5 | CA | California | +| 6 | CO | Colorado | +| 7 | CT | Connecticut | +| 8 | DE | Delaware | +| 9 | FL | Florida | +| 10 | GA | Georgia | +| 11 | HI | Hawaii | +| 12 | ID | Idaho | +| 13 | IL | Illinois | +| 14 | IN | Indiana | +| 15 | IA | Iowa | +| 16 | KS | Kansas | +| 17 | KY | Kentucky | +| 18 | LA | Louisiana | +| 19 | ME | Maine | +| 20 | MD | Maryland | +| 21 | MA | Massachusetts | +| 22 | MI | Michigan | +| 23 | MN | Minnesota | +| 24 | MS | Mississippi | +| 25 | MO | Missouri | +| 26 | MT | Montana | +| 27 | NE | Nebraska | +| 28 | NV | Nevada | +| 29 | NH | New Hampshire | +| 30 | NJ | New Jersey | +| 31 | NM | New Mexico | +| 32 | NY | New York | +| 33 | NC | North Carolina | +| 34 | ND | North Dakota | +| 35 | OH | Ohio | +| 36 | OK | Oklahoma | +| 37 | OR | Oregon | +| 38 | PA | Pennsylvania | +| 39 | RI | Rhode Island | +| 40 | SC | South Carolina | +| 41 | SD | South Dakota | +| 42 | TN | Tennessee | +| 43 | TX | Texas | +| 44 | UT | Utah | +| 45 | VT | Vermont | +| 46 | VA | Virginia | +| 47 | WA | Washington | +| 48 | WV | West Virginia | +| 49 | WI | Wisconsin | +| 50 | WY | Wyoming | +| 51 | DC | District of Columbia | +| 52 | AS | American Samoa | +| 53 | GU | Guam | +| 54 | MP | Northern Mariana Islands | +| 55 | PR | Puerto Rico | +| 56 | UM | United States Minor Outlying Islands | +| 57 | VI | Virgin Islands, U.S. | + +**Total subdivisions:** 57 diff --git a/src/swarms/doc/iso3166-reference.md b/src/swarms/doc/iso3166-reference.md new file mode 100644 index 00000000..855865d7 --- /dev/null +++ b/src/swarms/doc/iso3166-reference.md @@ -0,0 +1,97 @@ +# ISO 3166 Reference + +## Country Codes (ISO 3166-1 Numeric) + +FleetIdentity uses ISO 3166-1 numeric codes (1-999) for country identification. + +| Code | Country | +| :--- | :------------- | +| 124 | Canada | +| 250 | France | +| 276 | Germany | +| 392 | Japan | +| 826 | United Kingdom | +| 840 | United States | + +## Admin Area Codes + +Admin codes map ISO 3166-2 subdivisions to 1-indexed integers. + +### Region Key Encoding + +``` +Country: regionKey = countryCode +Admin Area: regionKey = (countryCode << 10) | adminCode +``` + +**Examples:** +| Location | Country | Admin | Region Key | +| :------- | ------: | ----: | ---------: | +| United States | 840 | — | 840 | +| US-California | 840 | 5 | 860,165 | +| Canada | 124 | — | 124 | +| CA-Alberta | 124 | 1 | 127,001 | + +## Admin Area Mapping Files + +The [iso3166-2/](iso3166-2/) directory contains per-country mappings. + +### File Format + +Filename: `{ISO_3166-1_numeric}-{Country_Name}.md` + +| Admin Code | ISO 3166-2 | Name | +| ---------: | :--------- | :-------------------- | +| 1 | XX | Full subdivision name | +| 2 | YY | ... | + +### Constraints + +- Admin codes: 1-indexed integers +- Valid range: 1-255 (covers all real-world subdivisions) +- Code 0 is invalid (reverts with `InvalidAdminCode()`) + +## United States (840) + +Selected entries from [iso3166-2/840-United_States.md](iso3166-2/840-United_States.md): + +| Admin | ISO 3166-2 | State | +| ----: | :--------- | :--------- | +| 1 | AL | Alabama | +| 5 | CA | California | +| 32 | NY | New York | +| 43 | TX | Texas | + +## Usage + +```solidity +// US-California +uint16 countryCode = 840; +uint8 adminCode = 5; +uint32 regionKey = fleetIdentity.makeAdminRegion(countryCode, adminCode); +// regionKey = (840 << 10) | 5 = 860165 + +// Register +fleetIdentity.registerFleetLocal(uuid, countryCode, adminCode, tier); +// tokenId = (860165 << 128) | uint128(uuid) +``` + +## Contract Functions + +```solidity +// Build region key +uint32 region = fleetIdentity.makeAdminRegion(countryCode, adminCode); + +// Active regions +uint16[] memory countries = fleetIdentity.getActiveCountries(); +uint32[] memory adminAreas = fleetIdentity.getActiveAdminAreas(); + +// Extract from token +uint32 region = fleetIdentity.tokenRegion(tokenId); +// If region < 1024: country-level +// If region >= 1024: adminCode = region & 0x3FF, countryCode = region >> 10 +``` + +## Data Source + +Mappings based on ISO 3166-2 standard maintained by ISO and national statistical agencies. diff --git a/src/swarms/doc/lifecycle.md b/src/swarms/doc/lifecycle.md new file mode 100644 index 00000000..6772934d --- /dev/null +++ b/src/swarms/doc/lifecycle.md @@ -0,0 +1,138 @@ +# Lifecycle & State Machines + +## UUID Registration States + +```mermaid +stateDiagram-v2 + [*] --> None + + None --> Owned : claimUuid() + None --> Local : registerFleetLocal() + None --> Country : registerFleetCountry() + + Owned --> Local : registerFleetLocal() + Owned --> Country : registerFleetCountry() + Owned --> [*] : releaseUuid() / burn() + + Local --> Owned : unregisterToOwned() + Local --> [*] : burn() all tokens + + Country --> Owned : unregisterToOwned() + Country --> [*] : burn() all tokens + + note right of Owned : regionKey = 0 + note right of Local : regionKey ≥ 1024 + note right of Country : regionKey 1-999 +``` + +### State Transitions + +| From | To | Function | Bond Effect | +| :------------ | :------ | :------------------------- | :------------------------------------------------------------- | +| None | Owned | `claimUuid()` | Pull BASE_BOND from owner | +| None | Local | `registerFleetLocal()` | Pull BASE_BOND + tierBond from caller (becomes owner+operator) | +| None | Country | `registerFleetCountry()` | Pull BASE_BOND + tierBond from caller (becomes owner+operator) | +| Owned | Local | `registerFleetLocal()` | Pull tierBond from operator (only operator can call) | +| Owned | Country | `registerFleetCountry()` | Pull tierBond from operator (only operator can call) | +| Local/Country | Owned | `unregisterToOwned()` | Refund tierBond to operator | +| Owned | None | `releaseUuid()` / `burn()` | Refund BASE_BOND to owner | +| Local/Country | None | `burn()` | Refund tierBond to operator; BASE_BOND to owner on last burn | + +## Swarm Status States + +```mermaid +stateDiagram-v2 + [*] --> REGISTERED : registerSwarm() + + REGISTERED --> ACCEPTED : acceptSwarm() + REGISTERED --> REJECTED : rejectSwarm() + + ACCEPTED --> REGISTERED : updateSwarm*() + REJECTED --> REGISTERED : updateSwarm*() + + REGISTERED --> [*] : delete / purge + ACCEPTED --> [*] : delete / purge + REJECTED --> [*] : delete / purge +``` + +### Status Effects + +| Status | checkMembership | Provider Action Required | +| :--------- | :-------------- | :------------------------------- | +| REGISTERED | Reverts | Accept or reject | +| ACCEPTED | Works | None | +| REJECTED | Reverts | None (fleet can update to retry) | + +## Fleet Token Lifecycle + +```mermaid +sequenceDiagram + participant TOKEN as BOND_TOKEN + participant FI as FleetIdentity + participant Owner + participant Operator + + Note over FI: Fresh Registration (caller = owner+operator) + FI->>TOKEN: transferFrom(caller, this, BASE_BOND + tierBond) + + Note over FI: Owned → Registered (operator only) + FI->>TOKEN: transferFrom(operator, this, tierBond) + + Note over FI: Multi-region (operator only) + FI->>TOKEN: transferFrom(operator, this, tierBond) + + Note over FI: Promotion (operator pays) + FI->>TOKEN: transferFrom(operator, this, additionalBond) + + Note over FI: Demotion (operator receives) + FI->>TOKEN: transfer(operator, refund) + + Note over FI: Change Operator (O(1) via uuidTotalTierBonds) + FI->>TOKEN: transferFrom(newOperator, this, totalTierBonds) + FI->>TOKEN: transfer(oldOperator, totalTierBonds) + + Note over FI: Unregister to Owned + FI->>TOKEN: transfer(operator, tierBond) + + Note over FI: Burn (non-last token) + FI->>TOKEN: transfer(operator, tierBond) + + Note over FI: Burn (last token) + FI->>TOKEN: transfer(operator, tierBond) + FI->>TOKEN: transfer(owner, BASE_BOND) +``` + +## Orphan Lifecycle + +```mermaid +flowchart TD + ACTIVE[Swarm Active] --> BURN{NFT burned?} + BURN -->|No| ACTIVE + BURN -->|Yes| ORPHAN[Swarm Orphaned] + ORPHAN --> CHECK[isSwarmValid returns false] + CHECK --> PURGE[Anyone: purgeOrphanedSwarm] + PURGE --> DELETED[Swarm Deleted + Gas Refund] +``` + +### Orphan Guards + +These operations revert with `SwarmOrphaned()` if either NFT invalid: + +- `acceptSwarm(swarmId)` +- `rejectSwarm(swarmId)` +- `checkMembership(swarmId, tagHash)` + +## Region Index Maintenance + +```mermaid +flowchart LR + REG[registerFleet*] --> FIRST{First in region?} + FIRST -->|Yes| ADD[Add to activeCountries/activeAdminAreas] + FIRST -->|No| SKIP[Already indexed] + + BURN[burn / demotion] --> EMPTY{Region empty?} + EMPTY -->|Yes| REMOVE[Remove from index] + EMPTY -->|No| KEEP[Keep] +``` + +Indexes are automatically maintained—no manual intervention needed. diff --git a/src/swarms/doc/maintenance.md b/src/swarms/doc/maintenance.md new file mode 100644 index 00000000..bad2231d --- /dev/null +++ b/src/swarms/doc/maintenance.md @@ -0,0 +1,202 @@ +# Fleet Maintenance + +## Overview + +After registration, fleet owners (or their designated operators) must monitor bundle inclusion as market conditions change: + +- New fleets registering at higher tiers +- Existing fleets promoting +- Bundle slots limited to 20 per location + +## Operator Delegation + +Fleet tier management can be delegated to an **operator**: + +```solidity +// Owner delegates to operator (transfers total tier bonds) +fleetIdentity.setOperator(uuid, operatorAddress); + +// Check who manages tiers +address manager = fleetIdentity.operatorOf(uuid); // returns operator or owner + +// Check total tier bonds for a UUID (O(1) lookup) +uint256 totalBonds = fleetIdentity.uuidTotalTierBonds(uuid); +``` + +**Bond responsibilities:** + +- **Owner**: Pays BASE_BOND once at initial registration/claim +- **Operator**: Pays/receives all tier bonds (promotion, demotion, registration) + +**Permissions:** + +- **Register**: Only operator can register owned UUIDs +- **Promote/Demote**: Only operator (or owner if no operator set) +- **Burn**: Only token holder (ERC-721 ownerOf) +- **SetOperator**: Only UUID owner + +**setOperator Transfer:** +When changing operators, the new operator must pay the old operator for **all** accumulated tier bonds across all registered regions (tracked via `uuidTotalTierBonds` for O(1) lookup). + +## Maintenance Cycle + +```mermaid +flowchart TD + START([Registered]) --> CHECK{In bundle?} + + CHECK -->|Yes| OPTIMIZE{Lower tier
possible?} + CHECK -->|No| PROMOTE[Promote] + + OPTIMIZE -->|Yes| DEMOTE[Demote → refund] + OPTIMIZE -->|No| WAIT + + DEMOTE --> WAIT + PROMOTE --> WAIT + + WAIT[Wait 24h] --> CHECK + + style DEMOTE fill:#2ecc71,color:#fff + style PROMOTE fill:#ff9f43,color:#fff +``` + +## Check Inclusion + +### Local Fleets + +```typescript +const [uuids, count] = await fleetIdentity.buildHighestBondedUuidBundle( + countryCode, + adminCode, +); +const isIncluded = uuids + .slice(0, count) + .some((u) => u.toLowerCase() === myUuid.toLowerCase()); +``` + +### Country Fleets + +Must check **every** active admin area in their country: + +```typescript +const adminAreas = await fleetIdentity.getActiveAdminAreas(); +const myAdminAreas = adminAreas.filter( + (rk) => Number(rk >> 10n) === myCountryCode, +); + +const missingAreas = []; +for (const rk of myAdminAreas) { + const adminCode = Number(rk & 0x3ffn); + const [uuids, count] = await fleetIdentity.buildHighestBondedUuidBundle( + myCountryCode, + adminCode, + ); + if (!uuids.slice(0, count).some((u) => u === myUuid)) { + missingAreas.push(adminCode); + } +} +``` + +### No Active Admin Areas + +When no EdgeBeaconScanners deployed yet: + +```typescript +const [uuids, count] = await fleetIdentity.buildCountryOnlyBundle(countryCode); +// Check position among country-level competitors only +``` + +## Get Required Tier + +### Local + +```solidity +(uint256 tier, uint256 bond) = fleetIdentity.localInclusionHint(cc, admin); +``` + +### Country + +```solidity +(uint256 tier, uint256 bond) = fleetIdentity.countryInclusionHint(cc); +// Scans ALL active admin areas (unbounded view, free off-chain) +``` + +## Promote + +```mermaid +sequenceDiagram + actor FO as Fleet Owner + participant FI as FleetIdentity + participant TOKEN as BOND_TOKEN + + FO->>+FI: tierBond(currentTier, isCountry) + FI-->>-FO: currentBond + FO->>+FI: tierBond(targetTier, isCountry) + FI-->>-FO: targetBond + + Note over FO: additionalBond = targetBond - currentBond + + FO->>TOKEN: approve(FleetIdentity, additionalBond) + FO->>+FI: reassignTier(tokenId, targetTier) + FI->>TOKEN: transferFrom(...) + FI-->>-FO: FleetPromoted +``` + +### Quick Promote + +```solidity +fleetIdentity.promote(tokenId); +// Moves to currentTier + 1 +``` + +### Handle TierFull + +```typescript +while (attempts < 3) { + try { + await fleetIdentity.reassignTier(tokenId, requiredTier); + break; + } catch (e) { + if (e.message.includes("TierFull")) { + const [newTier] = await fleetIdentity.localInclusionHint(cc, admin); + requiredTier = newTier; + // Re-approve if needed + } else throw e; + } +} +``` + +## Demote (Save Bond) + +No approval needed—refunds automatically: + +```typescript +const [suggestedTier] = await fleetIdentity.localInclusionHint(cc, admin); +const currentTier = await fleetIdentity.fleetTier(tokenId); + +if (suggestedTier < currentTier) { + await fleetIdentity.reassignTier(tokenId, suggestedTier); + // Refund deposited to operator +} +``` + +## Propagation Timing + +| Phase | Duration | +| :----------------------- | :--------------- | +| Transaction confirmation | ~1-2s (ZkSync) | +| Event indexing | ~1-10s | +| Edge network sync | Minutes to hours | + +**Recommendation**: 24-hour check interval. + +## Summary + +| Task | Method | +| :-------------------------- | :---------------------------------------- | +| Check inclusion (local) | `buildHighestBondedUuidBundle(cc, admin)` | +| Check inclusion (country) | Loop all admin areas | +| Get required tier (local) | `localInclusionHint(cc, admin)` | +| Get required tier (country) | `countryInclusionHint(cc)` | +| Calculate bond | `tierBond(tier, isCountry)` | +| Move tier | `reassignTier(tokenId, tier)` | +| Quick promote | `promote(tokenId)` | diff --git a/src/swarms/doc/swarm-operations.md b/src/swarms/doc/swarm-operations.md new file mode 100644 index 00000000..853352b8 --- /dev/null +++ b/src/swarms/doc/swarm-operations.md @@ -0,0 +1,199 @@ +# Swarm Operations + +## Overview + +A **Swarm** is a cryptographic representation of ~10k-20k BLE tags. Individual tags are never enumerated on-chain—membership is verified via XOR filter. + +## Registration Flow + +```mermaid +sequenceDiagram + actor FO as Fleet Owner + actor PRV as Provider Owner + participant SR as SwarmRegistry + participant FI as FleetIdentity + participant SP as ServiceProvider + + Note over FO: Build XOR filter off-chain + + FO->>+SR: registerSwarm(fleetUuid, providerId, filter, fpSize, tagType) + SR->>FI: uuidOwner(fleetUuid) + SR->>SP: ownerOf(providerId) + Note over SR: swarmId = keccak256(fleetUuid, providerId, filter) + SR-->>-FO: swarmId (status: REGISTERED) + + PRV->>+SR: acceptSwarm(swarmId) + SR->>SP: ownerOf(providerId) + SR-->>-PRV: status: ACCEPTED +``` + +### Parameters + +| Parameter | Type | Description | +| :----------- | :------ | :--------------------------- | +| `fleetUuid` | bytes16 | UUID that owns this swarm | +| `providerId` | uint256 | ServiceProvider token ID | +| `filter` | bytes | XOR filter data | +| `fpSize` | uint8 | Fingerprint size (1-16 bits) | +| `tagType` | TagType | Tag identity scheme | + +### Swarm ID + +Deterministic derivation: + +```solidity +swarmId = uint256(keccak256(abi.encode(fleetUuid, providerId, filter))) +``` + +Duplicate registration reverts with `SwarmAlreadyExists()`. + +## XOR Filter Construction + +### Off-Chain Steps + +1. **Build TagIDs** for all tags per TagType schema +2. **Hash each TagID**: `tagHash = keccak256(tagId)` +3. **Construct XOR filter** using Peeling Algorithm +4. **Submit filter** in `registerSwarm()` + +### TagType Schemas + +| Type | Format | Bytes | +| :--------------------- | :---------------------------- | -----: | +| `IBEACON_PAYLOAD_ONLY` | UUID ∥ Major ∥ Minor | 20 | +| `IBEACON_INCLUDES_MAC` | UUID ∥ Major ∥ Minor ∥ MAC | 26 | +| `VENDOR_ID` | companyID ∥ hash(vendorBytes) | varies | +| `GENERIC` | custom | varies | + +### MAC Normalization (IBEACON_INCLUDES_MAC) + +| MAC Type | Action | +| :---------------------- | :------------------------------- | +| Public/Static (00) | Use real MAC | +| Random/Private (01, 11) | Replace with `FF:FF:FF:FF:FF:FF` | + +This supports rotating privacy MACs while validating "it's a privacy tag." + +### Filter Membership Math + +``` +h = keccak256(tagId) +M = filterLength * 8 / fingerprintSize // slot count + +h1 = uint32(h) % M +h2 = uint32(h >> 32) % M +h3 = uint32(h >> 64) % M +fp = (h >> 96) & ((1 << fingerprintSize) - 1) + +Member if: Filter[h1] ^ Filter[h2] ^ Filter[h3] == fp +``` + +## Provider Approval + +```mermaid +stateDiagram-v2 + [*] --> REGISTERED : registerSwarm() + + REGISTERED --> ACCEPTED : acceptSwarm() + REGISTERED --> REJECTED : rejectSwarm() + + ACCEPTED --> REGISTERED : updateSwarm*() + REJECTED --> REGISTERED : updateSwarm*() + + REGISTERED --> [*] : deleteSwarm() / purge + ACCEPTED --> [*] : deleteSwarm() / purge + REJECTED --> [*] : deleteSwarm() / purge +``` + +| Action | Caller | Effect | +| :--------------------- | :------------- | :---------------- | +| `acceptSwarm(swarmId)` | Provider owner | status → ACCEPTED | +| `rejectSwarm(swarmId)` | Provider owner | status → REJECTED | + +Only `ACCEPTED` swarms pass `checkMembership()`. + +## Updates + +Both operations reset status to `REGISTERED`: + +```solidity +// Replace filter +swarmRegistry.updateSwarmFilter(swarmId, newFilterData); + +// Change provider +swarmRegistry.updateSwarmProvider(swarmId, newProviderId); +``` + +```mermaid +sequenceDiagram + actor FO as Fleet Owner + participant SR as SwarmRegistry + participant FI as FleetIdentity + + FO->>+SR: updateSwarmFilter(swarmId, newFilter) + SR->>FI: uuidOwner(fleetUuid) + Note over SR: status → REGISTERED + SR-->>-FO: ✓ (requires re-approval) +``` + +## Deletion + +```solidity +swarmRegistry.deleteSwarm(swarmId); +``` + +- Removes from `uuidSwarms[]` (O(1) swap-and-pop) +- Deletes `swarms[swarmId]` +- Universal variant: deletes `filterData[swarmId]` + +## Orphan Handling + +When fleet or provider NFT is burned, referencing swarms become **orphaned**. + +### Detection + +```solidity +(bool fleetValid, bool providerValid) = swarmRegistry.isSwarmValid(swarmId); +// Returns (false, _) if UUID has no owner +// Returns (_, false) if provider NFT burned +``` + +### Cleanup + +Anyone can purge orphaned swarms: + +```solidity +swarmRegistry.purgeOrphanedSwarm(swarmId); +// Gas refund incentive +``` + +```mermaid +sequenceDiagram + actor Anyone as Purger + participant SR as SwarmRegistry + + Anyone->>+SR: isSwarmValid(swarmId) + SR-->>-Anyone: (false, true) + + Anyone->>+SR: purgeOrphanedSwarm(swarmId) + Note over SR: Verify orphaned + Note over SR: Delete swarm data + SR-->>-Anyone: SwarmPurged event + gas refund +``` + +### Guards + +Operations revert with `SwarmOrphaned()` if either NFT is invalid: + +- `acceptSwarm()` +- `rejectSwarm()` +- `checkMembership()` + +## Storage Variants + +| Variant | Filter Storage | Deletion | +| :------------ | :-------------------------- | :-------------------------------- | +| **L1** | SSTORE2 (contract bytecode) | Struct cleared; bytecode persists | +| **Universal** | `mapping(uint256 => bytes)` | Full deletion | + +Universal exposes `getFilterData(swarmId)` for off-chain retrieval. diff --git a/test/FleetIdentity.t.sol b/test/FleetIdentity.t.sol new file mode 100644 index 00000000..b382b32f --- /dev/null +++ b/test/FleetIdentity.t.sol @@ -0,0 +1,3745 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.24; + +import {Test} from "forge-std/Test.sol"; +import {FleetIdentity} from "../src/swarms/FleetIdentity.sol"; +import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; + +/// @dev Minimal ERC-20 mock with public mint for testing. +contract MockERC20 is ERC20 { + constructor() ERC20("Mock Bond Token", "MBOND") {} + + function mint(address to, uint256 amount) external { + _mint(to, amount); + } +} + +/// @dev ERC-20 that returns false on transfer instead of reverting. +contract BadERC20 is ERC20 { + bool public shouldFail; + + constructor() ERC20("Bad Token", "BAD") {} + + function mint(address to, uint256 amount) external { + _mint(to, amount); + } + + function setFail(bool _fail) external { + shouldFail = _fail; + } + + function transfer(address to, uint256 amount) public override returns (bool) { + if (shouldFail) return false; + return super.transfer(to, amount); + } + + function transferFrom(address from, address to, uint256 amount) public override returns (bool) { + if (shouldFail) return false; + return super.transferFrom(from, to, amount); + } +} + +contract FleetIdentityTest is Test { + FleetIdentity fleet; + MockERC20 bondToken; + + address alice = address(0xA); + address bob = address(0xB); + address carol = address(0xC); + + bytes16 constant UUID_1 = bytes16(keccak256("fleet-alpha")); + bytes16 constant UUID_2 = bytes16(keccak256("fleet-bravo")); + bytes16 constant UUID_3 = bytes16(keccak256("fleet-charlie")); + + uint256 constant BASE_BOND = 100 ether; + + uint16 constant US = 840; + uint16 constant DE = 276; + uint16 constant FR = 250; + uint16 constant JP = 392; + uint16 constant ADMIN_CA = 1; + uint16 constant ADMIN_NY = 2; + + event FleetRegistered( + address indexed owner, + bytes16 indexed uuid, + uint256 indexed tokenId, + uint32 regionKey, + uint256 tierIndex, + uint256 bondAmount, + address operator + ); + event OperatorSet( + bytes16 indexed uuid, + address indexed oldOperator, + address indexed newOperator, + uint256 tierExcessTransferred + ); + event FleetPromoted( + uint256 indexed tokenId, uint256 indexed fromTier, uint256 indexed toTier, uint256 additionalBond + ); + event FleetDemoted(uint256 indexed tokenId, uint256 indexed fromTier, uint256 indexed toTier, uint256 bondRefund); + event FleetBurned( + address indexed owner, uint256 indexed tokenId, uint32 indexed regionKey, uint256 tierIndex, uint256 bondRefund + ); + + function setUp() public { + bondToken = new MockERC20(); + fleet = new FleetIdentity(address(bondToken), BASE_BOND); + + // Mint enough for all 24 tiers (tier 23 bond = BASE_BOND * 2^23 ≈ 838M ether) + // Total for 8 members across 24 tiers ≈ 13.4 billion ether + bondToken.mint(alice, 100_000_000_000_000 ether); + bondToken.mint(bob, 100_000_000_000_000 ether); + bondToken.mint(carol, 100_000_000_000_000 ether); + + vm.prank(alice); + bondToken.approve(address(fleet), type(uint256).max); + vm.prank(bob); + bondToken.approve(address(fleet), type(uint256).max); + vm.prank(carol); + bondToken.approve(address(fleet), type(uint256).max); + } + + // --- Helpers --- + + /// @dev Compute tokenId from (uuid, region) using new encoding + function _tokenId(bytes16 uuid, uint32 region) internal pure returns (uint256) { + return (uint256(region) << 128) | uint256(uint128(uuid)); + } + + /// @dev Given a UUID from buildBundle, find tokenId by checking local first, then country + function _findTokenId(bytes16 uuid, uint16 cc, uint16 admin) internal view returns (uint256) { + uint32 localRegion = (uint32(cc) << 10) | uint32(admin); + uint256 localTokenId = _tokenId(uuid, localRegion); + // Check if local token exists by trying to get its owner + try fleet.ownerOf(localTokenId) returns (address) { + return localTokenId; + } catch { + uint32 countryRegion = uint32(cc); + return _tokenId(uuid, countryRegion); + } + } + + function _uuid(uint256 i) internal pure returns (bytes16) { + return bytes16(keccak256(abi.encodePacked("fleet-", i))); + } + + function _regionUS() internal pure returns (uint32) { + return uint32(US); + } + + function _regionDE() internal pure returns (uint32) { + return uint32(DE); + } + + function _regionUSCA() internal pure returns (uint32) { + return (uint32(US) << 10) | uint32(ADMIN_CA); + } + + function _regionUSNY() internal pure returns (uint32) { + return (uint32(US) << 10) | uint32(ADMIN_NY); + } + + function _makeAdminRegion(uint16 cc, uint16 admin) internal pure returns (uint32) { + return (uint32(cc) << 10) | uint32(admin); + } + + function _registerNCountry(address owner, uint16 cc, uint256 count, uint256 startSeed) + internal + returns (uint256[] memory ids) + { + uint256 cap = fleet.TIER_CAPACITY(); + ids = new uint256[](count); + for (uint256 i = 0; i < count; i++) { + uint256 targetTier = i / cap; + vm.prank(owner); + ids[i] = fleet.registerFleetCountry(_uuid(startSeed + i), cc, targetTier); + } + } + + function _registerNCountryAt(address owner, uint16 cc, uint256 count, uint256 startSeed, uint256 tier) + internal + returns (uint256[] memory ids) + { + ids = new uint256[](count); + for (uint256 i = 0; i < count; i++) { + vm.prank(owner); + ids[i] = fleet.registerFleetCountry(_uuid(startSeed + i), cc, tier); + } + } + + function _registerNLocal(address owner, uint16 cc, uint16 admin, uint256 count, uint256 startSeed) + internal + returns (uint256[] memory ids) + { + uint256 cap = fleet.TIER_CAPACITY(); + ids = new uint256[](count); + for (uint256 i = 0; i < count; i++) { + uint256 targetTier = i / cap; + vm.prank(owner); + ids[i] = fleet.registerFleetLocal(_uuid(startSeed + i), cc, admin, targetTier); + } + } + + function _registerNLocalAt(address owner, uint16 cc, uint16 admin, uint256 count, uint256 startSeed, uint256 tier) + internal + returns (uint256[] memory ids) + { + ids = new uint256[](count); + for (uint256 i = 0; i < count; i++) { + vm.prank(owner); + ids[i] = fleet.registerFleetLocal(_uuid(startSeed + i), cc, admin, tier); + } + } + + // --- Constructor --- + + function test_constructor_setsImmutables() public view { + assertEq(address(fleet.BOND_TOKEN()), address(bondToken)); + assertEq(fleet.BASE_BOND(), BASE_BOND); + assertEq(fleet.name(), "Swarm Fleet Identity"); + assertEq(fleet.symbol(), "SFID"); + } + + function test_constructor_constants() public view { + assertEq(fleet.TIER_CAPACITY(), 10); + assertEq(fleet.MAX_TIERS(), 24); + assertEq(fleet.MAX_BONDED_UUID_BUNDLE_SIZE(), 20); + assertEq(fleet.COUNTRY_BOND_MULTIPLIER(), 16); + } + + // --- tierBond --- + + function test_tierBond_local_tier0() public view { + // Local regions get 1× multiplier + assertEq(fleet.tierBond(0, false), BASE_BOND); + } + + function test_tierBond_country_tier0() public view { + // Country regions get 16x multiplier + assertEq(fleet.tierBond(0, true), BASE_BOND * fleet.COUNTRY_BOND_MULTIPLIER()); + } + + function test_tierBond_local_tier1() public view { + assertEq(fleet.tierBond(1, false), BASE_BOND * 2); + } + + function test_tierBond_country_tier1() public view { + assertEq(fleet.tierBond(1, true), BASE_BOND * fleet.COUNTRY_BOND_MULTIPLIER() * 2); + } + + function test_tierBond_geometricProgression() public view { + for (uint256 i = 1; i <= 5; i++) { + assertEq(fleet.tierBond(i, false), fleet.tierBond(i - 1, false) * 2); + assertEq(fleet.tierBond(i, true), fleet.tierBond(i - 1, true) * 2); + } + } + + // --- registerFleetCountry --- + + function test_registerFleetCountry_auto_setsRegionAndTier() public { + vm.prank(alice); + uint256 tokenId = fleet.registerFleetCountry(UUID_1, US, 0); + + assertEq(fleet.tokenRegion(tokenId), _regionUS()); + assertEq(fleet.fleetTier(tokenId), 0); + assertEq(fleet.bonds(tokenId), BASE_BOND * fleet.COUNTRY_BOND_MULTIPLIER()); // Country gets 16x multiplier + assertEq(fleet.regionTierCount(_regionUS()), 1); + } + + function test_RevertIf_registerFleetCountry_invalidCode_zero() public { + vm.prank(alice); + vm.expectRevert(FleetIdentity.InvalidCountryCode.selector); + fleet.registerFleetCountry(UUID_1, 0, 0); + } + + function test_RevertIf_registerFleetCountry_invalidCode_over999() public { + vm.prank(alice); + vm.expectRevert(FleetIdentity.InvalidCountryCode.selector); + fleet.registerFleetCountry(UUID_1, 1000, 0); + } + + // --- registerFleetLocal --- + + function test_registerFleetLocal_setsRegionAndTier() public { + vm.prank(alice); + uint256 tokenId = fleet.registerFleetLocal(UUID_1, US, ADMIN_CA, 0); + + assertEq(fleet.tokenRegion(tokenId), _regionUSCA()); + assertEq(fleet.fleetTier(tokenId), 0); + assertEq(fleet.bonds(tokenId), BASE_BOND); + } + + function test_RevertIf_registerFleetLocal_invalidCountry() public { + vm.prank(alice); + vm.expectRevert(FleetIdentity.InvalidCountryCode.selector); + fleet.registerFleetLocal(UUID_1, 0, ADMIN_CA, 0); + } + + function test_RevertIf_registerFleetLocal_invalidAdmin_zero() public { + vm.prank(alice); + vm.expectRevert(FleetIdentity.InvalidAdminCode.selector); + fleet.registerFleetLocal(UUID_1, US, 0, 0); + } + + function test_RevertIf_registerFleetLocal_invalidAdmin_over4095() public { + vm.prank(alice); + vm.expectRevert(FleetIdentity.InvalidAdminCode.selector); + fleet.registerFleetLocal(UUID_1, US, 4096, 0); + } + + // --- Per-region independent tier indexing (KEY REQUIREMENT) --- + + function test_perRegionTiers_firstFleetInEachLevelPaysBondWithMultiplier() public { + // Country level pays 16x multiplier + vm.prank(alice); + uint256 c1 = fleet.registerFleetCountry(UUID_1, US, 0); + // Local level pays 1× multiplier + vm.prank(alice); + uint256 l1 = fleet.registerFleetLocal(UUID_2, US, ADMIN_CA, 0); + + assertEq(fleet.fleetTier(c1), 0); + assertEq(fleet.fleetTier(l1), 0); + + assertEq(fleet.bonds(c1), BASE_BOND * fleet.COUNTRY_BOND_MULTIPLIER()); // Country gets 16× multiplier + assertEq(fleet.bonds(l1), BASE_BOND); // Local gets 1× multiplier + } + + function test_perRegionTiers_fillOneRegionDoesNotAffectOthers() public { + // Fill US country tier 0 with 4 fleets + _registerNCountryAt(alice, US, 4, 0, 0); + assertEq(fleet.regionTierCount(_regionUS()), 1); + assertEq(fleet.tierMemberCount(_regionUS(), 0), 4); + + // Next US country fleet goes to tier 1 + vm.prank(bob); + uint256 us21 = fleet.registerFleetCountry(_uuid(100), US, 1); + assertEq(fleet.fleetTier(us21), 1); + assertEq(fleet.bonds(us21), BASE_BOND * fleet.COUNTRY_BOND_MULTIPLIER() * 2); // Country tier 1: 16× * 2^1 + + // DE country is independent - can still join tier 0 + vm.prank(bob); + uint256 de1 = fleet.registerFleetCountry(_uuid(200), DE, 0); + assertEq(fleet.fleetTier(de1), 0); + assertEq(fleet.bonds(de1), BASE_BOND * fleet.COUNTRY_BOND_MULTIPLIER()); + assertEq(fleet.regionTierCount(_regionDE()), 1); + + // US local is independent - can still join tier 0 + vm.prank(bob); + uint256 usca1 = fleet.registerFleetLocal(_uuid(300), US, ADMIN_CA, 0); + assertEq(fleet.fleetTier(usca1), 0); + assertEq(fleet.bonds(usca1), BASE_BOND); + } + + function test_perRegionTiers_twoCountriesIndependent() public { + // Register 4 US country fleets at tier 0 + _registerNCountryAt(alice, US, 4, 0, 0); + assertEq(fleet.tierMemberCount(_regionUS(), 0), 4); + + // Next US country fleet explicitly goes to tier 1 + vm.prank(bob); + uint256 us21 = fleet.registerFleetCountry(_uuid(500), US, 1); + assertEq(fleet.fleetTier(us21), 1); + assertEq(fleet.bonds(us21), BASE_BOND * fleet.COUNTRY_BOND_MULTIPLIER() * 2); // Country tier 1: 16× * 2^1 + + // DE country is independent - can still join tier 0 + vm.prank(bob); + uint256 de1 = fleet.registerFleetCountry(_uuid(600), DE, 0); + assertEq(fleet.fleetTier(de1), 0); + assertEq(fleet.bonds(de1), BASE_BOND * fleet.COUNTRY_BOND_MULTIPLIER()); // Country tier 0: 16× * 2^0 + } + + function test_perRegionTiers_twoAdminAreasIndependent() public { + // Register 4 local fleets at tier 0 in US/CA + _registerNLocalAt(alice, US, ADMIN_CA, 4, 0, 0); + assertEq(fleet.tierMemberCount(_regionUSCA(), 0), 4); + + // NY is independent - can still join tier 0 + vm.prank(bob); + uint256 ny1 = fleet.registerFleetLocal(_uuid(500), US, ADMIN_NY, 0); + assertEq(fleet.fleetTier(ny1), 0); + assertEq(fleet.bonds(ny1), BASE_BOND); + } + + // --- Local inclusion hint tier logic --- + + function test_localInclusionHint_emptyRegionReturnsTier0() public { + // No fleets anywhere — localInclusionHint returns tier 0. + (uint256 inclusionTier,) = fleet.localInclusionHint(US, ADMIN_CA); + assertEq(inclusionTier, 0); + + // Register at tier 0 (inclusionTier is 0, so no promotion needed) + vm.prank(alice); + uint256 tokenId = fleet.registerFleetLocal(UUID_1, US, ADMIN_CA, 0); + assertEq(fleet.fleetTier(tokenId), 0); + assertEq(fleet.regionTierCount(_regionUSCA()), 1); + } + + function test_localInclusionHint_returnsCheapestInclusionTier() public { + // Fill admin-area tier 0 so tier 0 is full. + _registerNLocalAt(alice, US, ADMIN_CA, fleet.TIER_CAPACITY(), 0, 0); + + // localInclusionHint should return tier 1 (cheapest tier with capacity). + (uint256 inclusionTier,) = fleet.localInclusionHint(US, ADMIN_CA); + assertEq(inclusionTier, 1); + + // Register directly at inclusionTier as tier 0 is full + vm.prank(bob); + uint256 tokenId = fleet.registerFleetLocal(_uuid(100), US, ADMIN_CA, inclusionTier); + assertEq(fleet.fleetTier(tokenId), 1); + assertEq(fleet.regionTierCount(_regionUSCA()), 2); + } + + // --- promote --- + + function test_promote_next_movesToNextTierInRegion() public { + vm.prank(alice); + uint256 tokenId = fleet.registerFleetCountry(UUID_1, US, 0); + + vm.prank(alice); + fleet.promote(tokenId); + + assertEq(fleet.fleetTier(tokenId), 1); + assertEq(fleet.bonds(tokenId), fleet.tierBond(1, true)); + } + + function test_promote_next_pullsBondDifference() public { + vm.prank(alice); + uint256 tokenId = fleet.registerFleetLocal(UUID_1, US, ADMIN_CA, 0); + + uint256 balBefore = bondToken.balanceOf(alice); + uint256 diff = fleet.tierBond(1, false) - fleet.tierBond(0, false); + + vm.prank(alice); + fleet.promote(tokenId); + + assertEq(bondToken.balanceOf(alice), balBefore - diff); + } + + function test_reassignTier_promotesWhenTargetHigher() public { + vm.prank(alice); + uint256 tokenId = fleet.registerFleetLocal(UUID_1, US, ADMIN_CA, 0); + + vm.prank(alice); + fleet.reassignTier(tokenId, 3); + + assertEq(fleet.fleetTier(tokenId), 3); + assertEq(fleet.bonds(tokenId), fleet.tierBond(3, false)); + assertEq(fleet.regionTierCount(_regionUSCA()), 4); + } + + function test_promote_emitsEvent() public { + vm.prank(alice); + uint256 tokenId = fleet.registerFleetLocal(UUID_1, US, ADMIN_CA, 0); + uint256 diff = fleet.tierBond(1, false) - fleet.tierBond(0, false); + + vm.expectEmit(true, true, true, true); + emit FleetPromoted(tokenId, 0, 1, diff); + + vm.prank(alice); + fleet.promote(tokenId); + } + + function test_RevertIf_promote_notOperator() public { + vm.prank(alice); + uint256 tokenId = fleet.registerFleetLocal(UUID_1, US, ADMIN_CA, 0); + + vm.prank(bob); + vm.expectRevert(FleetIdentity.NotOperator.selector); + fleet.promote(tokenId); + } + + function test_RevertIf_reassignTier_targetSameAsCurrent() public { + vm.prank(alice); + uint256 tokenId = fleet.registerFleetLocal(UUID_1, US, ADMIN_CA, 0); + vm.prank(alice); + fleet.reassignTier(tokenId, 2); + + vm.prank(alice); + vm.expectRevert(FleetIdentity.TargetTierSameAsCurrent.selector); + fleet.reassignTier(tokenId, 2); + } + + function test_RevertIf_promote_targetTierFull() public { + vm.prank(alice); + uint256 tokenId = fleet.registerFleetLocal(UUID_1, US, ADMIN_CA, 0); + + // Fill tier 1 with TIER_CAPACITY members + for (uint256 i = 0; i < fleet.TIER_CAPACITY(); i++) { + vm.prank(bob); + fleet.registerFleetLocal(_uuid(50 + i), US, ADMIN_CA, 1); + } + + vm.prank(alice); + vm.expectRevert(FleetIdentity.TierFull.selector); + fleet.promote(tokenId); + } + + function test_RevertIf_reassignTier_exceedsMaxTiers() public { + vm.prank(alice); + uint256 tokenId = fleet.registerFleetLocal(UUID_1, US, ADMIN_CA, 0); + + vm.prank(alice); + vm.expectRevert(FleetIdentity.MaxTiersReached.selector); + fleet.reassignTier(tokenId, 50); + } + + // --- reassignTier (demote direction) --- + + function test_reassignTier_demotesWhenTargetLower() public { + vm.prank(alice); + uint256 tokenId = fleet.registerFleetCountry(UUID_1, DE, 0); + vm.prank(alice); + fleet.reassignTier(tokenId, 3); + + vm.prank(alice); + fleet.reassignTier(tokenId, 1); + + assertEq(fleet.fleetTier(tokenId), 1); + assertEq(fleet.bonds(tokenId), fleet.tierBond(1, true)); + } + + function test_reassignTier_demoteRefundsBondDifference() public { + vm.prank(alice); + uint256 tokenId = fleet.registerFleetLocal(UUID_1, US, ADMIN_CA, 0); + vm.prank(alice); + fleet.reassignTier(tokenId, 3); + + uint256 balBefore = bondToken.balanceOf(alice); + uint256 refund = fleet.tierBond(3, false) - fleet.tierBond(1, false); + + vm.prank(alice); + fleet.reassignTier(tokenId, 1); + + assertEq(bondToken.balanceOf(alice), balBefore + refund); + } + + function test_reassignTier_demoteEmitsEvent() public { + vm.prank(alice); + uint256 tokenId = fleet.registerFleetLocal(UUID_1, US, ADMIN_CA, 0); + vm.prank(alice); + fleet.reassignTier(tokenId, 3); + uint256 refund = fleet.tierBond(3, false) - fleet.tierBond(1, false); + + vm.expectEmit(true, true, true, true); + emit FleetDemoted(tokenId, 3, 1, refund); + + vm.prank(alice); + fleet.reassignTier(tokenId, 1); + } + + function test_reassignTier_demoteTrimsTierCountWhenTopEmpties() public { + vm.prank(alice); + uint256 tokenId = fleet.registerFleetLocal(UUID_1, US, ADMIN_CA, 0); + vm.prank(alice); + fleet.reassignTier(tokenId, 3); + assertEq(fleet.regionTierCount(_regionUSCA()), 4); + + vm.prank(alice); + fleet.reassignTier(tokenId, 0); + assertEq(fleet.regionTierCount(_regionUSCA()), 1); + } + + function test_RevertIf_reassignTier_demoteNotOperator() public { + vm.prank(alice); + uint256 tokenId = fleet.registerFleetLocal(UUID_1, US, ADMIN_CA, 0); + vm.prank(alice); + fleet.reassignTier(tokenId, 2); + + vm.prank(bob); + vm.expectRevert(FleetIdentity.NotOperator.selector); + fleet.reassignTier(tokenId, 0); + } + + function test_RevertIf_reassignTier_demoteTargetTierFull() public { + _registerNLocalAt(alice, US, ADMIN_CA, fleet.TIER_CAPACITY(), 0, 0); + + // Register at tier 1 since tier 0 is full, then promote further + vm.prank(bob); + uint256 tokenId = fleet.registerFleetLocal(_uuid(100), US, ADMIN_CA, 1); + vm.prank(bob); + fleet.reassignTier(tokenId, 2); + + vm.prank(bob); + vm.expectRevert(FleetIdentity.TierFull.selector); + fleet.reassignTier(tokenId, 0); + } + + function test_RevertIf_reassignTier_promoteNotOperator() public { + vm.prank(alice); + uint256 tokenId = fleet.registerFleetLocal(UUID_1, US, ADMIN_CA, 0); + + vm.prank(bob); + vm.expectRevert(FleetIdentity.NotOperator.selector); + fleet.reassignTier(tokenId, 3); + } + + // --- burn --- + + function test_burn_refundsTierBond() public { + vm.prank(alice); + uint256 tokenId = fleet.registerFleetLocal(UUID_1, US, ADMIN_CA, 0); + uint256 balBefore = bondToken.balanceOf(alice); + + vm.prank(alice); + fleet.burn(tokenId); + + // Last registered token burn -> transitions to owned-only + // Operator (alice) gets tierBond refund, owned-only token minted (holds BASE_BOND) + assertEq(bondToken.balanceOf(alice), balBefore + fleet.tierBond(0, false)); + assertEq(bondToken.balanceOf(address(fleet)), BASE_BOND); // owned-only token holds BASE_BOND + assertEq(fleet.bonds(tokenId), 0); + + // Verify owned-only token was minted + uint256 ownedTokenId = uint256(uint128(UUID_1)); + assertEq(fleet.ownerOf(ownedTokenId), alice); + assertTrue(fleet.isOwnedOnly(UUID_1)); + } + + function test_burn_emitsEvent() public { + vm.prank(alice); + uint256 tokenId = fleet.registerFleetLocal(UUID_1, US, ADMIN_CA, 0); + + vm.expectEmit(true, true, true, true); + // Event emits tier bond refund only (owned-only token keeps BASE_BOND) + emit FleetBurned(alice, tokenId, _regionUSCA(), 0, fleet.tierBond(0, false)); + + vm.prank(alice); + fleet.burn(tokenId); + } + + function test_burn_trimsTierCount() public { + vm.prank(alice); + uint256 tokenId = fleet.registerFleetCountry(UUID_1, US, 0); + vm.prank(alice); + fleet.reassignTier(tokenId, 3); + assertEq(fleet.regionTierCount(_regionUS()), 4); + + vm.prank(alice); + fleet.burn(tokenId); + assertEq(fleet.regionTierCount(_regionUS()), 0); + + // Verify transitioned to owned-only + assertTrue(fleet.isOwnedOnly(UUID_1)); + } + + function test_burn_allowsReregistration_sameRegion() public { + vm.prank(alice); + uint256 tokenId = fleet.registerFleetLocal(UUID_1, US, ADMIN_CA, 0); + + vm.prank(alice); + fleet.burn(tokenId); + + // Now in owned-only state - burn that too to fully release + uint256 ownedTokenId = uint256(uint128(UUID_1)); + vm.prank(alice); + fleet.burn(ownedTokenId); + + // Same UUID can be re-registered in same region, same tokenId + vm.prank(bob); + uint256 newId = fleet.registerFleetLocal(UUID_1, US, ADMIN_CA, 0); + assertEq(newId, tokenId); + assertEq(fleet.tokenRegion(newId), _regionUSCA()); + } + + function test_multiRegion_sameUuidCanRegisterInDifferentRegions() public { + // Same UUID can be registered in multiple regions simultaneously (by SAME owner, SAME level) + vm.prank(alice); + uint256 localId1 = fleet.registerFleetLocal(UUID_1, US, ADMIN_CA, 0); + + vm.prank(alice); + uint256 localId2 = fleet.registerFleetLocal(UUID_1, DE, ADMIN_CA, 0); + + // Different tokenIds for different regions + assertTrue(localId1 != localId2, "Different regions should have different tokenIds"); + + // Both have same UUID but different regions + assertEq(fleet.tokenUuid(localId1), UUID_1); + assertEq(fleet.tokenUuid(localId2), UUID_1); + assertEq(fleet.tokenRegion(localId1), _regionUSCA()); + assertEq(fleet.tokenRegion(localId2), _makeAdminRegion(DE, ADMIN_CA)); + + // Both owned by alice + assertEq(fleet.ownerOf(localId1), alice); + assertEq(fleet.ownerOf(localId2), alice); + } + + function test_RevertIf_burn_registeredToken_notOperator() public { + vm.prank(alice); + uint256 tokenId = fleet.registerFleetLocal(UUID_1, US, ADMIN_CA, 0); + + // Bob is not operator - should revert + vm.prank(bob); + vm.expectRevert(FleetIdentity.NotOperator.selector); + fleet.burn(tokenId); + } + + // --- localInclusionHint --- + + function test_localInclusionHint_emptyRegion() public view { + (uint256 tier, uint256 bond) = fleet.localInclusionHint(US, ADMIN_CA); + assertEq(tier, 0); + assertEq(bond, BASE_BOND); + } + + function test_localInclusionHint_afterFillingAdminTier0() public { + _registerNLocalAt(alice, US, ADMIN_CA, fleet.TIER_CAPACITY(), 0, 0); + + // Admin tier 0 full → cheapest inclusion is tier 1. + (uint256 tier, uint256 bond) = fleet.localInclusionHint(US, ADMIN_CA); + assertEq(tier, 1); + assertEq(bond, BASE_BOND * 2); + } + + // --- highestActiveTier --- + + function test_highestActiveTier_noFleets() public view { + assertEq(fleet.highestActiveTier(_regionUS()), 0); + assertEq(fleet.highestActiveTier(_regionUSCA()), 0); + } + + function test_highestActiveTier_afterRegistrations() public { + vm.prank(alice); + uint256 tokenId = fleet.registerFleetCountry(UUID_1, US, 0); + vm.prank(alice); + fleet.reassignTier(tokenId, 3); + assertEq(fleet.highestActiveTier(_regionUS()), 3); + + // Different region still at 0 + assertEq(fleet.highestActiveTier(_regionDE()), 0); + } + + // --- EdgeBeaconScanner helpers --- + + function test_tierMemberCount_perRegion() public { + _registerNLocalAt(alice, US, ADMIN_CA, 3, 0, 0); + _registerNCountryAt(bob, US, 4, 100, 0); + + assertEq(fleet.tierMemberCount(_regionUSCA(), 0), 3); + assertEq(fleet.tierMemberCount(_regionUS(), 0), 4); + } + + function test_getTierMembers_perRegion() public { + vm.prank(alice); + uint256 usId = fleet.registerFleetCountry(UUID_1, US, 0); + + vm.prank(bob); + uint256 uscaId = fleet.registerFleetLocal(UUID_2, US, ADMIN_CA, 0); + + uint256[] memory usMembers = fleet.getTierMembers(_regionUS(), 0); + assertEq(usMembers.length, 1); + assertEq(usMembers[0], usId); + + uint256[] memory uscaMembers = fleet.getTierMembers(_regionUSCA(), 0); + assertEq(uscaMembers.length, 1); + assertEq(uscaMembers[0], uscaId); + } + + function test_getTierUuids_perRegion() public { + vm.prank(alice); + fleet.registerFleetCountry(UUID_1, US, 0); + + vm.prank(bob); + fleet.registerFleetLocal(UUID_2, US, ADMIN_CA, 0); + + bytes16[] memory usUUIDs = fleet.getTierUuids(_regionUS(), 0); + assertEq(usUUIDs.length, 1); + assertEq(usUUIDs[0], UUID_1); + + bytes16[] memory uscaUUIDs = fleet.getTierUuids(_regionUSCA(), 0); + assertEq(uscaUUIDs.length, 1); + assertEq(uscaUUIDs[0], UUID_2); + } + + // --- Region indexes --- + + function test_activeCountries_addedOnRegistration() public { + vm.prank(alice); + fleet.registerFleetCountry(UUID_1, US, 0); + vm.prank(bob); + fleet.registerFleetCountry(UUID_2, DE, 0); + + uint16[] memory countries = fleet.getActiveCountries(); + assertEq(countries.length, 2); + } + + function test_activeCountries_removedWhenAllBurned() public { + vm.prank(alice); + uint256 id1 = fleet.registerFleetCountry(UUID_1, US, 0); + + uint16[] memory before_ = fleet.getActiveCountries(); + assertEq(before_.length, 1); + + vm.prank(alice); + fleet.burn(id1); + + uint16[] memory after_ = fleet.getActiveCountries(); + assertEq(after_.length, 0); + } + + function test_activeCountries_notDuplicated() public { + vm.prank(alice); + fleet.registerFleetCountry(UUID_1, US, 0); + vm.prank(bob); + fleet.registerFleetCountry(UUID_2, US, 0); + + uint16[] memory countries = fleet.getActiveCountries(); + assertEq(countries.length, 1); + assertEq(countries[0], US); + } + + function test_activeAdminAreas_trackedCorrectly() public { + vm.prank(alice); + fleet.registerFleetLocal(UUID_1, US, ADMIN_CA, 0); + vm.prank(bob); + fleet.registerFleetLocal(UUID_2, US, ADMIN_NY, 0); + + uint32[] memory areas = fleet.getActiveAdminAreas(); + assertEq(areas.length, 2); + } + + function test_activeAdminAreas_removedWhenAllBurned() public { + vm.prank(alice); + uint256 id1 = fleet.registerFleetLocal(UUID_1, US, ADMIN_CA, 0); + + assertEq(fleet.getActiveAdminAreas().length, 1); + + vm.prank(alice); + fleet.burn(id1); + + assertEq(fleet.getActiveAdminAreas().length, 0); + } + + // --- Region key helpers --- + + function test_makeAdminRegion() public view { + assertEq(fleet.makeAdminRegion(US, ADMIN_CA), (uint32(US) << 10) | uint32(ADMIN_CA)); + } + + function test_regionKeyNoOverlap_countryVsAdmin() public pure { + uint32 maxCountry = 999; + uint32 minAdmin = (uint32(1) << 10) | uint32(1); + assertTrue(minAdmin > maxCountry); + } + + // --- tokenUuid / bonds --- + + function test_tokenUuid_roundTrip() public { + vm.prank(alice); + uint256 tokenId = fleet.registerFleetLocal(UUID_1, US, ADMIN_CA, 0); + assertEq(fleet.tokenUuid(tokenId), UUID_1); + } + + function test_bonds_returnsTierBond() public { + vm.prank(alice); + uint256 tokenId = fleet.registerFleetLocal(UUID_1, US, ADMIN_CA, 0); + assertEq(fleet.bonds(tokenId), BASE_BOND); + } + + function test_bonds_zeroForNonexistentToken() public view { + assertEq(fleet.bonds(99999), 0); + } + + // --- ERC721Enumerable --- + + function test_enumerable_totalSupply() public { + assertEq(fleet.totalSupply(), 0); + + vm.prank(alice); + fleet.registerFleetCountry(UUID_1, US, 0); + assertEq(fleet.totalSupply(), 1); + + vm.prank(bob); + fleet.registerFleetCountry(UUID_2, DE, 0); + assertEq(fleet.totalSupply(), 2); + + vm.prank(carol); + fleet.registerFleetLocal(UUID_3, US, ADMIN_CA, 0); + assertEq(fleet.totalSupply(), 3); + } + + function test_enumerable_supportsInterface() public view { + assertTrue(fleet.supportsInterface(0x780e9d63)); + assertTrue(fleet.supportsInterface(0x80ac58cd)); + assertTrue(fleet.supportsInterface(0x01ffc9a7)); + } + + // --- Bond accounting --- + + function test_bondAccounting_acrossRegions() public { + vm.prank(alice); + uint256 c1 = fleet.registerFleetCountry(UUID_1, US, 0); + vm.prank(bob); + uint256 c2 = fleet.registerFleetCountry(UUID_2, DE, 0); + vm.prank(carol); + uint256 l1 = fleet.registerFleetLocal(UUID_3, US, ADMIN_CA, 0); + + // Each token costs BASE_BOND + tierBond + // c1 and c2 are country (BASE_BOND + 16*BASE_BOND each), l1 is local (BASE_BOND + BASE_BOND) + uint256 countryTotal = 2 * (BASE_BOND + fleet.tierBond(0, true)); + uint256 localTotal = BASE_BOND + fleet.tierBond(0, false); + assertEq(bondToken.balanceOf(address(fleet)), countryTotal + localTotal); + + // Burn c2: transitions to owned-only (BASE_BOND stays in contract) + vm.prank(bob); + fleet.burn(c2); + uint256 ownedTokenBob = uint256(uint128(UUID_2)); + // After burning c2, remaining: c1 + l1 + owned-only token for UUID_2 + assertEq(bondToken.balanceOf(address(fleet)), (BASE_BOND + fleet.tierBond(0, true)) + (BASE_BOND + fleet.tierBond(0, false)) + BASE_BOND); + + // Burn the owned-only token for UUID_2 + vm.prank(bob); + fleet.burn(ownedTokenBob); + // Now: c1 + l1 + assertEq(bondToken.balanceOf(address(fleet)), (BASE_BOND + fleet.tierBond(0, true)) + (BASE_BOND + fleet.tierBond(0, false))); + + // Burn remaining tokens (and their resulting owned-only tokens) + vm.prank(alice); + fleet.burn(c1); + vm.prank(alice); + fleet.burn(uint256(uint128(UUID_1))); + vm.prank(carol); + fleet.burn(l1); + vm.prank(carol); + fleet.burn(uint256(uint128(UUID_3))); + assertEq(bondToken.balanceOf(address(fleet)), 0); + } + + function test_bondAccounting_reassignTierRoundTrip() public { + vm.prank(alice); + uint256 tokenId = fleet.registerFleetLocal(UUID_1, US, ADMIN_CA, 0); + uint256 balStart = bondToken.balanceOf(alice); + + vm.prank(alice); + fleet.reassignTier(tokenId, 3); + + vm.prank(alice); + fleet.reassignTier(tokenId, 0); + + assertEq(bondToken.balanceOf(alice), balStart); + assertEq(fleet.bonds(tokenId), BASE_BOND); + } + + // --- ERC-20 edge case --- + + function test_RevertIf_bondToken_transferFromReturnsFalse() public { + BadERC20 badToken = new BadERC20(); + FleetIdentity f = new FleetIdentity(address(badToken), BASE_BOND); + + badToken.mint(alice, 1_000 ether); + vm.prank(alice); + badToken.approve(address(f), type(uint256).max); + + badToken.setFail(true); + + vm.prank(alice); + vm.expectRevert(); + f.registerFleetLocal(UUID_1, US, ADMIN_CA, 0); + } + + // --- Transfer preserves region and tier --- + + function test_transfer_regionAndTierStayWithToken() public { + vm.prank(alice); + uint256 tokenId = fleet.registerFleetCountry(UUID_1, US, 0); + vm.prank(alice); + fleet.reassignTier(tokenId, 2); + + vm.prank(alice); + fleet.transferFrom(alice, bob, tokenId); + + assertEq(fleet.tokenRegion(tokenId), _regionUS()); + assertEq(fleet.fleetTier(tokenId), 2); + assertEq(fleet.bonds(tokenId), fleet.tierBond(2, true)); + + // After transfer, bob holds the token but alice is still uuidOwner/operator. + // On burn, operator (alice) gets full tierBond, owned-only token minted to owner (alice). + uint256 aliceBefore = bondToken.balanceOf(alice); + vm.prank(alice); // operator burns + fleet.burn(tokenId); + // Alice gets tier bond refund + assertEq(bondToken.balanceOf(alice), aliceBefore + fleet.tierBond(2, true)); + // Owned-only token minted to alice + assertTrue(fleet.isOwnedOnly(UUID_1)); + } + + // --- Tier lifecycle --- + + function test_tierLifecycle_fillBurnBackfillPerRegion() public { + // Register 4 US country fleets at tier 0 (fills capacity) + uint256[] memory usIds = _registerNCountryAt(alice, US, 4, 0, 0); + assertEq(fleet.tierMemberCount(_regionUS(), 0), 4); + + // Next country fleet goes to tier 1 + vm.prank(bob); + uint256 us5 = fleet.registerFleetCountry(_uuid(100), US, 1); + assertEq(fleet.fleetTier(us5), 1); + + // Burn from tier 0 — now tier 0 has 3, tier 1 has 1. + vm.prank(alice); + fleet.burn(usIds[3]); + + // Explicitly register into tier 1. + vm.prank(carol); + uint256 backfill = fleet.registerFleetCountry(_uuid(200), US, 1); + assertEq(fleet.fleetTier(backfill), 1); + assertEq(fleet.tierMemberCount(_regionUS(), 1), 2); + } + + // --- Edge cases --- + + function test_zeroBaseBond_allowsRegistration() public { + FleetIdentity f = new FleetIdentity(address(bondToken), 0); + vm.prank(alice); + bondToken.approve(address(f), type(uint256).max); + + vm.prank(alice); + uint256 tokenId = f.registerFleetLocal(UUID_1, US, ADMIN_CA, 0); + assertEq(f.bonds(tokenId), 0); + + vm.prank(alice); + f.burn(tokenId); + } + + // --- Fuzz Tests --- + + function testFuzz_registerFleetCountry_validCountryCodes(uint16 cc) public { + cc = uint16(bound(cc, 1, 999)); + + vm.prank(alice); + uint256 tokenId = fleet.registerFleetCountry(UUID_1, cc, 0); + + assertEq(fleet.tokenRegion(tokenId), uint32(cc)); + assertEq(fleet.fleetTier(tokenId), 0); + assertEq(fleet.bonds(tokenId), BASE_BOND * fleet.COUNTRY_BOND_MULTIPLIER()); // Country gets 16x multiplier + } + + function testFuzz_registerFleetLocal_validCodes(uint16 cc, uint16 admin) public { + cc = uint16(bound(cc, 1, 999)); + admin = uint16(bound(admin, 1, 255)); + + vm.prank(alice); + uint256 tokenId = fleet.registerFleetLocal(UUID_1, cc, admin, 0); + + uint32 expectedRegion = (uint32(cc) << 10) | uint32(admin); + assertEq(fleet.tokenRegion(tokenId), expectedRegion); + assertEq(fleet.fleetTier(tokenId), 0); + assertEq(fleet.bonds(tokenId), BASE_BOND); + } + + function testFuzz_promote_onlyOperator(address caller) public { + vm.assume(caller != alice); + vm.assume(caller != address(0)); + + vm.prank(alice); + uint256 tokenId = fleet.registerFleetLocal(UUID_1, US, ADMIN_CA, 0); + + vm.prank(caller); + vm.expectRevert(FleetIdentity.NotOperator.selector); + fleet.promote(tokenId); + } + + function testFuzz_burn_onlyOperator(address caller) public { + vm.assume(caller != alice); + vm.assume(caller != address(0)); + + vm.prank(alice); + uint256 tokenId = fleet.registerFleetLocal(UUID_1, US, ADMIN_CA, 0); + + // Only operator (alice) can burn registered tokens, not random callers + vm.prank(caller); + vm.expectRevert(FleetIdentity.NotOperator.selector); + fleet.burn(tokenId); + } + + // ══════════════════════════════════════════════ + // UUID Ownership Enforcement Tests + // ══════════════════════════════════════════════ + + function test_uuidOwner_setOnFirstRegistration() public { + assertEq(fleet.uuidOwner(UUID_1), address(0), "No owner before registration"); + assertEq(fleet.uuidTokenCount(UUID_1), 0, "No tokens before registration"); + + vm.prank(alice); + fleet.registerFleetLocal(UUID_1, US, ADMIN_CA, 0); + + assertEq(fleet.uuidOwner(UUID_1), alice, "Alice is UUID owner after registration"); + assertEq(fleet.uuidTokenCount(UUID_1), 1, "Token count is 1 after registration"); + } + + function test_uuidOwner_sameOwnerCanRegisterMultipleRegions() public { + // Alice registers UUID_1 in first region (same level across all) + vm.prank(alice); + uint256 id1 = fleet.registerFleetLocal(UUID_1, US, ADMIN_CA, 0); + + // Alice can register same UUID in second region (same level) + vm.prank(alice); + uint256 id2 = fleet.registerFleetLocal(UUID_1, DE, ADMIN_CA, 0); + + // And a third region (same level) + vm.prank(alice); + uint256 id3 = fleet.registerFleetLocal(UUID_1, FR, ADMIN_CA, 0); + + assertEq(fleet.uuidOwner(UUID_1), alice, "Alice is still UUID owner"); + assertEq(fleet.uuidTokenCount(UUID_1), 3, "Token count is 3"); + assertEq(fleet.ownerOf(id1), alice); + assertEq(fleet.ownerOf(id2), alice); + assertEq(fleet.ownerOf(id3), alice); + } + + function test_RevertIf_differentOwnerRegistersSameUuid_local() public { + // Alice registers UUID_1 first + vm.prank(alice); + fleet.registerFleetLocal(UUID_1, US, ADMIN_CA, 0); + + // Bob tries to register same UUID in different region → revert + vm.prank(bob); + vm.expectRevert(FleetIdentity.NotOperator.selector); + fleet.registerFleetLocal(UUID_1, DE, ADMIN_CA, 0); + } + + function test_RevertIf_differentOwnerRegistersSameUuid_country() public { + // Alice registers UUID_1 first + vm.prank(alice); + fleet.registerFleetCountry(UUID_1, US, 0); + + // Bob tries to register same UUID in different country → revert + vm.prank(bob); + vm.expectRevert(FleetIdentity.NotOperator.selector); + fleet.registerFleetCountry(UUID_1, DE, 0); + } + + function test_RevertIf_differentOwnerRegistersSameUuid_crossLevel() public { + // Alice registers UUID_1 at country level + vm.prank(alice); + fleet.registerFleetCountry(UUID_1, US, 0); + + // Bob tries to register same UUID at local level → revert + vm.prank(bob); + vm.expectRevert(FleetIdentity.NotOperator.selector); + fleet.registerFleetLocal(UUID_1, DE, ADMIN_CA, 0); + } + + function test_uuidOwner_clearedWhenAllTokensBurned() public { + // Alice registers UUID_1 in one region + vm.prank(alice); + uint256 tokenId = fleet.registerFleetLocal(UUID_1, US, ADMIN_CA, 0); + + assertEq(fleet.uuidOwner(UUID_1), alice); + assertEq(fleet.uuidTokenCount(UUID_1), 1); + + // Burn the registered token -> transitions to owned-only + vm.prank(alice); + fleet.burn(tokenId); + + // UUID owner should NOT be cleared yet (now in owned-only state) + assertEq(fleet.uuidOwner(UUID_1), alice, "UUID owner preserved in owned-only state"); + assertTrue(fleet.isOwnedOnly(UUID_1)); + + // Burn the owned-only token to fully clear ownership + uint256 ownedTokenId = uint256(uint128(UUID_1)); + vm.prank(alice); + fleet.burn(ownedTokenId); + + // NOW UUID owner should be cleared + assertEq(fleet.uuidOwner(UUID_1), address(0), "UUID owner cleared after owned-only token burned"); + assertEq(fleet.uuidTokenCount(UUID_1), 0, "Token count is 0 after all burned"); + } + + function test_uuidOwner_notClearedWhileTokensRemain() public { + // Alice registers UUID_1 in two regions (same level) + vm.prank(alice); + uint256 id1 = fleet.registerFleetLocal(UUID_1, US, ADMIN_CA, 0); + + vm.prank(alice); + uint256 id2 = fleet.registerFleetLocal(UUID_1, DE, ADMIN_CA, 0); + + assertEq(fleet.uuidTokenCount(UUID_1), 2); + + // Burn first token + vm.prank(alice); + fleet.burn(id1); + + // UUID owner should still be alice (one token remains) + assertEq(fleet.uuidOwner(UUID_1), alice, "UUID owner still alice with remaining token"); + assertEq(fleet.uuidTokenCount(UUID_1), 1, "Token count decremented to 1"); + + // Burn second token -> transitions to owned-only + vm.prank(alice); + fleet.burn(id2); + + // Still owned (in owned-only state) + assertEq(fleet.uuidOwner(UUID_1), alice, "UUID owner preserved in owned-only state"); + assertTrue(fleet.isOwnedOnly(UUID_1)); + + // Burn owned-only token to fully clear + uint256 ownedTokenId = uint256(uint128(UUID_1)); + vm.prank(alice); + fleet.burn(ownedTokenId); + + // Now UUID owner should be cleared + assertEq(fleet.uuidOwner(UUID_1), address(0), "UUID owner cleared after owned-only burned"); + assertEq(fleet.uuidTokenCount(UUID_1), 0); + } + + function test_uuidOwner_differentUuidsHaveDifferentOwners() public { + // Alice registers UUID_1 + vm.prank(alice); + fleet.registerFleetLocal(UUID_1, US, ADMIN_CA, 0); + + // Bob registers UUID_2 (different UUID, no conflict) + vm.prank(bob); + fleet.registerFleetLocal(UUID_2, US, ADMIN_CA, 0); + + assertEq(fleet.uuidOwner(UUID_1), alice); + assertEq(fleet.uuidOwner(UUID_2), bob); + } + + function test_uuidOwner_canReRegisterAfterBurningAll() public { + // Alice registers and burns UUID_1 + vm.prank(alice); + uint256 tokenId = fleet.registerFleetLocal(UUID_1, US, ADMIN_CA, 0); + vm.prank(alice); + fleet.burn(tokenId); + + // Now in owned-only state, burn that too + uint256 ownedTokenId = uint256(uint128(UUID_1)); + vm.prank(alice); + fleet.burn(ownedTokenId); + + // Bob can now register the same UUID (uuid owner was cleared) + vm.prank(bob); + uint256 newTokenId = fleet.registerFleetLocal(UUID_1, US, ADMIN_CA, 0); + + assertEq(fleet.uuidOwner(UUID_1), bob, "Bob is now UUID owner"); + assertEq(fleet.uuidTokenCount(UUID_1), 1); + assertEq(fleet.ownerOf(newTokenId), bob); + } + + function test_uuidOwner_transferDoesNotChangeUuidOwner() public { + // Alice registers UUID_1 + vm.prank(alice); + uint256 tokenId = fleet.registerFleetLocal(UUID_1, US, ADMIN_CA, 0); + + assertEq(fleet.uuidOwner(UUID_1), alice); + + // Alice transfers to Bob + vm.prank(alice); + fleet.transferFrom(alice, bob, tokenId); + + // Token owner changed but UUID owner did not + assertEq(fleet.ownerOf(tokenId), bob); + assertEq(fleet.uuidOwner(UUID_1), alice, "UUID owner still alice after transfer"); + } + + function test_RevertIf_transferRecipientTriesToRegisterSameUuid() public { + // Alice registers UUID_1 + vm.prank(alice); + uint256 tokenId = fleet.registerFleetLocal(UUID_1, US, ADMIN_CA, 0); + + // Alice transfers to Bob + vm.prank(alice); + fleet.transferFrom(alice, bob, tokenId); + + // Bob now owns tokenId, but cannot register NEW tokens for UUID_1 + vm.prank(bob); + vm.expectRevert(FleetIdentity.NotOperator.selector); + fleet.registerFleetLocal(UUID_1, DE, ADMIN_CA, 0); + } + + function test_uuidOwner_originalOwnerCanStillRegisterAfterTransfer() public { + // Alice registers UUID_1 in one region + vm.prank(alice); + uint256 tokenId = fleet.registerFleetLocal(UUID_1, US, ADMIN_CA, 0); + + // Alice transfers to Bob + vm.prank(alice); + fleet.transferFrom(alice, bob, tokenId); + + // Alice can still register UUID_1 in new regions (she's still uuidOwner, same level) + vm.prank(alice); + uint256 newTokenId = fleet.registerFleetLocal(UUID_1, DE, ADMIN_CA, 0); + + assertEq(fleet.ownerOf(newTokenId), alice); + assertEq(fleet.uuidTokenCount(UUID_1), 2); + } + + function testFuzz_uuidOwner_enforcedAcrossAllRegions(uint16 cc1, uint16 cc2, uint16 admin1, uint16 admin2) public { + cc1 = uint16(bound(cc1, 1, 999)); + cc2 = uint16(bound(cc2, 1, 999)); + admin1 = uint16(bound(admin1, 1, 255)); + admin2 = uint16(bound(admin2, 1, 255)); + + // Alice registers first + vm.prank(alice); + fleet.registerFleetLocal(UUID_1, cc1, admin1, 0); + + // Bob cannot register same UUID anywhere + vm.prank(bob); + vm.expectRevert(FleetIdentity.NotOperator.selector); + fleet.registerFleetLocal(UUID_1, cc2, admin2, 0); + + vm.prank(bob); + vm.expectRevert(FleetIdentity.NotOperator.selector); + fleet.registerFleetCountry(UUID_1, cc2, 0); + } + + function testFuzz_uuidOwner_multiRegionTokenCount(uint8 regionCount) public { + regionCount = uint8(bound(regionCount, 1, 10)); + + for (uint8 i = 0; i < regionCount; i++) { + uint16 cc = uint16(1 + i); + vm.prank(alice); + fleet.registerFleetCountry(UUID_1, cc, 0); + } + + assertEq(fleet.uuidTokenCount(UUID_1), regionCount); + assertEq(fleet.uuidOwner(UUID_1), alice); + } + + function testFuzz_uuidOwner_partialBurnPreservesOwnership(uint8 burnCount) public { + uint8 totalTokens = 5; + burnCount = uint8(bound(burnCount, 1, totalTokens - 1)); + + // Register tokens + uint256[] memory tokenIds = new uint256[](totalTokens); + for (uint8 i = 0; i < totalTokens; i++) { + uint16 cc = uint16(1 + i); + vm.prank(alice); + tokenIds[i] = fleet.registerFleetCountry(UUID_1, cc, 0); + } + + assertEq(fleet.uuidTokenCount(UUID_1), totalTokens); + + // Burn some tokens + for (uint8 i = 0; i < burnCount; i++) { + vm.prank(alice); + fleet.burn(tokenIds[i]); + } + + // Owner still alice, count decreased + assertEq(fleet.uuidOwner(UUID_1), alice); + assertEq(fleet.uuidTokenCount(UUID_1), totalTokens - burnCount); + } + + // ══════════════════════════════════════════════ + // UUID Level Enforcement Tests + // ══════════════════════════════════════════════ + + function test_uuidLevel_setOnFirstRegistration_local() public { + assertEq(uint8(fleet.uuidLevel(UUID_1)), 0, "No level before registration"); + + vm.prank(alice); + fleet.registerFleetLocal(UUID_1, US, ADMIN_CA, 0); + + assertEq(uint8(fleet.uuidLevel(UUID_1)), 2, "Level is 2 (local) after local registration"); + } + + function test_uuidLevel_setOnFirstRegistration_country() public { + assertEq(uint8(fleet.uuidLevel(UUID_1)), 0, "No level before registration"); + + vm.prank(alice); + fleet.registerFleetCountry(UUID_1, US, 0); + + assertEq(uint8(fleet.uuidLevel(UUID_1)), 3, "Level is 3 (country) after country registration"); + } + + function test_RevertIf_crossLevelRegistration_localThenCountry() public { + // Alice registers UUID_1 at local level + vm.prank(alice); + fleet.registerFleetLocal(UUID_1, US, ADMIN_CA, 0); + + // Alice tries to register same UUID at country level → revert + vm.prank(alice); + vm.expectRevert(FleetIdentity.UuidLevelMismatch.selector); + fleet.registerFleetCountry(UUID_1, DE, 0); + } + + function test_RevertIf_crossLevelRegistration_countryThenLocal() public { + // Alice registers UUID_1 at country level + vm.prank(alice); + fleet.registerFleetCountry(UUID_1, US, 0); + + // Alice tries to register same UUID at local level → revert + vm.prank(alice); + vm.expectRevert(FleetIdentity.UuidLevelMismatch.selector); + fleet.registerFleetLocal(UUID_1, DE, ADMIN_CA, 0); + } + + function test_uuidLevel_clearedOnLastTokenBurn() public { + vm.prank(alice); + uint256 tokenId = fleet.registerFleetLocal(UUID_1, US, ADMIN_CA, 0); + + assertEq(uint8(fleet.uuidLevel(UUID_1)), 2); + + // Burn -> transitions to owned-only (level = 1) + vm.prank(alice); + fleet.burn(tokenId); + + assertEq(uint8(fleet.uuidLevel(UUID_1)), 1, "Level is Owned after burning last registered token"); + + // Burn owned-only token to fully clear + uint256 ownedTokenId = uint256(uint128(UUID_1)); + vm.prank(alice); + fleet.burn(ownedTokenId); + + assertEq(uint8(fleet.uuidLevel(UUID_1)), 0, "Level cleared after owned-only token burned"); + } + + function test_uuidLevel_notClearedWhileTokensRemain() public { + vm.prank(alice); + uint256 id1 = fleet.registerFleetLocal(UUID_1, US, ADMIN_CA, 0); + + vm.prank(alice); + fleet.registerFleetLocal(UUID_1, DE, ADMIN_CA, 0); + + assertEq(uint8(fleet.uuidLevel(UUID_1)), 2); + + vm.prank(alice); + fleet.burn(id1); + + assertEq(uint8(fleet.uuidLevel(UUID_1)), 2, "Level preserved while tokens remain"); + } + + function test_uuidLevel_canChangeLevelAfterBurningAll() public { + // Register as local + vm.prank(alice); + uint256 tokenId = fleet.registerFleetLocal(UUID_1, US, ADMIN_CA, 0); + assertEq(uint8(fleet.uuidLevel(UUID_1)), 2); + + // Burn + vm.prank(alice); + fleet.burn(tokenId); + + // Now can register as country + vm.prank(alice); + fleet.registerFleetCountry(UUID_1, US, 0); + assertEq(uint8(fleet.uuidLevel(UUID_1)), 3); + } + + // ══════════════════════════════════════════════ + // Owned-Only Mode Tests + // ══════════════════════════════════════════════ + + function test_claimUuid_basic() public { + uint256 aliceBalanceBefore = bondToken.balanceOf(alice); + + vm.prank(alice); + uint256 tokenId = fleet.claimUuid(UUID_1, address(0)); + + // Token minted + assertEq(fleet.ownerOf(tokenId), alice); + assertEq(fleet.tokenUuid(tokenId), UUID_1); + assertEq(fleet.tokenRegion(tokenId), 0); // OWNED_REGION_KEY + + // UUID ownership set + assertEq(fleet.uuidOwner(UUID_1), alice); + assertEq(fleet.uuidTokenCount(UUID_1), 1); + assertTrue(fleet.isOwnedOnly(UUID_1)); + assertEq(uint8(fleet.uuidLevel(UUID_1)), 1); // Owned + + // Bond pulled + assertEq(aliceBalanceBefore - bondToken.balanceOf(alice), BASE_BOND); + + // bonds() returns BASE_BOND for owned-only + assertEq(fleet.bonds(tokenId), BASE_BOND); + } + + function test_RevertIf_claimUuid_alreadyOwned() public { + vm.prank(alice); + fleet.claimUuid(UUID_1, address(0)); + + vm.prank(bob); + vm.expectRevert(FleetIdentity.UuidAlreadyOwned.selector); + fleet.claimUuid(UUID_1, address(0)); + } + + function test_RevertIf_claimUuid_alreadyRegistered() public { + vm.prank(alice); + fleet.registerFleetLocal(UUID_1, US, ADMIN_CA, 0); + + vm.prank(bob); + vm.expectRevert(FleetIdentity.UuidAlreadyOwned.selector); + fleet.claimUuid(UUID_1, address(0)); + } + + function test_RevertIf_claimUuid_invalidUuid() public { + vm.prank(alice); + vm.expectRevert(FleetIdentity.InvalidUUID.selector); + fleet.claimUuid(bytes16(0), address(0)); + } + + function test_registerFromOwned_local() public { + // First claim + vm.prank(alice); + uint256 ownedTokenId = fleet.claimUuid(UUID_1, address(0)); + + uint256 aliceBalanceBefore = bondToken.balanceOf(alice); + + // Register from owned state - operator (alice) pays tierBond + vm.prank(alice); + uint256 tokenId = fleet.registerFleetLocal(UUID_1, US, ADMIN_CA, 0); + + // Old owned token burned + vm.expectRevert(); + fleet.ownerOf(ownedTokenId); + + // New token exists + assertEq(fleet.ownerOf(tokenId), alice); + assertEq(fleet.tokenRegion(tokenId), _regionUSCA()); + assertEq(fleet.fleetTier(tokenId), 0); + + // UUID state updated + assertEq(fleet.uuidOwner(UUID_1), alice); + assertEq(fleet.uuidTokenCount(UUID_1), 1); // still 1 + assertFalse(fleet.isOwnedOnly(UUID_1)); + assertEq(uint8(fleet.uuidLevel(UUID_1)), 2); // Local + + // Operator pays tierBond (owner already paid BASE_BOND via claim) + assertEq(aliceBalanceBefore - bondToken.balanceOf(alice), fleet.tierBond(0, false)); + } + + function test_registerFromOwned_country() public { + vm.prank(alice); + fleet.claimUuid(UUID_1, address(0)); + + uint256 aliceBalanceBefore = bondToken.balanceOf(alice); + + vm.prank(alice); + uint256 tokenId = fleet.registerFleetCountry(UUID_1, US, 0); + + assertEq(fleet.ownerOf(tokenId), alice); + assertEq(fleet.tokenRegion(tokenId), uint32(US)); + assertEq(uint8(fleet.uuidLevel(UUID_1)), 3); // Country + + // Operator pays tierBond for country tier 0 = 16*BASE_BOND + assertEq(aliceBalanceBefore - bondToken.balanceOf(alice), fleet.tierBond(0, true)); + } + + function test_registerFromOwned_higherTier() public { + vm.prank(alice); + fleet.claimUuid(UUID_1, address(0)); + + uint256 aliceBalanceBefore = bondToken.balanceOf(alice); + + // Register at tier 0 local - operator pays tierBond(0, false) = BASE_BOND + vm.prank(alice); + uint256 tokenId = fleet.registerFleetLocal(UUID_1, US, ADMIN_CA, 0); + assertEq(aliceBalanceBefore - bondToken.balanceOf(alice), fleet.tierBond(0, false)); + + // Promote to tier 2: additional bond = tierBond(2) - tierBond(0) = 4*BASE_BOND - BASE_BOND = 3*BASE_BOND + uint256 balBeforePromote = bondToken.balanceOf(alice); + vm.prank(alice); + fleet.reassignTier(tokenId, 2); + assertEq(balBeforePromote - bondToken.balanceOf(alice), 3 * BASE_BOND); + } + + function test_burn_lastRegisteredToken_transitionsToOwned() public { + vm.prank(alice); + uint256 tokenId = fleet.registerFleetLocal(UUID_1, US, ADMIN_CA, 0); + + uint256 aliceBalanceBefore = bondToken.balanceOf(alice); + + vm.prank(alice); + fleet.burn(tokenId); + + // Old token burned + vm.expectRevert(); + fleet.ownerOf(tokenId); + + // New owned-only token exists + uint256 ownedTokenId = uint256(uint128(UUID_1)); + assertEq(fleet.ownerOf(ownedTokenId), alice); + assertEq(fleet.tokenRegion(ownedTokenId), 0); + + // UUID state updated to Owned + assertTrue(fleet.isOwnedOnly(UUID_1)); + assertEq(uint8(fleet.uuidLevel(UUID_1)), 1); // Owned + + // Operator (alice) gets tierBond refunded + assertEq(bondToken.balanceOf(alice) - aliceBalanceBefore, fleet.tierBond(0, false)); + } + + function test_burn_lastRegisteredToken_withHighTierRefund() public { + // Register at tier 0, then promote to tier 2 + vm.prank(alice); + uint256 tokenId = fleet.registerFleetLocal(UUID_1, US, ADMIN_CA, 0); + vm.prank(alice); + fleet.reassignTier(tokenId, 2); + + uint256 aliceBalanceBefore = bondToken.balanceOf(alice); + + vm.prank(alice); + fleet.burn(tokenId); + + // Operator (alice) gets full tierBond(2, false) refunded + assertEq(bondToken.balanceOf(alice) - aliceBalanceBefore, fleet.tierBond(2, false)); + + // Transitioned to owned-only + assertTrue(fleet.isOwnedOnly(UUID_1)); + } + + function test_burn_lastCountryToken_transitionsToOwned() public { + // Register country tier 0 + vm.prank(alice); + uint256 tokenId = fleet.registerFleetCountry(UUID_1, US, 0); + + uint256 aliceBalanceBefore = bondToken.balanceOf(alice); + + vm.prank(alice); + fleet.burn(tokenId); + + // Operator (alice) gets full tierBond(0, true) refunded + assertEq(bondToken.balanceOf(alice) - aliceBalanceBefore, fleet.tierBond(0, true)); + + // Level changed to Owned + assertEq(uint8(fleet.uuidLevel(UUID_1)), 1); + assertTrue(fleet.isOwnedOnly(UUID_1)); + } + + function test_burn_multiRegion_doesNotTransitionUntilLastToken() public { + // Register in two regions + vm.prank(alice); + uint256 id1 = fleet.registerFleetLocal(UUID_1, US, ADMIN_CA, 0); + vm.prank(alice); + uint256 id2 = fleet.registerFleetLocal(UUID_1, DE, ADMIN_CA, 0); + + // Burn first token - should NOT transition to owned + vm.prank(alice); + fleet.burn(id1); + + // Still registered level, not owned + assertFalse(fleet.isOwnedOnly(UUID_1)); + assertEq(fleet.uuidTokenCount(UUID_1), 1); + + // Second token still exists + assertEq(fleet.ownerOf(id2), alice); + + // Burn second token - NOW should transition to owned + vm.prank(alice); + fleet.burn(id2); + + assertTrue(fleet.isOwnedOnly(UUID_1)); + uint256 ownedTokenId = uint256(uint128(UUID_1)); + assertEq(fleet.ownerOf(ownedTokenId), alice); + } + + function test_burn_ownedOnly_clearsUuid() public { + vm.prank(alice); + uint256 tokenId = fleet.claimUuid(UUID_1, address(0)); + + uint256 aliceBalanceBefore = bondToken.balanceOf(alice); + + vm.prank(alice); + fleet.burn(tokenId); + + // Token burned + vm.expectRevert(); + fleet.ownerOf(tokenId); + + // UUID cleared + assertEq(fleet.uuidOwner(UUID_1), address(0)); + assertEq(fleet.uuidTokenCount(UUID_1), 0); + assertEq(uint8(fleet.uuidLevel(UUID_1)), 0); // None + + // Refund received + assertEq(bondToken.balanceOf(alice) - aliceBalanceBefore, BASE_BOND); + } + + function test_burn_ownedOnly_afterTransfer() public { + vm.prank(alice); + uint256 tokenId = fleet.claimUuid(UUID_1, address(0)); + + // Transfer to bob + vm.prank(alice); + fleet.transferFrom(alice, bob, tokenId); + + // uuidOwner should have updated + assertEq(fleet.uuidOwner(UUID_1), bob); + + // Alice cannot burn (not token owner) + vm.prank(alice); + vm.expectRevert(FleetIdentity.NotTokenOwner.selector); + fleet.burn(tokenId); + + // Bob can burn + uint256 bobBalanceBefore = bondToken.balanceOf(bob); + vm.prank(bob); + fleet.burn(tokenId); + assertEq(bondToken.balanceOf(bob) - bobBalanceBefore, BASE_BOND); + } + + function test_RevertIf_burn_ownedOnly_notOwner() public { + vm.prank(alice); + uint256 tokenId = fleet.claimUuid(UUID_1, address(0)); + + // Bob cannot burn owned-only token (not owner) + vm.prank(bob); + vm.expectRevert(FleetIdentity.NotTokenOwner.selector); + fleet.burn(tokenId); + } + + function test_ownedOnly_transfer_updatesUuidOwner() public { + vm.prank(alice); + uint256 tokenId = fleet.claimUuid(UUID_1, address(0)); + + assertEq(fleet.uuidOwner(UUID_1), alice); + + vm.prank(alice); + fleet.transferFrom(alice, bob, tokenId); + + // uuidOwner updated on transfer for owned-only tokens + assertEq(fleet.uuidOwner(UUID_1), bob); + assertEq(fleet.ownerOf(tokenId), bob); + } + + function test_ownedOnly_notInBundle() public { + // Claim some UUIDs as owned-only + vm.prank(alice); + fleet.claimUuid(UUID_1, address(0)); + vm.prank(alice); + fleet.claimUuid(UUID_2, address(0)); + + // Bundle should be empty + (bytes16[] memory uuids, uint256 count) = fleet.buildHighestBondedUuidBundle(US, ADMIN_CA); + assertEq(count, 0); + + // Now register one + vm.prank(alice); + fleet.registerFleetLocal(UUID_1, US, ADMIN_CA, 0); + + // Bundle should contain only the registered one + (uuids, count) = fleet.buildHighestBondedUuidBundle(US, ADMIN_CA); + assertEq(count, 1); + assertEq(uuids[0], UUID_1); + } + + function test_burn_ownedOnly() public { + vm.prank(alice); + uint256 tokenId = fleet.claimUuid(UUID_1, address(0)); + + uint256 aliceBalanceBefore = bondToken.balanceOf(alice); + + vm.prank(alice); + fleet.burn(tokenId); + + // Token burned + vm.expectRevert(); + fleet.ownerOf(tokenId); + + // UUID cleared + assertEq(fleet.uuidOwner(UUID_1), address(0)); + + // Refund received + assertEq(bondToken.balanceOf(alice) - aliceBalanceBefore, BASE_BOND); + } + + function test_ownedOnly_canReRegisterAfterBurn() public { + vm.prank(alice); + uint256 tokenId = fleet.claimUuid(UUID_1, address(0)); + + vm.prank(alice); + fleet.burn(tokenId); + + // Bob can now claim or register + vm.prank(bob); + uint256 newTokenId = fleet.registerFleetLocal(UUID_1, US, ADMIN_CA, 0); + + assertEq(fleet.ownerOf(newTokenId), bob); + assertEq(fleet.uuidOwner(UUID_1), bob); + } + + function test_migration_viaBurnAndReregister() public { + // This test shows the migration pattern using burn + + // Register local in US + vm.prank(alice); + uint256 oldTokenId = fleet.registerFleetLocal(UUID_1, US, ADMIN_CA, 0); + + uint256 aliceBalanceAfterRegister = bondToken.balanceOf(alice); + + // Burn registered token -> transitions to owned-only, refunds tierBond(0, false) + vm.prank(alice); + fleet.burn(oldTokenId); + + // Now in owned-only state, re-register in DE as country + // Pays tierBond(0, true) = 16*BASE_BOND for country registration + vm.prank(alice); + uint256 newTokenId = fleet.registerFleetCountry(UUID_1, DE, 0); + + assertEq(fleet.ownerOf(newTokenId), alice); + assertEq(fleet.tokenRegion(newTokenId), uint32(DE)); + assertEq(uint8(fleet.uuidLevel(UUID_1)), 3); // Country + + // Net bond change: tierBond(0, true) - tierBond(0, false) = 16*BASE_BOND - BASE_BOND = 15*BASE_BOND + assertEq(aliceBalanceAfterRegister - bondToken.balanceOf(alice), 15 * BASE_BOND); + } + + function testFuzz_tierBond_geometric(uint256 tier) public view { + tier = bound(tier, 0, 10); + uint256 expected = BASE_BOND; + for (uint256 i = 0; i < tier; i++) { + expected *= 2; + } + // Local regions get 1× multiplier + assertEq(fleet.tierBond(tier, false), expected); + // Country regions get 16x multiplier + assertEq(fleet.tierBond(tier, true), expected * fleet.COUNTRY_BOND_MULTIPLIER()); + } + + function testFuzz_perRegionTiers_newRegionAlwaysStartsAtTier0(uint16 cc) public { + cc = uint16(bound(cc, 1, 999)); + vm.assume(cc != US); // Skip US since we fill it below + + // Fill one country with 8 fleets + _registerNCountry(alice, US, 8, 0); + uint256 cap = fleet.TIER_CAPACITY(); + uint256 expectedTiers = (8 + cap - 1) / cap; // ceiling division + assertEq(fleet.regionTierCount(_regionUS()), expectedTiers); + + // New country should start at tier 0 regardless of other regions + vm.prank(bob); + uint256 tokenId = fleet.registerFleetCountry(_uuid(999), cc, 0); + assertEq(fleet.fleetTier(tokenId), 0); + assertEq(fleet.bonds(tokenId), BASE_BOND * fleet.COUNTRY_BOND_MULTIPLIER()); // Country gets 16x multiplier + } + + function testFuzz_tierAssignment_autoFillsSequentiallyPerRegion(uint8 count) public { + count = uint8(bound(count, 1, 40)); + uint256 cap = fleet.TIER_CAPACITY(); + + for (uint256 i = 0; i < count; i++) { + uint256 expectedTier = i / cap; + vm.prank(alice); + uint256 tokenId = fleet.registerFleetLocal(_uuid(i + 300), US, ADMIN_CA, expectedTier); + + assertEq(fleet.fleetTier(tokenId), expectedTier); + } + + uint256 expectedTiers = (uint256(count) + cap - 1) / cap; + assertEq(fleet.regionTierCount(_regionUSCA()), expectedTiers); + } + + // --- Invariants --- + + function test_invariant_contractBalanceEqualsSumOfBonds() public { + vm.prank(alice); + uint256 id1 = fleet.registerFleetCountry(UUID_1, US, 0); + vm.prank(bob); + uint256 id2 = fleet.registerFleetCountry(UUID_2, DE, 0); + vm.prank(carol); + uint256 id3 = fleet.registerFleetLocal(UUID_3, US, ADMIN_CA, 0); + + // Contract balance = 3 * BASE_BOND (per UUID) + sum of tierBonds + uint256 expected = 3 * BASE_BOND + fleet.bonds(id1) + fleet.bonds(id2) + fleet.bonds(id3); + assertEq(bondToken.balanceOf(address(fleet)), expected); + + // Burn id1 -> transitions to owned-only (BASE_BOND stays, tierBond refunded) + vm.prank(alice); + fleet.burn(id1); + + // After burn: 2 registered UUIDs + 1 owned-only UUID + // = 2 * BASE_BOND (registered) + 2 * tierBond (registered) + BASE_BOND (owned-only) + uint256 expectedAfterBurn = 3 * BASE_BOND + fleet.bonds(id2) + fleet.bonds(id3); + assertEq(bondToken.balanceOf(address(fleet)), expectedAfterBurn); + } + + function test_invariant_contractBalanceAfterReassignTierBurn() public { + vm.prank(alice); + uint256 id1 = fleet.registerFleetCountry(UUID_1, US, 0); + vm.prank(bob); + uint256 id2 = fleet.registerFleetLocal(UUID_2, US, ADMIN_CA, 0); + vm.prank(carol); + uint256 id3 = fleet.registerFleetLocal(UUID_3, DE, ADMIN_NY, 0); + + vm.prank(alice); + fleet.reassignTier(id1, 3); + + vm.prank(alice); + fleet.reassignTier(id1, 1); + + // Contract balance = 3 * BASE_BOND + sum of tierBonds + uint256 expected = 3 * BASE_BOND + fleet.bonds(id1) + fleet.bonds(id2) + fleet.bonds(id3); + assertEq(bondToken.balanceOf(address(fleet)), expected); + + // Burn all registered tokens (each transitions to owned-only) + vm.prank(alice); + fleet.burn(id1); + vm.prank(bob); + fleet.burn(id2); + vm.prank(carol); + fleet.burn(id3); + + // Now have 3 owned-only tokens, each with BASE_BOND + assertEq(bondToken.balanceOf(address(fleet)), 3 * BASE_BOND); + + // Burn all owned-only tokens + vm.prank(alice); + fleet.burn(uint256(uint128(UUID_1))); + vm.prank(bob); + fleet.burn(uint256(uint128(UUID_2))); + vm.prank(carol); + fleet.burn(uint256(uint128(UUID_3))); + + assertEq(bondToken.balanceOf(address(fleet)), 0); + } + + // --- countryInclusionHint --- + + function test_countryInclusionHint_emptyReturnsZero() public view { + (uint256 tier, uint256 bond) = fleet.countryInclusionHint(US); + assertEq(tier, 0); + assertEq(bond, BASE_BOND * fleet.COUNTRY_BOND_MULTIPLIER()); // Country pays 16x multiplier + } + + function test_countryInclusionHint_onlyCountryFleets() public { + _registerNCountryAt(alice, US, fleet.TIER_CAPACITY(), 1000, 0); // fills tier 0 + vm.prank(bob); + fleet.registerFleetCountry(_uuid(9000), US, 1); // tier 1 + + // Tier 0 is full → cheapest inclusion = tier 1. + (uint256 tier, uint256 bond) = fleet.countryInclusionHint(US); + assertEq(tier, 1); + assertEq(bond, BASE_BOND * fleet.COUNTRY_BOND_MULTIPLIER() * 2); // Country pays 16x multiplier, tier 1 = 2× base + } + + function test_countryInclusionHint_adminAreaCreatesPressure() public { + // Country US: tier 0 with 1 member + vm.prank(alice); + fleet.registerFleetCountry(_uuid(1000), US, 0); + + // US-CA: push to tier 3 (1 member at tier 3) + vm.prank(bob); + fleet.registerFleetLocal(_uuid(2000), US, ADMIN_CA, 3); + + // Country fleet needs to be included in bundle(US, ADMIN_CA). + // Simulation: cursor 3→0. At cursor 3: admin=1 (fits). At cursor 0: admin=0, country=1+1=2 (fits). + // Country tier 0 with 2 members: 2 <= 20-1 = 19. Fits. + // So cheapest = 0 (tier 0 has room: 1/4). + (uint256 tier,) = fleet.countryInclusionHint(US); + assertEq(tier, 0); + } + + function test_countryInclusionHint_multipleAdminAreas_takesMax() public { + // US-CA: fill admin tier 0 + fill country tier 0 + _registerNLocalAt(alice, US, ADMIN_CA, fleet.TIER_CAPACITY(), 0, 0); + _registerNCountryAt(alice, US, fleet.TIER_CAPACITY(), 100, 0); + // US-NY: light (3 admin) + _registerNLocal(alice, US, ADMIN_NY, 3, 200); + + // Country tier 0 is full (TIER_CAPACITY members). + // Even though the bundle has room, the tier capacity is exhausted. + // So cheapest inclusion tier for a country fleet = 1. + (uint256 tier,) = fleet.countryInclusionHint(US); + assertEq(tier, 1); + } + + function test_countryInclusionHint_ignoresOtherCountries() public { + // DE admin area at tier 5 — should NOT affect US hint + vm.prank(alice); + fleet.registerFleetLocal(_uuid(1000), DE, 1, 5); + + // US-CA at tier 1 + vm.prank(bob); + fleet.registerFleetLocal(_uuid(2000), US, ADMIN_CA, 1); + + (uint256 usTier,) = fleet.countryInclusionHint(US); + // US country fleet needs inclusion in bundle(US, ADMIN_CA). + // Admin has 1 at tier 1. Country at tier 0: +1=1, fits. + assertEq(usTier, 0); + } + + function test_countryInclusionHint_afterBurn_updates() public { + vm.prank(alice); + uint256 id = fleet.registerFleetLocal(_uuid(1000), US, ADMIN_CA, 3); + + vm.prank(alice); + fleet.burn(id); + + (uint256 after_,) = fleet.countryInclusionHint(US); + assertEq(after_, 0); + } + + function test_countryInclusionHint_registrantCanActOnHint() public { + // Fill up to create pressure + _registerNLocal(alice, US, ADMIN_CA, 8, 0); + _registerNCountry(alice, US, 8, 100); + + (uint256 inclusionTier, uint256 hintBond) = fleet.countryInclusionHint(US); + + // Bob registers at country level at the hinted tier + vm.prank(bob); + fleet.registerFleetCountry(_uuid(2000), US, inclusionTier); + + uint256 tokenId = _tokenId(_uuid(2000), _regionUS()); + assertEq(fleet.fleetTier(tokenId), inclusionTier); + assertEq(fleet.bonds(tokenId), hintBond); + + // Bundle for US-CA includes Bob's fleet + (bytes16[] memory uuids, uint256 count) = fleet.buildHighestBondedUuidBundle(US, ADMIN_CA); + assertGt(count, 0); + bool foundCountry; + for (uint256 i = 0; i < count; i++) { + if (uuids[i] == _uuid(2000)) foundCountry = true; + } + assertTrue(foundCountry, "Country fleet should appear in bundle"); + } + + // --- buildHighestBondedUuidBundle (shared-cursor fair-stop) --- + + // ── Empty / Single-level basics ── + + function test_buildBundle_emptyReturnsZero() public view { + (, uint256 count) = fleet.buildHighestBondedUuidBundle(US, ADMIN_CA); + assertEq(count, 0); + } + + function test_RevertIf_buildBundle_adminCodeZero() public { + vm.prank(alice); + fleet.registerFleetCountry(UUID_1, US, 0); + + vm.expectRevert(FleetIdentity.AdminAreaRequired.selector); + fleet.buildHighestBondedUuidBundle(US, 0); + } + + function test_buildBundle_singleCountry() public { + vm.prank(alice); + fleet.registerFleetCountry(UUID_1, US, 0); + + (bytes16[] memory uuids, uint256 count) = fleet.buildHighestBondedUuidBundle(US, ADMIN_CA); + assertEq(count, 1); + assertEq(uuids[0], UUID_1); + } + + function test_buildBundle_singleLocal() public { + vm.prank(alice); + fleet.registerFleetLocal(UUID_1, US, ADMIN_CA, 0); + + (bytes16[] memory uuids, uint256 count) = fleet.buildHighestBondedUuidBundle(US, ADMIN_CA); + assertEq(count, 1); + assertEq(uuids[0], UUID_1); + } + + // ── Same cursor, both levels at tier 0 ── + + function test_buildBundle_bothLevelsTied_levelPriorityOrder() public { + // Both at tier 0 → shared cursor 0 → level priority: local, country + vm.prank(alice); + fleet.registerFleetLocal(UUID_2, US, ADMIN_CA, 0); + vm.prank(alice); + fleet.registerFleetCountry(UUID_1, US, 0); + + (bytes16[] memory uuids, uint256 count) = fleet.buildHighestBondedUuidBundle(US, ADMIN_CA); + assertEq(count, 2); + assertEq(uuids[0], UUID_2); // local first + assertEq(uuids[1], UUID_1); // country second + } + + function test_buildBundle_2LevelsTier0_fullCapacity() public { + // 4 local + 4 country at tier 0 = 8 + // Bundle fits all since max is 20 + _registerNLocalAt(alice, US, ADMIN_CA, 4, 1000, 0); + _registerNCountryAt(alice, US, 4, 2000, 0); + + (, uint256 count) = fleet.buildHighestBondedUuidBundle(US, ADMIN_CA); + assertEq(count, 8); + } + + function test_buildBundle_2LevelsTier0_partialFill() public { + // 3 local + 2 country = 5 + _registerNLocalAt(alice, US, ADMIN_CA, 3, 1000, 0); + _registerNCountryAt(alice, US, 2, 2000, 0); + + (, uint256 count) = fleet.buildHighestBondedUuidBundle(US, ADMIN_CA); + assertEq(count, 5); + } + + // ── Bond priority: higher tier index = higher bond = comes first ── + + function test_buildBundle_higherBondFirst() public { + // Country: promote to tier 2 (bond=8*4*BASE) + vm.prank(alice); + uint256 usId = fleet.registerFleetCountry(UUID_1, US, 0); + vm.prank(alice); + fleet.reassignTier(usId, 2); + // Local: tier 0 (bond=BASE) + vm.prank(alice); + fleet.registerFleetLocal(UUID_2, US, ADMIN_CA, 0); + + (bytes16[] memory uuids, uint256 count) = fleet.buildHighestBondedUuidBundle(US, ADMIN_CA); + assertEq(count, 2); + assertEq(uuids[0], UUID_1); // highest bond first (country tier 2) + assertEq(uuids[1], UUID_2); // local tier 0 + } + + function test_buildBundle_multiTierDescendingBond() public { + // Local tier 2 (bond=4*BASE) + vm.prank(alice); + uint256 id1 = fleet.registerFleetLocal(UUID_1, US, ADMIN_CA, 0); + vm.prank(alice); + fleet.reassignTier(id1, 2); + + // Country tier 1 (bond=8*2*BASE) + vm.prank(alice); + uint256 id2 = fleet.registerFleetCountry(UUID_2, US, 0); + vm.prank(alice); + fleet.reassignTier(id2, 1); + + // Local tier 0 (bond=BASE) + vm.prank(alice); + fleet.registerFleetLocal(UUID_3, US, ADMIN_CA, 0); + + (bytes16[] memory uuids, uint256 count) = fleet.buildHighestBondedUuidBundle(US, ADMIN_CA); + assertEq(count, 3); + assertEq(uuids[0], UUID_1); // local tier 2: bond=4*BASE + assertEq(uuids[1], UUID_2); // country tier 1: bond=16*BASE (but added after local at cursor) + } + + function test_buildBundle_multiTierMultiLevel_correctOrder() public { + // Admin: tier 0 (4 members) + tier 1 (1 member) + _registerNLocalAt(alice, US, ADMIN_CA, 4, 8000, 0); + vm.prank(alice); + fleet.registerFleetLocal(_uuid(8100), US, ADMIN_CA, 1); + + // Country: promote to tier 1 (bond=8*2*BASE) + vm.prank(alice); + uint256 countryId = fleet.registerFleetCountry(_uuid(8200), US, 0); + vm.prank(alice); + fleet.reassignTier(countryId, 1); + + // Country: promote to tier 2 (bond=8*4*BASE) + vm.prank(alice); + uint256 country2Id = fleet.registerFleetCountry(_uuid(8300), US, 0); + vm.prank(alice); + fleet.reassignTier(country2Id, 2); + + (bytes16[] memory uuids, uint256 count) = fleet.buildHighestBondedUuidBundle(US, ADMIN_CA); + // Cursor=2: country(1)→include. Count=1. + // Cursor=1: local(1)+country(1)→include. Count=3. + // Cursor=0: local(4)→include. Count=7. + assertEq(count, 7); + assertEq(uuids[0], fleet.tokenUuid(country2Id)); // tier 2 first + } + + // ── All-or-nothing ── + + function test_buildBundle_allOrNothing_tierSkippedWhenDoesNotFit() public { + // Fill room so that at a cursor position a tier can't fit. + // Admin tier 1: 4 members + for (uint256 i = 0; i < 4; i++) { + vm.prank(alice); + fleet.registerFleetLocal(_uuid(5100 + i), US, ADMIN_CA, 1); + } + // Country tier 1: 4 members + for (uint256 i = 0; i < 4; i++) { + vm.prank(alice); + fleet.registerFleetCountry(_uuid(6100 + i), US, 1); + } + + // Tier 0: local(4), country(3) + _registerNLocalAt(alice, US, ADMIN_CA, 4, 5000, 0); + _registerNCountryAt(alice, US, 3, 6000, 0); + + (, uint256 count) = fleet.buildHighestBondedUuidBundle(US, ADMIN_CA); + // Cursor=1: local(4)+country(4)=8. Count=8, room=12. + // Cursor=0: local(4)≤12→include[count=12,room=8]. country(3)≤8→include[count=15,room=5]. + assertEq(count, 15); + } + + function test_buildBundle_allOrNothing_noPartialCollection() public { + // Room=3, tier has 5 members → some members skipped. + // Local tier 1: 4 members + for (uint256 i = 0; i < 4; i++) { + vm.prank(alice); + fleet.registerFleetLocal(_uuid(2000 + i), US, ADMIN_CA, 1); + } + // Country tier 1: 4 members + for (uint256 i = 0; i < 4; i++) { + vm.prank(alice); + fleet.registerFleetCountry(_uuid(3000 + i), US, 1); + } + + (, uint256 count) = fleet.buildHighestBondedUuidBundle(US, ADMIN_CA); + // Cursor=1: local(4)+country(4)=8. Count=8. + // Cursor=0: all empty at tier 0. Done. + assertEq(count, 8); + } + + function test_buildBundle_partialInclusion_fillsRemainingSlots() public { + uint256 cap = fleet.TIER_CAPACITY(); + // With partial inclusion: bundle fills remaining slots. + // Country tier 0: cap members + _registerNCountryAt(alice, US, cap, 0, 0); + + // Local: cap at tier 0 + cap at tier 1 + _registerNLocalAt(alice, US, ADMIN_CA, cap, 5000, 0); + for (uint256 i = 0; i < cap; i++) { + vm.prank(alice); + fleet.registerFleetLocal(_uuid(5100 + i), US, ADMIN_CA, 1); + } + + (bytes16[] memory uuids, uint256 count) = fleet.buildHighestBondedUuidBundle(US, ADMIN_CA); + // Tier 1: local=cap. Tier 0: local=cap + country=cap. + // Total = 3*cap, capped at MAX_BONDED_UUID_BUNDLE_SIZE. + uint256 total = 3 * cap; + uint256 expectedCount = total > fleet.MAX_BONDED_UUID_BUNDLE_SIZE() ? fleet.MAX_BONDED_UUID_BUNDLE_SIZE() : total; + assertEq(count, expectedCount); + + // Verify country UUIDs ARE in the result (if bundle has room) + uint256 countryCount; + for (uint256 i = 0; i < count; i++) { + uint256 tokenId = _findTokenId(uuids[i], US, ADMIN_CA); + uint32 region = fleet.tokenRegion(tokenId); + if (region == _regionUS()) countryCount++; + } + // With cap=10, bundle=20: tier 1 local (10) + tier 0 local (10) = 20, no room for country + // With cap=4, bundle=20: tier 1 local (4) + tier 0 local (4) + country (4) = 12 + uint256 localSlots = 2 * cap; // tier 0 and tier 1 locals + uint256 remainingRoom = fleet.MAX_BONDED_UUID_BUNDLE_SIZE() > localSlots ? + fleet.MAX_BONDED_UUID_BUNDLE_SIZE() - localSlots : 0; + uint256 expectedCountry = remainingRoom > cap ? cap : remainingRoom; + assertEq(countryCount, expectedCountry, "country members included based on remaining room"); + } + + // ── Partial inclusion (replaces all-or-nothing + fair-stop) ── + + function test_buildBundle_partialInclusion_fillsBundleCompletely() public { + // With partial inclusion, we fill the bundle completely by including + // as many members as fit, in array order. + + // Consume 6 slots at tier 1. + for (uint256 i = 0; i < 3; i++) { + vm.prank(alice); + fleet.registerFleetLocal(_uuid(1000 + i), US, ADMIN_CA, 1); + } + for (uint256 i = 0; i < 3; i++) { + vm.prank(alice); + fleet.registerFleetCountry(_uuid(2000 + i), US, 1); + } + + // Tier 0: full capacities (TIER_CAPACITY = 4). + _registerNLocalAt(alice, US, ADMIN_CA, 4, 3000, 0); + _registerNCountryAt(alice, US, 4, 4000, 0); + + (, uint256 count) = fleet.buildHighestBondedUuidBundle(US, ADMIN_CA); + // Cursor=1: local(3)+country(3)=6. Count=6, room=14. + // Cursor=0: local(4)≤14→include 4[count=10,room=10]. + // country(4)≤10→include 4[count=14,room=6]. + assertEq(count, 14); + } + + function test_buildBundle_partialFill_localAndCountry() public { + // Two local tiers consume 8 slots, leaving 12 for cursor=0. + // At cursor=0: local(4) fits. country(4) included. + + for (uint256 i = 0; i < 4; i++) { + vm.prank(alice); + fleet.registerFleetLocal(_uuid(1000 + i), US, ADMIN_CA, 1); + } + for (uint256 i = 0; i < 4; i++) { + vm.prank(alice); + fleet.registerFleetLocal(_uuid(2000 + i), US, ADMIN_CA, 2); + } + + // Tier 0: 4 local + 4 country (TIER_CAPACITY = 4) + _registerNLocalAt(alice, US, ADMIN_CA, 4, 3000, 0); + _registerNCountryAt(alice, US, 4, 4000, 0); + + (, uint256 count) = fleet.buildHighestBondedUuidBundle(US, ADMIN_CA); + // Cursor=2: local(4)→include. Count=4. + // Cursor=1: local(4)→include. Count=8, room=12. + // Cursor=0: local(4)≤12→include[count=12,room=8]. country(4)≤8→include[count=16,room=4]. + assertEq(count, 16); + } + + function test_buildBundle_partialInclusion_allLevelsPartiallyIncluded() public { + // With partial inclusion, both levels get included partially if needed. + + // Consume 8 slots at tier 1. + for (uint256 i = 0; i < 4; i++) { + vm.prank(alice); + fleet.registerFleetLocal(_uuid(1000 + i), US, ADMIN_CA, 1); + } + for (uint256 i = 0; i < 4; i++) { + vm.prank(alice); + fleet.registerFleetCountry(_uuid(2000 + i), US, 1); + } + + // Tier 0: local=4, country=4 (TIER_CAPACITY = 4) + _registerNLocalAt(alice, US, ADMIN_CA, 4, 3000, 0); + _registerNCountryAt(alice, US, 4, 4000, 0); + + (bytes16[] memory uuids, uint256 count) = fleet.buildHighestBondedUuidBundle(US, ADMIN_CA); + // Cursor=1: local(4)+country(4)=8. Count=8, room=12. + // Cursor=0: local(4)≤12→include 4[count=12,room=8]. + // country(4)≤8→include 4[count=16]. + assertEq(count, 16); + + // Verify local tier 0 is present + bool foundLocal = false; + for (uint256 i = 0; i < count; i++) { + if (uuids[i] == _uuid(3000)) foundLocal = true; + } + assertTrue(foundLocal, "local tier 0 should be included"); + + // Count how many country tier 0 members are included + uint256 countryT0Count; + for (uint256 i = 0; i < count; i++) { + uint256 tokenId = _findTokenId(uuids[i], US, ADMIN_CA); + if (fleet.tokenRegion(tokenId) == _regionUS() && fleet.fleetTier(tokenId) == 0) countryT0Count++; + } + assertEq(countryT0Count, 4, "4 country tier 0 members included"); + } + + function test_buildBundle_doesNotDescendAfterBundleFull() public { + // When cursor=1 fills bundle, cursor=0 tiers are NOT included. + + // Tier 1: local(4) + country(4) + more local(4) + more country(4) = 16 + for (uint256 i = 0; i < 4; i++) { + vm.prank(alice); + fleet.registerFleetLocal(_uuid(1000 + i), US, ADMIN_CA, 1); + } + for (uint256 i = 0; i < 4; i++) { + vm.prank(alice); + fleet.registerFleetCountry(_uuid(2000 + i), US, 1); + } + for (uint256 i = 0; i < 4; i++) { + vm.prank(alice); + fleet.registerFleetLocal(_uuid(3000 + i), US, ADMIN_CA, 2); + } + + // Tier 0: extras that might not all fit + _registerNLocalAt(alice, US, ADMIN_CA, 4, 4000, 0); + _registerNCountryAt(alice, US, 4, 5000, 0); + + (, uint256 count) = fleet.buildHighestBondedUuidBundle(US, ADMIN_CA); + // Cursor=1: admin(8)+country(8)+global(4)=20. Bundle full. + assertEq(count, 20); + } + + function test_buildBundle_partialInclusion_fillsAtHighTier() public { + // With TIER_CAPACITY = 4: + // Cursor=2: local(3)→include. Count=3. + // Cursor=1: local(4)+country(4)=8→include. Count=11, room=9. + // Cursor=0: local(1)≤9→include[count=12,room=8]. country(1)≤8→include[count=13,room=7]. + + for (uint256 i = 0; i < 3; i++) { + vm.prank(alice); + fleet.registerFleetLocal(_uuid(1000 + i), US, ADMIN_CA, 2); + } + for (uint256 i = 0; i < 4; i++) { + vm.prank(alice); + fleet.registerFleetLocal(_uuid(2000 + i), US, ADMIN_CA, 1); + } + for (uint256 i = 0; i < 4; i++) { + vm.prank(alice); + fleet.registerFleetCountry(_uuid(3000 + i), US, 1); + } + + // Tier 0 extras (would be included with more room): + vm.prank(alice); + fleet.registerFleetLocal(_uuid(5000), US, ADMIN_CA, 0); + vm.prank(alice); + fleet.registerFleetCountry(_uuid(5001), US, 0); + + (bytes16[] memory uuids, uint256 count) = fleet.buildHighestBondedUuidBundle(US, ADMIN_CA); + // Cursor=2: local(3)→include. Count=3, room=17. + // Cursor=1: local(4)+country(4)→include. Count=11, room=9. + // Cursor=0: local(1)≤9→include[count=12,room=8]. country(1)≤8→include[count=13,room=7]. + assertEq(count, 13); + } + + function test_buildBundle_partialInclusion_higherPriorityFirst() public { + // Partial inclusion fills higher-priority levels first at each tier. + // Local gets slots before country. + + // Local tier 1: 4, Country tier 1: 4 + for (uint256 i = 0; i < 4; i++) { + vm.prank(alice); + fleet.registerFleetLocal(_uuid(1000 + i), US, ADMIN_CA, 1); + } + for (uint256 i = 0; i < 4; i++) { + vm.prank(alice); + fleet.registerFleetCountry(_uuid(2000 + i), US, 1); + } + + // Tier 0: local=4, country=4 (TIER_CAPACITY = 4) + _registerNLocalAt(alice, US, ADMIN_CA, 4, 3000, 0); + _registerNCountryAt(alice, US, 4, 4000, 0); + + (bytes16[] memory uuids, uint256 count) = fleet.buildHighestBondedUuidBundle(US, ADMIN_CA); + // Cursor=1: local(4)+country(4)=8. Count=8, room=12. + // Cursor=0: local(4)≤12→include 4[count=12,room=8]. country(4)≤8→include 4[count=16]. + assertEq(count, 16); + + // Verify local tier 0 full inclusion (4 of 4) + uint256 localT0Count; + for (uint256 i = 0; i < count; i++) { + uint256 tokenId = _findTokenId(uuids[i], US, ADMIN_CA); + if (fleet.tokenRegion(tokenId) == _regionUSCA() && fleet.fleetTier(tokenId) == 0) localT0Count++; + } + assertEq(localT0Count, 4, "4 local tier 0 included"); + } + + // ── Tie-breaker: local before country at same cursor ── + + function test_buildBundle_tieBreaker_localBeforeCountry() public { + // Room=8 after higher tiers. Local tier 0 (4) tried before country tier 0 (4). + // Local fits (4), then country (4). + + // Eat 12 room at tier 1 and 2. + for (uint256 i = 0; i < 4; i++) { + vm.prank(alice); + fleet.registerFleetLocal(_uuid(1000 + i), US, ADMIN_CA, 1); + } + for (uint256 i = 0; i < 4; i++) { + vm.prank(alice); + fleet.registerFleetCountry(_uuid(2000 + i), US, 1); + } + for (uint256 i = 0; i < 4; i++) { + vm.prank(alice); + fleet.registerFleetLocal(_uuid(3000 + i), US, ADMIN_CA, 2); + } + + // Tier 0: local=4, country=4 (TIER_CAPACITY = 4) + _registerNLocalAt(alice, US, ADMIN_CA, 4, 4000, 0); + _registerNCountryAt(alice, US, 4, 5000, 0); + + (bytes16[] memory uuids, uint256 count) = fleet.buildHighestBondedUuidBundle(US, ADMIN_CA); + // Cursor=2: local(4)→include. Count=4, room=16. + // Cursor=1: local(4)+country(4)=8→include. Count=12, room=8. + // Cursor=0: local(4)≤8→include[count=16,room=4]. country(4)≤4→include 4[count=20,room=0]. + assertEq(count, 20); + + // Verify: local(12) + country(8) + uint256 localCount; + uint256 countryCount; + for (uint256 i = 0; i < count; i++) { + uint256 tokenId = _findTokenId(uuids[i], US, ADMIN_CA); + uint32 region = fleet.tokenRegion(tokenId); + if (region == _regionUS()) countryCount++; + else if (region == _regionUSCA()) localCount++; + } + assertEq(localCount, 12); // tier 0 (4) + tier 1 (4) + tier 2 (4) + assertEq(countryCount, 8); // tier 1 (4) + tier 0 (4) + } + + // ── Empty tiers and gaps ── + + function test_buildBundle_emptyTiersSkippedCleanly() public { + // Register at tier 0 then promote to tier 2, leaving tier 1 empty. + vm.prank(alice); + uint256 id = fleet.registerFleetLocal(UUID_1, US, ADMIN_CA, 0); + vm.prank(alice); + fleet.reassignTier(id, 2); + + vm.prank(alice); + fleet.registerFleetCountry(UUID_2, US, 0); + + (bytes16[] memory uuids, uint256 count) = fleet.buildHighestBondedUuidBundle(US, ADMIN_CA); + // Cursor=2: local(1)→include. Count=1. + // Cursor=1: all empty. No skip. Descend. + // Cursor=0: country(1)→include. Count=2. + assertEq(count, 2); + assertEq(uuids[0], UUID_1); + assertEq(uuids[1], UUID_2); + } + + function test_buildBundle_multipleEmptyTiersInMiddle() public { + // Local at tier 5, country at tier 0. Tiers 1-4 empty. + vm.prank(alice); + uint256 id = fleet.registerFleetLocal(UUID_1, US, ADMIN_CA, 0); + vm.prank(alice); + fleet.reassignTier(id, 5); + vm.prank(alice); + fleet.registerFleetCountry(UUID_2, US, 0); + + (, uint256 count) = fleet.buildHighestBondedUuidBundle(US, ADMIN_CA); + assertEq(count, 2); + } + + function test_buildBundle_emptyTiersInMiddle_countryToo() public { + // Country: register at tier 0 and tier 2 (tier 1 empty) + vm.prank(alice); + fleet.registerFleetCountry(UUID_1, US, 0); + vm.prank(alice); + fleet.registerFleetCountry(UUID_2, US, 2); + + (bytes16[] memory uuids, uint256 count) = fleet.buildHighestBondedUuidBundle(US, ADMIN_CA); + assertEq(count, 2); + assertEq(uuids[0], UUID_2); // higher bond first + assertEq(uuids[1], UUID_1); + } + + // ── Local isolation ── + + function test_buildBundle_multipleAdminAreas_isolated() public { + _registerNLocalAt(alice, US, ADMIN_CA, 4, 1000, 0); + _registerNLocalAt(alice, US, ADMIN_NY, 4, 2000, 0); + + (, uint256 countCA) = fleet.buildHighestBondedUuidBundle(US, ADMIN_CA); + // CA locals + any country + assertEq(countCA, 4); + (, uint256 countNY) = fleet.buildHighestBondedUuidBundle(US, ADMIN_NY); + // NY locals + any country (same country) + assertEq(countNY, 4); + } + + // ── Single level, multiple tiers ── + + function test_buildBundle_singleLevelMultipleTiers() public { + // Only country, multiple tiers. Country fleets fill all available slots. + _registerNCountryAt(alice, US, 4, 1000, 0); // tier 0: 4 members + _registerNCountryAt(alice, US, 4, 2000, 1); // tier 1: 4 members + _registerNCountryAt(alice, US, 4, 3000, 2); // tier 2: 4 members + + (bytes16[] memory uuids, uint256 count) = fleet.buildHighestBondedUuidBundle(US, ADMIN_CA); + assertEq(count, 12); // all country fleets included + // Verify order: tier 2 first (highest bond) + uint256[] memory t2 = fleet.getTierMembers(_regionUS(), 2); + for (uint256 i = 0; i < 4; i++) { + assertEq(uuids[i], bytes16(uint128(t2[i]))); + } + } + + function test_buildBundle_singleLevelOnlyLocal() public { + _registerNLocalAt(alice, US, ADMIN_CA, 4, 1000, 0); + (, uint256 count) = fleet.buildHighestBondedUuidBundle(US, ADMIN_CA); + assertEq(count, 4); + } + + function test_buildBundle_onlyCountry() public { + // TIER_CAPACITY = 4, so split across two tiers + _registerNCountryAt(alice, US, 4, 1000, 0); + _registerNCountryAt(alice, US, 4, 1100, 1); + + (bytes16[] memory uuids, uint256 count) = fleet.buildHighestBondedUuidBundle(US, ADMIN_CA); + assertEq(count, 8); + assertEq(uuids[0], _uuid(1100)); // tier 1 comes first (higher bond) + } + + function test_buildBundle_countryFillsSlots() public { + // Test that country fleets fill bundle slots when room is available. + // + // Setup: 2 local fleets + 12 country fleets across 3 tiers + // Expected: All 14 should be included since bundle has room + _registerNLocalAt(alice, US, ADMIN_CA, 2, 1000, 0); + _registerNCountryAt(alice, US, 4, 2000, 0); // tier 0: 4 country + _registerNCountryAt(alice, US, 4, 3000, 1); // tier 1: 4 country + _registerNCountryAt(alice, US, 4, 4000, 2); // tier 2: 4 country + + (bytes16[] memory uuids, uint256 count) = fleet.buildHighestBondedUuidBundle(US, ADMIN_CA); + + // All 14 should be included: 2 local + 12 country + assertEq(count, 14); + + // Verify order: tier 2 country (highest bond) → tier 1 country → tier 0 local/country + // First 4 should be tier 2 country fleets + for (uint256 i = 0; i < 4; i++) { + assertEq(uuids[i], _uuid(4000 + i)); + } + } + + function test_buildBundle_localsPriorityWithinTier() public { + // When locals and country compete at same tier, locals are included first. + // + // Setup: 8 local fleets + 12 country fleets + _registerNLocalAt(alice, US, ADMIN_CA, 4, 1000, 0); + _registerNLocalAt(alice, US, ADMIN_CA, 4, 1100, 1); + _registerNCountryAt(alice, US, 4, 2000, 0); + _registerNCountryAt(alice, US, 4, 3000, 1); + _registerNCountryAt(alice, US, 4, 4000, 2); + + (, uint256 count) = fleet.buildHighestBondedUuidBundle(US, ADMIN_CA); + + // Total: 8 local + 12 country = 20 (bundle max) + assertEq(count, 20); + } + + // ── Shared cursor: different max tier indices per level ── + + function test_buildBundle_sharedCursor_levelsAtDifferentMaxTiers() public { + // Local at tier 3, Country at tier 1. + vm.prank(alice); + uint256 id1 = fleet.registerFleetLocal(UUID_1, US, ADMIN_CA, 0); + vm.prank(alice); + fleet.reassignTier(id1, 3); + vm.prank(alice); + uint256 id2 = fleet.registerFleetCountry(UUID_2, US, 0); + vm.prank(alice); + fleet.reassignTier(id2, 1); + vm.prank(alice); + fleet.registerFleetLocal(UUID_3, US, ADMIN_CA, 0); + + (bytes16[] memory uuids, uint256 count) = fleet.buildHighestBondedUuidBundle(US, ADMIN_CA); + assertEq(count, 3); + assertEq(uuids[0], UUID_1); // tier 3 + assertEq(uuids[1], UUID_2); // tier 1 + assertEq(uuids[2], UUID_3); // tier 0 + } + + function test_buildBundle_sharedCursor_sameTierIndex_differentBondByRegion() public view { + // Local tier 0 = BASE_BOND, Country tier 0 = BASE_BOND * fleet.COUNTRY_BOND_MULTIPLIER() (multiplier) + assertEq(fleet.tierBond(0, false), BASE_BOND); + assertEq(fleet.tierBond(0, true), BASE_BOND * fleet.COUNTRY_BOND_MULTIPLIER()); + assertEq(fleet.tierBond(1, false), BASE_BOND * 2); + assertEq(fleet.tierBond(1, true), BASE_BOND * fleet.COUNTRY_BOND_MULTIPLIER() * 2); + } + + // ── Lifecycle ── + + function test_buildBundle_afterBurn_reflects() public { + vm.prank(alice); + uint256 id1 = fleet.registerFleetLocal(UUID_1, US, ADMIN_CA, 0); + vm.prank(bob); + fleet.registerFleetLocal(UUID_2, US, ADMIN_CA, 0); + vm.prank(carol); + fleet.registerFleetLocal(UUID_3, US, ADMIN_CA, 0); + + (, uint256 countBefore) = fleet.buildHighestBondedUuidBundle(US, ADMIN_CA); + assertEq(countBefore, 3); + + vm.prank(alice); + fleet.burn(id1); + + (, uint256 countAfter) = fleet.buildHighestBondedUuidBundle(US, ADMIN_CA); + assertEq(countAfter, 2); + } + + function test_buildBundle_exhaustsBothLevels() public { + vm.prank(alice); + fleet.registerFleetCountry(UUID_1, US, 0); + vm.prank(alice); + fleet.registerFleetLocal(UUID_2, US, ADMIN_CA, 0); + + (bytes16[] memory uuids, uint256 count) = fleet.buildHighestBondedUuidBundle(US, ADMIN_CA); + assertEq(count, 2); + bool found1; + bool found2; + for (uint256 i = 0; i < count; i++) { + if (uuids[i] == UUID_1) found1 = true; + if (uuids[i] == UUID_2) found2 = true; + } + assertTrue(found1 && found2); + } + + function test_buildBundle_lifecycle_promotionsAndBurns() public { + vm.prank(alice); + uint256 l1 = fleet.registerFleetLocal(_uuid(100), US, ADMIN_CA, 0); + vm.prank(alice); + fleet.registerFleetLocal(_uuid(101), US, ADMIN_CA, 0); + vm.prank(alice); + fleet.registerFleetLocal(_uuid(102), US, ADMIN_CA, 0); + + vm.prank(alice); + uint256 c1 = fleet.registerFleetCountry(_uuid(200), US, 0); + vm.prank(alice); + fleet.registerFleetCountry(_uuid(201), US, 0); + + vm.prank(alice); + fleet.registerFleetLocal(_uuid(300), US, ADMIN_CA, 0); + + vm.prank(alice); + fleet.reassignTier(l1, 3); + vm.prank(alice); + fleet.reassignTier(c1, 1); + + (, uint256 count) = fleet.buildHighestBondedUuidBundle(US, ADMIN_CA); + // Cursor=3: local(1)→include. Count=1. + // Cursor=2: empty. Descend. + // Cursor=1: country(1)→include. Count=2. + // Cursor=0: local(3)+country(1)=4→include. Count=6. + assertEq(count, 6); + + vm.prank(alice); + fleet.burn(l1); + + (, count) = fleet.buildHighestBondedUuidBundle(US, ADMIN_CA); + assertEq(count, 5); + } + + // ── Cap enforcement ── + + function test_buildBundle_capsAt20() public { + // Fill local: 4+4+4 = 12 in 3 tiers + _registerNLocalAt(alice, US, ADMIN_CA, 4, 0, 0); + _registerNLocalAt(alice, US, ADMIN_CA, 4, 100, 1); + _registerNLocalAt(alice, US, ADMIN_CA, 4, 200, 2); + // Fill country US: 4+4 = 8 in 2 tiers (TIER_CAPACITY = 4) + _registerNCountryAt(bob, US, 4, 1000, 0); + _registerNCountryAt(bob, US, 4, 1100, 1); + + (, uint256 count) = fleet.buildHighestBondedUuidBundle(US, ADMIN_CA); + assertEq(count, 20); + } + + function test_buildBundle_exactlyFillsToCapacity() public { + // 12 local + 8 country = 20 exactly, spread across tiers (TIER_CAPACITY = 4). + _registerNLocalAt(alice, US, ADMIN_CA, 4, 1000, 0); + _registerNLocalAt(alice, US, ADMIN_CA, 4, 1100, 1); + _registerNLocalAt(alice, US, ADMIN_CA, 4, 1200, 2); + _registerNCountryAt(alice, US, 4, 2000, 0); + _registerNCountryAt(alice, US, 4, 2100, 1); + + (, uint256 count) = fleet.buildHighestBondedUuidBundle(US, ADMIN_CA); + assertEq(count, 20); + } + + function test_buildBundle_twentyOneMembers_partialInclusion() public { + // 21 total: local 12 + country 8 + 1 extra country at tier 2. + // With partial inclusion, bundle fills to 20. + // TIER_CAPACITY = 4, so spread across tiers. + _registerNLocalAt(alice, US, ADMIN_CA, 4, 1000, 0); + _registerNLocalAt(alice, US, ADMIN_CA, 4, 1100, 1); + _registerNLocalAt(alice, US, ADMIN_CA, 4, 1200, 2); + _registerNCountryAt(alice, US, 4, 2000, 0); + _registerNCountryAt(alice, US, 4, 2100, 1); + vm.prank(alice); + fleet.registerFleetCountry(_uuid(3000), US, 2); + + // Cursor=2: local(4)+country(1)=5. Count=5, room=15. + // Cursor=1: local(4)+country(4)=8. Count=13, room=7. + // Cursor=0: local(4)≤7→include 4[count=17,room=3]. + // country(4)>3→include 3 of 4[count=20,room=0]. + (, uint256 count) = fleet.buildHighestBondedUuidBundle(US, ADMIN_CA); + assertEq(count, 20); // caps at max bundle size + } + + // ── Integrity ── + + function test_buildBundle_noDuplicateUUIDs() public { + _registerNLocalAt(alice, US, ADMIN_CA, 4, 1000, 0); + _registerNCountryAt(bob, US, 4, 2000, 0); + + (bytes16[] memory uuids, uint256 count) = fleet.buildHighestBondedUuidBundle(US, ADMIN_CA); + for (uint256 i = 0; i < count; i++) { + for (uint256 j = i + 1; j < count; j++) { + assertTrue(uuids[i] != uuids[j], "Duplicate UUID found"); + } + } + } + + function test_buildBundle_noNonExistentUUIDs() public { + _registerNLocalAt(alice, US, ADMIN_CA, 3, 1000, 0); + _registerNCountryAt(bob, US, 2, 2000, 0); + vm.prank(carol); + fleet.registerFleetLocal(UUID_1, US, ADMIN_CA, 0); + + (bytes16[] memory uuids, uint256 count) = fleet.buildHighestBondedUuidBundle(US, ADMIN_CA); + assertEq(count, 6); + for (uint256 i = 0; i < count; i++) { + uint256 tokenId = _findTokenId(uuids[i], US, ADMIN_CA); + assertTrue(fleet.ownerOf(tokenId) != address(0)); + } + } + + function test_buildBundle_allReturnedAreFromCorrectRegions() public { + // Verify returned UUIDs are from local or country regions. + _registerNLocalAt(alice, US, ADMIN_CA, 4, 1000, 0); + _registerNCountryAt(alice, US, 3, 2000, 0); + + (bytes16[] memory uuids, uint256 count) = fleet.buildHighestBondedUuidBundle(US, ADMIN_CA); + + uint256 localFound; + uint256 countryFound; + for (uint256 i = 0; i < count; i++) { + uint256 tid = _findTokenId(uuids[i], US, ADMIN_CA); + uint32 region = fleet.tokenRegion(tid); + if (region == _regionUSCA()) localFound++; + else if (region == _regionUS()) countryFound++; + } + assertEq(localFound, 4, "local count"); + assertEq(countryFound, 3, "country count"); + } + + // ── Fuzz ── + + function testFuzz_buildBundle_neverExceeds20(uint8 cCount, uint8 lCount) public { + cCount = uint8(bound(cCount, 0, 15)); + lCount = uint8(bound(lCount, 0, 15)); + + for (uint256 i = 0; i < cCount; i++) { + vm.prank(alice); + fleet.registerFleetCountry(_uuid(31_000 + i), US, i / 4); + } + for (uint256 i = 0; i < lCount; i++) { + vm.prank(alice); + fleet.registerFleetLocal(_uuid(32_000 + i), US, ADMIN_CA, i / 4); + } + + (, uint256 count) = fleet.buildHighestBondedUuidBundle(US, ADMIN_CA); + assertLe(count, 20); + } + + function testFuzz_buildBundle_noDuplicates(uint8 cCount, uint8 lCount) public { + cCount = uint8(bound(cCount, 0, 12)); + lCount = uint8(bound(lCount, 0, 12)); + + for (uint256 i = 0; i < cCount; i++) { + vm.prank(alice); + fleet.registerFleetCountry(_uuid(41_000 + i), US, i / 4); + } + for (uint256 i = 0; i < lCount; i++) { + vm.prank(alice); + fleet.registerFleetLocal(_uuid(42_000 + i), US, ADMIN_CA, i / 4); + } + + (bytes16[] memory uuids, uint256 count) = fleet.buildHighestBondedUuidBundle(US, ADMIN_CA); + for (uint256 i = 0; i < count; i++) { + for (uint256 j = i + 1; j < count; j++) { + assertTrue(uuids[i] != uuids[j], "Fuzz: duplicate UUID"); + } + } + } + + function testFuzz_buildBundle_allReturnedUUIDsExist(uint8 cCount, uint8 lCount) public { + cCount = uint8(bound(cCount, 0, 12)); + lCount = uint8(bound(lCount, 0, 12)); + + for (uint256 i = 0; i < cCount; i++) { + vm.prank(alice); + fleet.registerFleetCountry(_uuid(51_000 + i), US, i / 4); + } + for (uint256 i = 0; i < lCount; i++) { + vm.prank(alice); + fleet.registerFleetLocal(_uuid(52_000 + i), US, ADMIN_CA, i / 4); + } + + (bytes16[] memory uuids, uint256 count) = fleet.buildHighestBondedUuidBundle(US, ADMIN_CA); + for (uint256 i = 0; i < count; i++) { + uint256 tokenId = _findTokenId(uuids[i], US, ADMIN_CA); + assertTrue(fleet.ownerOf(tokenId) != address(0), "Fuzz: UUID does not exist"); + } + } + + function testFuzz_buildBundle_partialInclusionInvariant(uint8 cCount, uint8 lCount) public { + cCount = uint8(bound(cCount, 0, 12)); + lCount = uint8(bound(lCount, 0, 12)); + + for (uint256 i = 0; i < cCount; i++) { + vm.prank(alice); + fleet.registerFleetCountry(_uuid(61_000 + i), US, i / 4); + } + for (uint256 i = 0; i < lCount; i++) { + vm.prank(alice); + fleet.registerFleetLocal(_uuid(62_000 + i), US, ADMIN_CA, i / 4); + } + + (bytes16[] memory uuids2, uint256 count2) = fleet.buildHighestBondedUuidBundle(US, ADMIN_CA); + + // With partial inclusion: for each (region, tier) group in the bundle, + // the included members should be a PREFIX of the full tier (registration order). + // We verify this by checking that included members are the first N in the tier's array. + for (uint256 i = 0; i < count2; i++) { + uint256 tid = _findTokenId(uuids2[i], US, ADMIN_CA); + uint32 region = fleet.tokenRegion(tid); + uint256 tier = fleet.fleetTier(tid); + + // Count how many from this (region, tier) are in the bundle + uint256 inBundle; + for (uint256 j = 0; j < count2; j++) { + uint256 tjd = _findTokenId(uuids2[j], US, ADMIN_CA); + if (fleet.tokenRegion(tjd) == region && fleet.fleetTier(tjd) == tier) { + inBundle++; + } + } + + // Get the full tier members + uint256[] memory tierMembers = fleet.getTierMembers(region, tier); + + // The included count should be <= total tier members + assertLe(inBundle, tierMembers.length, "Fuzz: more included than exist"); + + // Verify the included members are exactly the first `inBundle` members of the tier + // (prefix property for partial inclusion) + uint256 found; + for (uint256 m = 0; m < inBundle && m < tierMembers.length; m++) { + bytes16 expectedUuid = bytes16(uint128(tierMembers[m])); + for (uint256 j = 0; j < count2; j++) { + if (uuids2[j] == expectedUuid) { + found++; + break; + } + } + } + assertEq(found, inBundle, "Fuzz: included members not a prefix of tier"); + } + } + + // ══════════════════════════════════════════════════════════════════════════════════ + // Edge Cases: _findCheapestInclusionTier & MaxTiersReached + // ══════════════════════════════════════════════════════════════════════════════════ + + /// @notice When all 24 tiers of a region are full, localInclusionHint should revert. + function test_RevertIf_localInclusionHint_allTiersFull() public { + uint256 cap = fleet.TIER_CAPACITY(); + uint256 maxTiers = fleet.MAX_TIERS(); + // Fill all tiers of US/ADMIN_CA (cap members each) + for (uint256 tier = 0; tier < maxTiers; tier++) { + for (uint256 i = 0; i < cap; i++) { + vm.prank(alice); + fleet.registerFleetLocal(_uuid(tier * 100 + i), US, ADMIN_CA, tier); + } + } + + // Verify all tiers are full + for (uint256 tier = 0; tier < maxTiers; tier++) { + assertEq(fleet.tierMemberCount(fleet.makeAdminRegion(US, ADMIN_CA), tier), cap); + } + + // localInclusionHint should revert + vm.expectRevert(FleetIdentity.MaxTiersReached.selector); + fleet.localInclusionHint(US, ADMIN_CA); + } + + /// @notice When all tiers are full, registering at any tier should revert with TierFull. + function test_RevertIf_registerFleetLocal_allTiersFull() public { + uint256 cap = fleet.TIER_CAPACITY(); + uint256 maxTiers = fleet.MAX_TIERS(); + // Fill all tiers + for (uint256 tier = 0; tier < maxTiers; tier++) { + for (uint256 i = 0; i < cap; i++) { + vm.prank(alice); + fleet.registerFleetLocal(_uuid(tier * 100 + i), US, ADMIN_CA, tier); + } + } + + // Registration at tier 0 (or any full tier) should revert with TierFull + vm.prank(bob); + vm.expectRevert(FleetIdentity.TierFull.selector); + fleet.registerFleetLocal(_uuid(99999), US, ADMIN_CA, 0); + } + + /// @notice countryInclusionHint reverts when all tiers in the country region are full. + function test_RevertIf_countryInclusionHint_allTiersFull() public { + uint256 cap = fleet.TIER_CAPACITY(); + uint256 maxTiers = fleet.MAX_TIERS(); + // Fill all tiers of country US (cap members each) + for (uint256 tier = 0; tier < maxTiers; tier++) { + for (uint256 i = 0; i < cap; i++) { + vm.prank(alice); + fleet.registerFleetCountry(_uuid(tier * 100 + i), US, tier); + } + } + + vm.expectRevert(FleetIdentity.MaxTiersReached.selector); + fleet.countryInclusionHint(US); + } + + /// @notice Proves cheapest inclusion tier can be ABOVE maxTierIndex when bundle is + /// constrained by higher-priority levels at existing tiers. + /// + /// Scenario: + /// - Fill admin tiers 0, 1, 2 with 4 members each (full) + /// - Country US has 4 fleets at tier 2 (maxTierIndex) + /// - Admin tier 0-2 are FULL (4 members each), so a new fleet cannot join any. + /// - Cheapest inclusion should be tier 3 (above maxTierIndex=2). + function test_cheapestInclusionTier_aboveMaxTierIndex() public { + uint256 cap = fleet.TIER_CAPACITY(); + // Fill admin tiers 0, 1, 2 with TIER_CAPACITY members each + _registerNLocalAt(alice, US, ADMIN_CA, cap, 4000, 0); + _registerNLocalAt(alice, US, ADMIN_CA, cap, 5000, 1); + _registerNLocalAt(alice, US, ADMIN_CA, cap, 6000, 2); + // Country at tier 2 (sets maxTierIndex across regions) + _registerNCountryAt(alice, US, cap, 7000, 2); + + // Verify tier 2 is maxTierIndex + assertEq(fleet.regionTierCount(uint32(US)), 3); + assertEq(fleet.regionTierCount(fleet.makeAdminRegion(US, ADMIN_CA)), 3); + + // All admin tiers 0-2 are full (TIER_CAPACITY members each) + assertEq(fleet.tierMemberCount(fleet.makeAdminRegion(US, ADMIN_CA), 0), cap); + assertEq(fleet.tierMemberCount(fleet.makeAdminRegion(US, ADMIN_CA), 1), cap); + assertEq(fleet.tierMemberCount(fleet.makeAdminRegion(US, ADMIN_CA), 2), cap); + + // At tiers 0-2: all tiers are full, cannot join. + // At tier 3: above maxTierIndex, countBefore = 0, has room. + (uint256 inclusionTier, uint256 bond) = fleet.localInclusionHint(US, ADMIN_CA); + assertEq(inclusionTier, 3, "Should recommend tier 3 (above maxTierIndex=2)"); + assertEq(bond, BASE_BOND * 8); // local tier 3 bond = BASE_BOND * 2^3 + + // Verify registration at tier 3 works + vm.prank(bob); + uint256 tokenId = fleet.registerFleetLocal(_uuid(9999), US, ADMIN_CA, 3); + assertEq(fleet.fleetTier(tokenId), 3); + + // Confirm new fleet appears in bundle at the TOP (first position) + (bytes16[] memory uuids, uint256 count) = fleet.buildHighestBondedUuidBundle(US, ADMIN_CA); + // tier 3 (1) + tier 2 admin (cap) + tier 2 country (cap) + tier 1 admin (cap) + tier 0 admin (cap) + // = 1 + 4*cap, capped at MAX_BONDED_UUID_BUNDLE_SIZE + uint256 expectedCount = 1 + 4 * cap; + if (expectedCount > fleet.MAX_BONDED_UUID_BUNDLE_SIZE()) { + expectedCount = fleet.MAX_BONDED_UUID_BUNDLE_SIZE(); + } + assertEq(count, expectedCount); + assertEq(uuids[0], _uuid(9999), "Tier 3 fleet should be first in bundle"); + } + + /// @notice Edge case: bundle is full from tier maxTierIndex, and all tiers 0..maxTierIndex + /// at the candidate region are also full. The cheapest tier is above maxTierIndex. + function test_cheapestInclusionTier_aboveMaxTierIndex_candidateTiersFull() public { + uint256 cap = fleet.TIER_CAPACITY(); + // Country tier 0 has TIER_CAPACITY fleets + _registerNCountryAt(alice, US, cap, 1000, 0); + + // Admin tier 0 has TIER_CAPACITY fleets (full) + _registerNLocalAt(alice, US, ADMIN_CA, cap, 2000, 0); + + // Verify admin tier 0 is full + assertEq(fleet.tierMemberCount(fleet.makeAdminRegion(US, ADMIN_CA), 0), cap); + + // Admin tier 0 is full, so candidate must go elsewhere. + // Cheapest inclusion tier should be 1 (above maxTierIndex=0). + (uint256 inclusionTier,) = fleet.localInclusionHint(US, ADMIN_CA); + assertEq(inclusionTier, 1, "Should recommend tier 1 since tier 0 is full"); + } + + /// @notice When going above maxTierIndex would require tier >= MAX_TIERS, revert. + /// + /// Scenario: Fill global tiers 0-23 with 4 members each (96 global fleets). + /// A new LOCAL fleet cannot fit in any tier because: + /// - The bundle simulation runs through tiers 23→0 + /// - At each tier, global's 4 members + potential admin members need to fit + /// - With global filling 4 slots at every tier, and country/admin potentially + /// competing, we design a scenario where no tier works. + /// + /// Simpler approach: Fill all 24 admin tiers AND make bundle full at every tier. + function test_RevertIf_cheapestInclusionTier_exceedsMaxTiers() public { + uint256 cap = fleet.TIER_CAPACITY(); + // Fill all 24 tiers of admin area US/CA with TIER_CAPACITY members each + for (uint256 tier = 0; tier < fleet.MAX_TIERS(); tier++) { + for (uint256 i = 0; i < cap; i++) { + vm.prank(alice); + fleet.registerFleetLocal(_uuid(tier * 100 + i), US, ADMIN_CA, tier); + } + } + + // Now all admin tiers 0-23 are full. A new admin fleet must go to tier 24, + // which exceeds MAX_TIERS=24 (valid tiers are 0-23). + vm.expectRevert(FleetIdentity.MaxTiersReached.selector); + fleet.localInclusionHint(US, ADMIN_CA); + } + + /// @notice Verify that when bundle is full due to higher-tier members preventing + /// lower-tier inclusion, the hint correctly identifies the cheapest viable tier. + function test_cheapestInclusionTier_bundleFullFromHigherTiers() public { + uint256 cap = fleet.TIER_CAPACITY(); + // Create a scenario where: + // - Admin tiers 0-5 are all full (TIER_CAPACITY each) + // - Country tier 5 has TIER_CAPACITY members + // All admin tiers 0-5 are full, so must go to tier 6. + + // Fill admin tiers 0-5 with TIER_CAPACITY members each + for (uint256 tier = 0; tier <= 5; tier++) { + _registerNLocalAt(alice, US, ADMIN_CA, cap, 10000 + tier * 100, tier); + } + // Country at tier 5 + _registerNCountryAt(alice, US, cap, 11000, 5); + + // maxTierIndex = 5 + // All admin tiers 0-5 are full. Cannot join any. + // At tier 6: above maxTierIndex, countBefore = 0. Has room. + (uint256 inclusionTier,) = fleet.localInclusionHint(US, ADMIN_CA); + assertEq(inclusionTier, 6, "Must go above maxTierIndex=5 to tier 6"); + } + + /// @notice Verifies the bundle correctly includes a fleet registered above maxTierIndex. + function test_buildBundle_includesFleetAboveMaxTierIndex() public { + uint256 cap = fleet.TIER_CAPACITY(); + // Only country tier 0 has fleets (maxTierIndex = 0) + _registerNCountryAt(alice, US, cap, 20000, 0); + + // New admin registers at tier 2 (above maxTierIndex) + vm.prank(bob); + uint256 adminToken = fleet.registerFleetLocal(_uuid(21000), US, ADMIN_CA, 2); + + // Bundle should include admin tier 2 first (highest), then country tier 0 + (bytes16[] memory uuids, uint256 count) = fleet.buildHighestBondedUuidBundle(US, ADMIN_CA); + // Admin tier 2 (1) + Country tier 0 (cap) + uint256 expectedCount = 1 + cap; + if (expectedCount > fleet.MAX_BONDED_UUID_BUNDLE_SIZE()) { + expectedCount = fleet.MAX_BONDED_UUID_BUNDLE_SIZE(); + } + assertEq(count, expectedCount); + + // First should be admin tier 2 + assertEq(_tokenId(uuids[0], _regionUSCA()), adminToken, "Admin tier 2 fleet should be first"); + } + + // ══════════════════════════════════════════════════════════════════════════════════ + // Demonstration: Partial inclusion prevents total tier displacement + // ══════════════════════════════════════════════════════════════════════════════════ + + /// @notice DEMONSTRATES that partial inclusion prevents the scenario where a single + /// fleet registration could push an entire tier out of the bundle. + /// + /// Scenario (2-level system: country + local): + /// BEFORE: + /// - Admin tier 0: 4 members + /// - Country tier 0: 4 members + /// - Bundle: all 8 members included (4+4=8) + /// + /// AFTER (single admin tier 1 registration): + /// - Admin tier 1: 1 member (NEW - above previous maxTierIndex) + /// - With PARTIAL INCLUSION: + /// - Tier 1: admin(1) → count=1 + /// - Tier 0: admin(4) + country(4) = 8, count=9 + /// - Final bundle: 9 members (all fit) + /// + /// Result: All original fleets remain included. + function test_DEMO_partialInclusionPreventsFullDisplacement() public { + // === BEFORE STATE === + uint32 countryRegion = uint32(US); + + // Fill with admin(4) + country(4) = 8 + uint256[] memory localIds = _registerNLocalAt(alice, US, ADMIN_CA, 4, 30000, 0); // Admin tier 0: 4 + uint256[] memory countryIds = _registerNCountryAt(alice, US, 4, 31000, 0); // Country tier 0: 4 + + // Verify BEFORE: all 8 members in bundle + (bytes16[] memory uuidsBefore, uint256 countBefore) = fleet.buildHighestBondedUuidBundle(US, ADMIN_CA); + assertEq(countBefore, 8, "BEFORE: All 8 members should be in bundle"); + + // Verify all 4 country fleets are included BEFORE + uint256 countryCountBefore; + for (uint256 i = 0; i < countBefore; i++) { + uint256 tokenId = _findTokenId(uuidsBefore[i], US, ADMIN_CA); + if (fleet.tokenRegion(tokenId) == countryRegion) countryCountBefore++; + } + assertEq(countryCountBefore, 4, "BEFORE: All 4 country fleets in bundle"); + + // === SINGLE REGISTRATION === + // Bob registers just ONE fleet at admin tier 1 + vm.prank(bob); + fleet.registerFleetLocal(_uuid(99999), US, ADMIN_CA, 1); + + // === AFTER STATE === + (bytes16[] memory uuidsAfter, uint256 countAfter) = fleet.buildHighestBondedUuidBundle(US, ADMIN_CA); + + // Bundle now has 9 members (tier 1: 1 + tier 0: 4+4) + assertEq(countAfter, 9, "AFTER: Bundle should have 9 members"); + + // Count how many country fleets are included AFTER + uint256 countryCountAfter; + for (uint256 i = 0; i < countAfter; i++) { + uint256 tokenId = _findTokenId(uuidsAfter[i], US, ADMIN_CA); + if (fleet.tokenRegion(tokenId) == countryRegion) countryCountAfter++; + } + assertEq(countryCountAfter, 4, "AFTER: All 4 country fleets still in bundle"); + + // Verify all country fleets are still included + bool[] memory countryIncluded = new bool[](4); + for (uint256 i = 0; i < countAfter; i++) { + uint256 tokenId = _findTokenId(uuidsAfter[i], US, ADMIN_CA); + for (uint256 c = 0; c < 4; c++) { + if (tokenId == countryIds[c]) countryIncluded[c] = true; + } + } + assertTrue(countryIncluded[0], "First country fleet included"); + assertTrue(countryIncluded[1], "Second country fleet included"); + assertTrue(countryIncluded[2], "Third country fleet included"); + assertTrue(countryIncluded[3], "Fourth country fleet included"); + + // === IMPROVEMENT SUMMARY === + emit log_string("=== PARTIAL INCLUSION FIX DEMONSTRATED ==="); + emit log_string("A single tier-1 registration does not displace any country fleets"); + emit log_named_uint("Country fleets displaced", 0); + emit log_named_uint("Country fleets still included", 4); + } + + // ══════════════════════════════════════════════════════════════════════════════ + // buildCountryOnlyBundle tests + // ══════════════════════════════════════════════════════════════════════════════ + + function test_buildCountryOnlyBundle_emptyCountry() public view { + // No fleets registered yet + (bytes16[] memory uuids, uint256 count) = fleet.buildCountryOnlyBundle(US); + assertEq(count, 0, "Empty country should have 0 UUIDs"); + assertEq(uuids.length, 0, "Array should be trimmed to 0"); + } + + function test_buildCountryOnlyBundle_onlyCountryFleets() public { + // Register 3 country fleets at different tiers + vm.prank(alice); + fleet.registerFleetCountry(_uuid(1), US, 0); + vm.prank(alice); + fleet.registerFleetCountry(_uuid(2), US, 1); + vm.prank(alice); + fleet.registerFleetCountry(_uuid(3), US, 2); + + (bytes16[] memory uuids, uint256 count) = fleet.buildCountryOnlyBundle(US); + assertEq(count, 3, "Should include all 3 country fleets"); + + // Verify tier priority order (highest first) + assertEq(uuids[0], _uuid(3), "Tier 2 should be first"); + assertEq(uuids[1], _uuid(2), "Tier 1 should be second"); + assertEq(uuids[2], _uuid(1), "Tier 0 should be third"); + } + + function test_buildCountryOnlyBundle_excludesLocalFleets() public { + // Register country fleet + vm.prank(alice); + fleet.registerFleetCountry(_uuid(1), US, 0); + + // Register local fleet in same country + vm.prank(alice); + fleet.registerFleetLocal(_uuid(2), US, ADMIN_CA, 0); + + // Country-only bundle should ONLY include country fleet + (bytes16[] memory uuids, uint256 count) = fleet.buildCountryOnlyBundle(US); + assertEq(count, 1, "Should only include country fleet"); + assertEq(uuids[0], _uuid(1), "Should be the country fleet UUID"); + } + + function test_buildCountryOnlyBundle_respectsMaxBundleSize() public { + // Register 24 country fleets across 6 tiers (4 per tier = TIER_CAPACITY) + // This gives us more than MAX_BONDED_UUID_BUNDLE_SIZE (20) + for (uint256 tier = 0; tier < 6; tier++) { + for (uint256 i = 0; i < 4; i++) { + vm.prank(alice); + fleet.registerFleetCountry(_uuid(tier * 100 + i), US, tier); + } + } + + (bytes16[] memory uuids, uint256 count) = fleet.buildCountryOnlyBundle(US); + assertEq(count, 20, "Should cap at 20 UUIDs"); + assertEq(uuids.length, 20, "Array should be trimmed to 20"); + } + + function test_RevertIf_buildCountryOnlyBundle_invalidCountryCode() public { + vm.expectRevert(FleetIdentity.InvalidCountryCode.selector); + fleet.buildCountryOnlyBundle(0); + + vm.expectRevert(FleetIdentity.InvalidCountryCode.selector); + fleet.buildCountryOnlyBundle(1000); // > MAX_COUNTRY_CODE (999) + } + + function test_buildCountryOnlyBundle_multipleCountriesIndependent() public { + // Register in US (country 840) + vm.prank(alice); + fleet.registerFleetCountry(_uuid(1), US, 0); + + // Register in Germany (country 276) + vm.prank(alice); + fleet.registerFleetCountry(_uuid(2), DE, 0); + + // US bundle should only have US fleet + (bytes16[] memory usUuids, uint256 usCount) = fleet.buildCountryOnlyBundle(US); + assertEq(usCount, 1, "US should have 1 fleet"); + assertEq(usUuids[0], _uuid(1), "Should be US fleet"); + + // Germany bundle should only have Germany fleet + (bytes16[] memory deUuids, uint256 deCount) = fleet.buildCountryOnlyBundle(DE); + assertEq(deCount, 1, "Germany should have 1 fleet"); + assertEq(deUuids[0], _uuid(2), "Should be Germany fleet"); + } + + // ══════════════════════════════════════════════ + // Operator Tests + // ══════════════════════════════════════════════ + + function test_operatorOf_defaultsToUuidOwner() public { + vm.prank(alice); + fleet.registerFleetLocal(UUID_1, US, ADMIN_CA, 0); + + // No operator set, should default to uuidOwner + assertEq(fleet.operatorOf(UUID_1), alice); + } + + function test_operatorOf_returnsSetOperator() public { + vm.prank(alice); + fleet.registerFleetLocal(UUID_1, US, ADMIN_CA, 0); + + vm.prank(alice); + fleet.setOperator(UUID_1, bob); + + assertEq(fleet.operatorOf(UUID_1), bob); + } + + function test_setOperator_emitsEvent() public { + vm.prank(alice); + fleet.registerFleetLocal(UUID_1, US, ADMIN_CA, 0); + + // Just verify setOperator succeeds and changes state + vm.prank(alice); + fleet.setOperator(UUID_1, bob); + assertEq(fleet.operatorOf(UUID_1), bob); + } + + function test_setOperator_transfersTierExcess() public { + vm.prank(alice); + fleet.registerFleetLocal(UUID_1, US, ADMIN_CA, 2); + + uint256 tierBonds = fleet.tierBond(2, false); + uint256 aliceBefore = bondToken.balanceOf(alice); + uint256 bobBefore = bondToken.balanceOf(bob); + + vm.prank(alice); + fleet.setOperator(UUID_1, bob); + + // Alice gets full tier bonds refunded, bob pays full tier bonds + assertEq(bondToken.balanceOf(alice), aliceBefore + tierBonds); + assertEq(bondToken.balanceOf(bob), bobBefore - tierBonds); + } + + function test_setOperator_multiRegion_transfersAllTierExcess() public { + // Register in two local regions at different tiers + vm.prank(alice); + fleet.registerFleetLocal(UUID_1, US, ADMIN_CA, 2); + vm.prank(alice); + fleet.registerFleetLocal(UUID_1, US, ADMIN_NY, 1); + + uint256 tierBondsFirst = fleet.tierBond(2, false); + uint256 tierBondsSecond = fleet.tierBond(1, false); + uint256 totalTierBonds = tierBondsFirst + tierBondsSecond; + + uint256 aliceBefore = bondToken.balanceOf(alice); + uint256 bobBefore = bondToken.balanceOf(bob); + + vm.prank(alice); + fleet.setOperator(UUID_1, bob); + + assertEq(bondToken.balanceOf(alice), aliceBefore + totalTierBonds); + assertEq(bondToken.balanceOf(bob), bobBefore - totalTierBonds); + } + + function test_setOperator_zeroTierExcess_noTransfer() public { + // Register at tier 0, tierBond = BASE_BOND + vm.prank(alice); + fleet.registerFleetLocal(UUID_1, US, ADMIN_CA, 0); + + uint256 tierBonds = fleet.tierBond(0, false); + uint256 aliceBefore = bondToken.balanceOf(alice); + uint256 bobBefore = bondToken.balanceOf(bob); + + vm.prank(alice); + fleet.setOperator(UUID_1, bob); + + // Full tier bonds transferred (tierBond(0, false) = BASE_BOND) + assertEq(bondToken.balanceOf(alice), aliceBefore + tierBonds); + assertEq(bondToken.balanceOf(bob), bobBefore - tierBonds); + } + + function test_setOperator_changeOperator_transfersBetweenOperators() public { + vm.prank(alice); + fleet.registerFleetLocal(UUID_1, US, ADMIN_CA, 2); + + // Set bob as operator + vm.prank(alice); + fleet.setOperator(UUID_1, bob); + + uint256 tierBonds = fleet.tierBond(2, false); + uint256 bobBefore = bondToken.balanceOf(bob); + uint256 carolBefore = bondToken.balanceOf(carol); + + // Change operator from bob to carol + vm.prank(alice); + fleet.setOperator(UUID_1, carol); + + assertEq(bondToken.balanceOf(bob), bobBefore + tierBonds); + assertEq(bondToken.balanceOf(carol), carolBefore - tierBonds); + } + + function test_setOperator_clearOperator_refundsToOwner() public { + vm.prank(alice); + fleet.registerFleetLocal(UUID_1, US, ADMIN_CA, 2); + + vm.prank(alice); + fleet.setOperator(UUID_1, bob); + + uint256 tierBonds = fleet.tierBond(2, false); + uint256 aliceBefore = bondToken.balanceOf(alice); + uint256 bobBefore = bondToken.balanceOf(bob); + + // Clear operator (set to address(0)) + vm.prank(alice); + fleet.setOperator(UUID_1, address(0)); + + assertEq(bondToken.balanceOf(bob), bobBefore + tierBonds); + assertEq(bondToken.balanceOf(alice), aliceBefore - tierBonds); + assertEq(fleet.operatorOf(UUID_1), alice); // defaults to owner again + } + + function test_RevertIf_setOperator_notUuidOwner() public { + vm.prank(alice); + fleet.registerFleetLocal(UUID_1, US, ADMIN_CA, 0); + + vm.prank(bob); + vm.expectRevert(FleetIdentity.NotUuidOwner.selector); + fleet.setOperator(UUID_1, carol); + } + + function test_setOperator_ownedOnly() public { + vm.prank(alice); + fleet.claimUuid(UUID_1, address(0)); + + // setOperator now works for owned-only UUIDs + vm.prank(alice); + fleet.setOperator(UUID_1, bob); + assertEq(fleet.operatorOf(UUID_1), bob); + } + + function test_setOperator_country() public { + // Register at tier 2 + vm.prank(alice); + fleet.registerFleetCountry(UUID_1, US, 2); + + uint256 aliceAfterReg = bondToken.balanceOf(alice); + uint256 bobBefore = bondToken.balanceOf(bob); + + // Now set operator - bob pays full tier bonds to alice + vm.prank(alice); + fleet.setOperator(UUID_1, bob); + + uint256 tierBonds = fleet.tierBond(2, true); + assertEq(bondToken.balanceOf(alice), aliceAfterReg + tierBonds); + assertEq(bondToken.balanceOf(bob), bobBefore - tierBonds); + assertEq(fleet.operatorOf(UUID_1), bob); + assertEq(fleet.uuidOwner(UUID_1), alice); + } + + function test_setOperator_local() public { + // Register at tier 2 + vm.prank(alice); + fleet.registerFleetLocal(UUID_1, US, ADMIN_CA, 2); + + uint256 aliceAfterReg = bondToken.balanceOf(alice); + uint256 bobBefore = bondToken.balanceOf(bob); + + // Now set operator - bob pays full tier bonds to alice + vm.prank(alice); + fleet.setOperator(UUID_1, bob); + + uint256 tierBonds = fleet.tierBond(2, false); + assertEq(bondToken.balanceOf(alice), aliceAfterReg + tierBonds); + assertEq(bondToken.balanceOf(bob), bobBefore - tierBonds); + assertEq(fleet.operatorOf(UUID_1), bob); + } + + function test_operatorCanPromote() public { + // Register then set operator + vm.prank(alice); + fleet.registerFleetLocal(UUID_1, US, ADMIN_CA, 0); + vm.prank(alice); + fleet.setOperator(UUID_1, bob); + + uint256 bobBefore = bondToken.balanceOf(bob); + uint256 tokenId = _tokenId(UUID_1, _makeAdminRegion(US, ADMIN_CA)); + + vm.prank(bob); + fleet.promote(tokenId); + + assertEq(fleet.fleetTier(tokenId), 1); + // Bob paid the tier difference + uint256 tierDiff = fleet.tierBond(1, false) - fleet.tierBond(0, false); + assertEq(bondToken.balanceOf(bob), bobBefore - tierDiff); + } + + function test_operatorCanDemote() public { + // Register then set operator + vm.prank(alice); + fleet.registerFleetLocal(UUID_1, US, ADMIN_CA, 2); + vm.prank(alice); + fleet.setOperator(UUID_1, bob); + + uint256 bobBefore = bondToken.balanceOf(bob); + uint256 tokenId = _tokenId(UUID_1, _makeAdminRegion(US, ADMIN_CA)); + + vm.prank(bob); + fleet.reassignTier(tokenId, 0); + + assertEq(fleet.fleetTier(tokenId), 0); + // Bob gets tier difference refunded + uint256 tierDiff = fleet.tierBond(2, false) - fleet.tierBond(0, false); + assertEq(bondToken.balanceOf(bob), bobBefore + tierDiff); + } + + function test_RevertIf_ownerCannotPromoteWhenOperatorSet() public { + vm.prank(alice); + uint256 tokenId = fleet.registerFleetLocal(UUID_1, US, ADMIN_CA, 0); + vm.prank(alice); + fleet.setOperator(UUID_1, bob); + + vm.prank(alice); + vm.expectRevert(FleetIdentity.NotOperator.selector); + fleet.promote(tokenId); + } + + function test_operatorCanBurnRegisteredToken() public { + vm.prank(alice); + uint256 tokenId = fleet.registerFleetLocal(UUID_1, US, ADMIN_CA, 2); + vm.prank(alice); + fleet.setOperator(UUID_1, bob); + + uint256 bobBefore = bondToken.balanceOf(bob); + + // Operator (bob) burns the registered token + vm.prank(bob); + fleet.burn(tokenId); + + // Bob (operator) gets full tierBond. Alice gets owned-only token minted. + assertEq(bondToken.balanceOf(bob), bobBefore + fleet.tierBond(2, false)); + + // Verify owned-only token was minted to owner + uint256 ownedTokenId = uint256(uint128(UUID_1)); + assertEq(fleet.ownerOf(ownedTokenId), alice); + assertTrue(fleet.isOwnedOnly(UUID_1)); + } + + function test_RevertIf_ownerCannotBurnRegisteredWithOperator() public { + vm.prank(alice); + uint256 tokenId = fleet.registerFleetLocal(UUID_1, US, ADMIN_CA, 0); + vm.prank(alice); + fleet.setOperator(UUID_1, bob); + + // Owner cannot burn when there's a separate operator + vm.prank(alice); + vm.expectRevert(FleetIdentity.NotOperator.selector); + fleet.burn(tokenId); + } + + function test_burn_refundsOperatorAndPreservesOperator() public { + vm.prank(alice); + uint256 tokenId = fleet.registerFleetLocal(UUID_1, US, ADMIN_CA, 2); + vm.prank(alice); + fleet.setOperator(UUID_1, bob); + + uint256 bobBefore = bondToken.balanceOf(bob); + + // Operator burns registered token + vm.prank(bob); + fleet.burn(tokenId); + + // Bob (operator) gets full tier bond refunded + uint256 tierBond = fleet.tierBond(2, false); + assertEq(bondToken.balanceOf(bob), bobBefore + tierBond); + // Operator is preserved (still bob) on the new owned-only token + assertEq(fleet.operatorOf(UUID_1), bob); + assertTrue(fleet.isOwnedOnly(UUID_1)); + } + + function test_registerFromOwned_preservesOperator() public { + // Alice claims UUID with no operator + vm.prank(alice); + fleet.claimUuid(UUID_1, address(0)); + + // She registers to local (which removes owned-only token and creates registered token) + vm.prank(alice); + uint256 registeredToken = fleet.registerFleetLocal(UUID_1, US, ADMIN_CA, 0); + + assertEq(fleet.operatorOf(UUID_1), alice); + + // Now alice can set an operator + vm.prank(alice); + fleet.setOperator(UUID_1, bob); + assertEq(fleet.operatorOf(UUID_1), bob); + } + + function testFuzz_setOperator_tierExcessCalculation(uint8 tier1, uint8 tier2) public { + tier1 = uint8(bound(tier1, 0, 7)); + tier2 = uint8(bound(tier2, 0, 7)); + + // Register in two local regions + vm.prank(alice); + fleet.registerFleetLocal(UUID_1, US, ADMIN_CA, tier1); + vm.prank(alice); + fleet.registerFleetLocal(UUID_1, US, ADMIN_NY, tier2); + + uint256 expectedTierBonds = + fleet.tierBond(tier1, false) + + fleet.tierBond(tier2, false); + + uint256 aliceBefore = bondToken.balanceOf(alice); + uint256 bobBefore = bondToken.balanceOf(bob); + + vm.prank(alice); + fleet.setOperator(UUID_1, bob); + + assertEq(bondToken.balanceOf(alice), aliceBefore + expectedTierBonds); + assertEq(bondToken.balanceOf(bob), bobBefore - expectedTierBonds); + } + + // ══════════════════════════════════════════════ + // Additional Operator Management Tests + // ══════════════════════════════════════════════ + + function test_claimUuid_withOperator() public { + // Alice claims UUID with bob as operator + vm.prank(alice); + uint256 tokenId = fleet.claimUuid(UUID_1, bob); + + assertEq(fleet.operatorOf(UUID_1), bob); + assertEq(fleet.uuidOwner(UUID_1), alice); + assertEq(fleet.ownerOf(tokenId), alice); + } + + function test_claimUuid_operatorPersistsOnRegister() public { + // Alice claims UUID with bob as operator + vm.prank(alice); + fleet.claimUuid(UUID_1, bob); + + assertEq(fleet.operatorOf(UUID_1), bob); + + // When registering, OPERATOR (bob) must call and pays tier bond + uint256 bobBefore = bondToken.balanceOf(bob); + + vm.prank(bob); + fleet.registerFleetLocal(UUID_1, US, ADMIN_CA, 2); + + uint256 tierBond = fleet.tierBond(2, false); + assertEq(bondToken.balanceOf(bob), bobBefore - tierBond); + assertEq(fleet.operatorOf(UUID_1), bob); + } + + function test_claimUuid_emitsEventWithOperator() public { + vm.expectEmit(true, true, false, true); + emit FleetIdentity.UuidClaimed(alice, UUID_1, bob); + + vm.prank(alice); + fleet.claimUuid(UUID_1, bob); + } + + function test_claimUuid_withMsgSenderAsOperator() public { + // Using msg.sender should normalize to address(0) internally + vm.prank(alice); + fleet.claimUuid(UUID_1, alice); + + // operatorOf should return owner when stored operator is address(0) + assertEq(fleet.operatorOf(UUID_1), alice); + assertEq(fleet.uuidOperator(UUID_1), address(0)); // stored as 0 + } + + function test_setOperator_ownedOnly_operatorPreservedOnRegister() public { + // Claim UUID in owned-only mode + vm.prank(alice); + fleet.claimUuid(UUID_1, address(0)); + + // Set operator while owned-only + vm.prank(alice); + fleet.setOperator(UUID_1, bob); + + uint256 bobBefore = bondToken.balanceOf(bob); + + // Register - OPERATOR (bob) must call and pays tier bond + vm.prank(bob); + fleet.registerFleetLocal(UUID_1, US, ADMIN_CA, 2); + + assertEq(fleet.operatorOf(UUID_1), bob); + uint256 tierBond = fleet.tierBond(2, false); + assertEq(bondToken.balanceOf(bob), bobBefore - tierBond); + } + + function test_burn_lastToken_preservesOperator() public { + // Register and set operator + vm.prank(alice); + uint256 tokenId = fleet.registerFleetLocal(UUID_1, US, ADMIN_CA, 2); + vm.prank(alice); + fleet.setOperator(UUID_1, bob); + + // Operator burns the last registered token -> transitions to owned-only + vm.prank(bob); + fleet.burn(tokenId); + + // Operator should still be bob + assertEq(fleet.operatorOf(UUID_1), bob); + assertTrue(fleet.isOwnedOnly(UUID_1)); + } + + function test_operatorNotChangedOnMultiRegionRegistration() public { + // Register first region and set operator + vm.prank(alice); + fleet.registerFleetLocal(UUID_1, US, ADMIN_CA, 0); + vm.prank(alice); + fleet.setOperator(UUID_1, bob); + + uint256 bobBefore = bondToken.balanceOf(bob); + + // Register second region - OPERATOR (bob) must call and pays tier bond + vm.prank(bob); + fleet.registerFleetLocal(UUID_1, US, ADMIN_NY, 2); + + assertEq(fleet.operatorOf(UUID_1), bob); + + // Bob pays full tier bond for new region + uint256 tierBond = fleet.tierBond(2, false); + assertEq(bondToken.balanceOf(bob), bobBefore - tierBond); + } + + function test_freshRegistration_ownerIsOperator() public { + // Fresh registration without claim - owner pays BASE_BOND + tierBond + uint256 aliceBefore = bondToken.balanceOf(alice); + + vm.prank(alice); + fleet.registerFleetLocal(UUID_1, US, ADMIN_CA, 2); + + // Owner is operator when fresh registration + assertEq(fleet.operatorOf(UUID_1), alice); + assertEq(fleet.uuidOperator(UUID_1), address(0)); // stored as 0 + + // Owner paid BASE_BOND + tierBond + uint256 fullBond = BASE_BOND + fleet.tierBond(2, false); + assertEq(bondToken.balanceOf(alice), aliceBefore - fullBond); + } + + function test_RevertIf_setOperator_notRegistered() public { + // UUID not registered at all - uuidOwner is address(0), so NotUuidOwner reverts first + vm.prank(alice); + vm.expectRevert(FleetIdentity.NotUuidOwner.selector); + fleet.setOperator(UUID_1, bob); + } + + function test_operatorCanPromoteAfterOwnerTransfersOwnedToken() public { + // Alice claims with bob as operator + vm.prank(alice); + fleet.claimUuid(UUID_1, bob); + + uint256 ownedTokenId = uint256(uint128(UUID_1)); + + // Alice transfers owned token to carol + vm.prank(alice); + fleet.transferFrom(alice, carol, ownedTokenId); + + // Carol is now owner (uuidOwner transferred with token) + assertEq(fleet.uuidOwner(UUID_1), carol); + + // Bob (operator) registers - only operator can register owned UUID + uint256 bobBefore = bondToken.balanceOf(bob); + vm.prank(bob); + fleet.registerFleetLocal(UUID_1, US, ADMIN_CA, 1); + + // Bob paid full tier bond (owner already paid BASE_BOND via claim) + uint256 tierBond = fleet.tierBond(1, false); + assertEq(bondToken.balanceOf(bob), bobBefore - tierBond); + + // Bob can promote as operator + uint256 tokenId = fleet.computeTokenId(UUID_1, fleet.makeAdminRegion(US, ADMIN_CA)); + + vm.prank(bob); + fleet.promote(tokenId); + + assertEq(fleet.fleetTier(tokenId), 2); + } + + function test_burnWithOperator_transitionsToOwnedOnly() public { + // Fresh registration at tier 2 + vm.prank(alice); + uint256 tokenId = fleet.registerFleetLocal(UUID_1, US, ADMIN_CA, 2); + + // Set operator + vm.prank(alice); + fleet.setOperator(UUID_1, bob); + + uint256 bobBefore = bondToken.balanceOf(bob); + + // OPERATOR burns -> transitions to owned-only, bob gets tier bond refund + vm.prank(bob); + fleet.burn(tokenId); + + assertEq(bondToken.balanceOf(bob), bobBefore + fleet.tierBond(2, false)); + + // Owned-only token minted to owner + uint256 ownedTokenId = uint256(uint128(UUID_1)); + assertEq(fleet.ownerOf(ownedTokenId), alice); + assertTrue(fleet.isOwnedOnly(UUID_1)); + } +} diff --git a/test/FleetIdentityFairness.t.sol b/test/FleetIdentityFairness.t.sol new file mode 100644 index 00000000..4b67363e --- /dev/null +++ b/test/FleetIdentityFairness.t.sol @@ -0,0 +1,562 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.24; + +import {Test} from "forge-std/Test.sol"; +import {FleetIdentity} from "../src/swarms/FleetIdentity.sol"; +import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; + +/// @dev Minimal ERC-20 mock with public mint for testing. +contract MockERC20Fairness is ERC20 { + constructor() ERC20("Mock Bond Token", "MBOND") {} + + function mint(address to, uint256 amount) external { + _mint(to, amount); + } +} + +/** + * @title FleetIdentityFairness Tests + * @notice Economic fairness analysis for FleetIdentity bundle allocation. + * + * @dev **Fairness Philosophy - Economic Advantage Model** + * + * The FleetIdentity contract uses a simple tier-descent algorithm: + * - Iterate from highest tier to lowest + * - At each tier: include local fleets first, then country fleets + * - Stop when bundle is full (20 slots) + * + * **Economic Fairness via COUNTRY_BOND_MULTIPLIER (16×)** + * + * Country fleets pay 16× more than local fleets at the same tier: + * - Local tier 0: BASE_BOND * 1 = 100 NODL + * - Country tier 0: BASE_BOND * 16 = 1600 NODL + * - Local tier 4: BASE_BOND * 16 = 1600 NODL (same cost!) + * + * This means a local player can reach tier 4 for the same cost a country player + * pays for tier 0. The 16× multiplier provides significant economic advantage to locals: + * + * | Tier | Local Bond | Country Bond | Country Overpay vs Local Same Tier | + * |------|------------|--------------|-----------------------------------| + * | 0 | 100 NODL | 1600 NODL | 16× | + * | 1 | 200 NODL | 3200 NODL | 16× | + * | 2 | 400 NODL | 6400 NODL | 16× | + * | 3 | 800 NODL | 12800 NODL | 16× | + * + * **Priority Rules** + * + * 1. Higher tier always wins (regardless of level) + * 2. Within same tier: local beats country + * 3. Within same tier + level: earlier registration wins + * + * **Whale Attack Analysis** + * + * A country whale trying to dominate must pay significantly more: + * - To fill 10 country slots at tier 3: 10 × 12800 NODL = 128,000 NODL + * - 10 locals could counter at tier 3 for: 10 × 800 NODL = 8,000 NODL + * - Whale pays 16× more to compete at the same tier level + */ +contract FleetIdentityFairnessTest is Test { + MockERC20Fairness bondToken; + + // Test addresses representing different market participants + address[] localPlayers; + address[] countryPlayers; + address whale; + + uint256 constant BASE_BOND = 100 ether; + uint256 constant NUM_LOCAL_PLAYERS = 20; + uint256 constant NUM_COUNTRY_PLAYERS = 10; + + // Test country and admin areas + uint16 constant COUNTRY_US = 840; + uint16[] adminAreas; + uint256 constant NUM_ADMIN_AREAS = 5; + + function setUp() public { + bondToken = new MockERC20Fairness(); + + // Create test players + whale = address(0xABCDEF); + for (uint256 i = 0; i < NUM_LOCAL_PLAYERS; i++) { + localPlayers.push(address(uint160(0x1000 + i))); + } + for (uint256 i = 0; i < NUM_COUNTRY_PLAYERS; i++) { + countryPlayers.push(address(uint160(0x2000 + i))); + } + + // Create admin areas + for (uint16 i = 1; i <= NUM_ADMIN_AREAS; i++) { + adminAreas.push(i); + } + + // Fund all players generously + uint256 funding = 1_000_000_000_000 ether; + bondToken.mint(whale, funding); + for (uint256 i = 0; i < NUM_LOCAL_PLAYERS; i++) { + bondToken.mint(localPlayers[i], funding); + } + for (uint256 i = 0; i < NUM_COUNTRY_PLAYERS; i++) { + bondToken.mint(countryPlayers[i], funding); + } + } + + // ══════════════════════════════════════════════════════════════════════════════════ + // Helper Functions + // ══════════════════════════════════════════════════════════════════════════════════ + + function _deployFleet() internal returns (FleetIdentity) { + FleetIdentity fleet = new FleetIdentity(address(bondToken), BASE_BOND); + + // Approve all players + vm.prank(whale); + bondToken.approve(address(fleet), type(uint256).max); + for (uint256 i = 0; i < localPlayers.length; i++) { + vm.prank(localPlayers[i]); + bondToken.approve(address(fleet), type(uint256).max); + } + for (uint256 i = 0; i < countryPlayers.length; i++) { + vm.prank(countryPlayers[i]); + bondToken.approve(address(fleet), type(uint256).max); + } + + return fleet; + } + + function _uuid(uint256 seed) internal pure returns (bytes16) { + return bytes16(keccak256(abi.encodePacked("fleet-fairness-", seed))); + } + + function _makeAdminRegion(uint16 cc, uint16 admin) internal pure returns (uint32) { + return (uint32(cc) << 10) | uint32(admin); + } + + /// @dev Count how many slots in a bundle are from country vs local registrations + function _countBundleComposition(FleetIdentity fleet, uint16 cc, uint16 admin) + internal + view + returns (uint256 localCount, uint256 countryCount) + { + (bytes16[] memory uuids, uint256 count) = fleet.buildHighestBondedUuidBundle(cc, admin); + uint32 countryRegion = uint32(cc); + + for (uint256 i = 0; i < count; i++) { + // Try to find token in country region first + uint256 countryTokenId = fleet.computeTokenId(uuids[i], countryRegion); + try fleet.ownerOf(countryTokenId) returns (address) { + countryCount++; + } catch { + localCount++; + } + } + } + + // ══════════════════════════════════════════════════════════════════════════════════ + // Scenario Tests: Priority & Economic Behavior + // ══════════════════════════════════════════════════════════════════════════════════ + + /** + * @notice Scenario A: Local-Heavy Market + * Many local players competing, few country players. + * Tests that locals correctly fill slots by tier-descent priority. + */ + function test_scenarioA_localHeavyMarket() public { + FleetIdentity fleet = _deployFleet(); + uint16 targetAdmin = adminAreas[0]; + + // 16 local players at tiers 0-3 (4 per tier due to TIER_CAPACITY) + for (uint256 i = 0; i < 16; i++) { + vm.prank(localPlayers[i % NUM_LOCAL_PLAYERS]); + fleet.registerFleetLocal(_uuid(1000 + i), COUNTRY_US, targetAdmin, i / 4); + } + + // 4 country players at tier 0 + for (uint256 i = 0; i < 4; i++) { + vm.prank(countryPlayers[i]); + fleet.registerFleetCountry(_uuid(2000 + i), COUNTRY_US, 0); + } + + (uint256 localCount, uint256 countryCount) = _countBundleComposition(fleet, COUNTRY_US, targetAdmin); + (, uint256 totalCount) = fleet.buildHighestBondedUuidBundle(COUNTRY_US, targetAdmin); + + emit log_string("=== Scenario A: Local-Heavy Market ==="); + emit log_named_uint("Total bundle size", totalCount); + emit log_named_uint("Local slots used", localCount); + emit log_named_uint("Country slots used", countryCount); + + // With tier-descent priority, all 16 locals fill first, then 4 country + assertEq(localCount, 16, "All 16 locals should be included"); + assertEq(countryCount, 4, "All 4 country should fill remaining slots"); + assertEq(totalCount, 20, "Bundle should be full"); + } + + /** + * @notice Scenario B: Country-Heavy Market + * Few local players, many country players at higher tiers. + * Tests that higher-tier country beats lower-tier local. + */ + function test_scenarioB_countryHighTierDominance() public { + FleetIdentity fleet = _deployFleet(); + uint16 targetAdmin = adminAreas[0]; + + // 4 local players at tier 0 + for (uint256 i = 0; i < 4; i++) { + vm.prank(localPlayers[i]); + fleet.registerFleetLocal(_uuid(1000 + i), COUNTRY_US, targetAdmin, 0); + } + + // 12 country players at tiers 1-3 (4 per tier) + // These are at HIGHER tiers, so they come first in bundle + for (uint256 i = 0; i < 12; i++) { + vm.prank(countryPlayers[i % NUM_COUNTRY_PLAYERS]); + fleet.registerFleetCountry(_uuid(2000 + i), COUNTRY_US, (i / 4) + 1); + } + + (uint256 localCount, uint256 countryCount) = _countBundleComposition(fleet, COUNTRY_US, targetAdmin); + (, uint256 totalCount) = fleet.buildHighestBondedUuidBundle(COUNTRY_US, targetAdmin); + + emit log_string("=== Scenario B: Country High-Tier Dominance ==="); + emit log_named_uint("Total bundle size", totalCount); + emit log_named_uint("Local slots used", localCount); + emit log_named_uint("Country slots used", countryCount); + + // Country at tiers 1-3 comes before locals at tier 0 + assertEq(countryCount, 12, "All 12 country (higher tiers) included first"); + assertEq(localCount, 4, "Tier-0 locals fill remaining slots"); + assertEq(totalCount, 16, "Total should equal all registered fleets"); + } + + /** + * @notice Scenario C: Same-Tier Competition + * Locals and country at the same tier. + * Tests that locals get priority within the same tier. + */ + function test_scenarioC_sameTierLocalPriority() public { + FleetIdentity fleet = _deployFleet(); + uint16 targetAdmin = adminAreas[0]; + + // 4 local at tier 0 + for (uint256 i = 0; i < 4; i++) { + vm.prank(localPlayers[i]); + fleet.registerFleetLocal(_uuid(1000 + i), COUNTRY_US, targetAdmin, 0); + } + + // 4 country at tier 0 (same tier) + for (uint256 i = 0; i < 4; i++) { + vm.prank(countryPlayers[i]); + fleet.registerFleetCountry(_uuid(2000 + i), COUNTRY_US, 0); + } + + (bytes16[] memory uuids, uint256 count) = fleet.buildHighestBondedUuidBundle(COUNTRY_US, targetAdmin); + + emit log_string("=== Scenario C: Same-Tier Local Priority ==="); + emit log_named_uint("Total bundle size", count); + + // First 4 should be locals (priority within same tier) + for (uint256 i = 0; i < 4; i++) { + assertEq(uuids[i], _uuid(1000 + i), "Locals should come first"); + } + // Next 4 should be country + for (uint256 i = 0; i < 4; i++) { + assertEq(uuids[4 + i], _uuid(2000 + i), "Country should follow locals"); + } + } + + /** + * @notice Scenario D: Country Whale at High Tier + * Single whale registers many high-tier country fleets. + * Tests that whale can dominate IF they outbid locals on tier level. + */ + function test_scenarioD_countryWhaleHighTier() public { + FleetIdentity fleet = _deployFleet(); + uint16 targetAdmin = adminAreas[0]; + + // 12 locals at tiers 0-2 (4 per tier) + for (uint256 i = 0; i < 12; i++) { + vm.prank(localPlayers[i]); + fleet.registerFleetLocal(_uuid(1000 + i), COUNTRY_US, targetAdmin, i / 4); + } + + // Whale registers 8 country fleets at tiers 3-4 (4 per tier due to TIER_CAPACITY) + // This is above all locals (tiers 0-2) + for (uint256 i = 0; i < 8; i++) { + vm.prank(whale); + fleet.registerFleetCountry(_uuid(3000 + i), COUNTRY_US, 3 + (i / 4)); + } + + (, uint256 count) = fleet.buildHighestBondedUuidBundle(COUNTRY_US, targetAdmin); + (uint256 localCount, uint256 countryCount) = _countBundleComposition(fleet, COUNTRY_US, targetAdmin); + + emit log_string("=== Scenario D: Country Whale at High Tier ==="); + emit log_named_uint("Total bundle size", count); + emit log_named_uint("Local slots", localCount); + emit log_named_uint("Country slots", countryCount); + + // Whale's tier-3/4 country fleets come first (highest tiers) + // Then locals at tiers 0-2 fill remaining slots + assertEq(countryCount, 8, "Whale's 8 high-tier country fleets included"); + assertEq(localCount, 12, "All 12 locals at lower tiers included"); + assertEq(count, 20, "Bundle full"); + } + + /** + * @notice Scenario E: Locals Counter Whale by Matching Tier + * Shows that locals can economically counter a country whale. + */ + function test_scenarioE_localsCounterWhale() public { + FleetIdentity fleet = _deployFleet(); + uint16 targetAdmin = adminAreas[0]; + + // Whale registers 4 country fleets at tier 3 + // Cost: 4 × (BASE_BOND × 8 × 8) = 4 × 6400 = 25,600 NODL + for (uint256 i = 0; i < 4; i++) { + vm.prank(whale); + fleet.registerFleetCountry(_uuid(3000 + i), COUNTRY_US, 3); + } + + // 4 locals match at tier 3 (same priority, but cheaper!) + // Cost: 4 × (BASE_BOND × 8) = 4 × 800 = 3,200 NODL + for (uint256 i = 0; i < 4; i++) { + vm.prank(localPlayers[i]); + fleet.registerFleetLocal(_uuid(1000 + i), COUNTRY_US, targetAdmin, 3); + } + + (bytes16[] memory uuids, uint256 count) = fleet.buildHighestBondedUuidBundle(COUNTRY_US, targetAdmin); + + emit log_string("=== Scenario E: Locals Counter Whale ==="); + emit log_named_uint("Total bundle size", count); + + // Locals get priority at tier 3 (same tier, local-first) + for (uint256 i = 0; i < 4; i++) { + assertEq(uuids[i], _uuid(1000 + i), "Locals come first at same tier"); + } + for (uint256 i = 0; i < 4; i++) { + assertEq(uuids[4 + i], _uuid(3000 + i), "Country follows at same tier"); + } + + // Calculate cost ratio + uint256 whaleCost = 4 * fleet.tierBond(3, true); // 25,600 NODL + uint256 localCost = 4 * fleet.tierBond(3, false); // 3,200 NODL + + emit log_named_uint("Whale total cost (ether)", whaleCost / 1 ether); + emit log_named_uint("Locals total cost (ether)", localCost / 1 ether); + emit log_named_uint("Whale overpay factor", whaleCost / localCost); + + assertEq(whaleCost / localCost, 16, "Whale pays 16x more for same tier"); + } + + // ══════════════════════════════════════════════════════════════════════════════════ + // Economic Metrics & Analysis + // ══════════════════════════════════════════════════════════════════════════════════ + + /** + * @notice Verify the 16× economic advantage constants. + */ + function test_economicAdvantage_8xMultiplier() public { + FleetIdentity fleet = _deployFleet(); + + // Verify multiplier + assertEq(fleet.COUNTRY_BOND_MULTIPLIER(), 16, "Multiplier should be 16"); + + // At every tier, country pays exactly 16× local + for (uint256 tier = 0; tier < 6; tier++) { + uint256 localBond = fleet.tierBond(tier, false); + uint256 countryBond = fleet.tierBond(tier, true); + assertEq(countryBond, localBond * 16, "Country should pay 16x at every tier"); + } + } + + /** + * @notice Demonstrate that a local at tier N+4 costs the same as country at tier N. + */ + function test_economicAdvantage_localTierEquivalence() public { + FleetIdentity fleet = _deployFleet(); + + // Local tier 4 = Country tier 0 (2^4 = 16) + assertEq( + fleet.tierBond(4, false), + fleet.tierBond(0, true), + "Local tier 4 should equal country tier 0" + ); + + // Local tier 5 = Country tier 1 + assertEq( + fleet.tierBond(5, false), + fleet.tierBond(1, true), + "Local tier 5 should equal country tier 1" + ); + + // Local tier 6 = Country tier 2 + assertEq( + fleet.tierBond(6, false), + fleet.tierBond(2, true), + "Local tier 6 should equal country tier 2" + ); + + emit log_string("=== Local Tier Equivalence ==="); + emit log_string("Local tier N+4 costs the same as Country tier N"); + emit log_string("This gives locals a 4-tier economic advantage"); + } + + /** + * @notice Analyze country registration efficiency across admin areas. + */ + function test_economicAdvantage_multiRegionEfficiency() public { + FleetIdentity fleet = _deployFleet(); + + // Single country registration covers ALL admin areas + uint256 countryBond = fleet.tierBond(0, true); // 800 NODL + + // To cover N admin areas locally, costs N × local_bond + uint256 localPerArea = fleet.tierBond(0, false); // 100 NODL + + emit log_string("=== Multi-Region Efficiency Analysis ==="); + emit log_named_uint("Country tier-0 bond (ether)", countryBond / 1 ether); + emit log_named_uint("Local tier-0 bond per area (ether)", localPerArea / 1 ether); + + // Country is MORE efficient when covering > 8 admin areas + // Break-even: 8 local registrations = 1 country registration + uint256 breakEvenAreas = countryBond / localPerArea; + emit log_named_uint("Break-even admin areas", breakEvenAreas); + + assertEq(breakEvenAreas, 16, "Country efficient for 16+ admin areas"); + } + + /** + * @notice Bond escalation analysis showing geometric growth. + */ + function test_bondEscalationAnalysis() public { + FleetIdentity fleet = _deployFleet(); + + emit log_string(""); + emit log_string("=== BOND ESCALATION ANALYSIS ==="); + emit log_string(""); + emit log_string("Tier | Local Bond (ether) | Country Bond (ether)"); + emit log_string("-----+--------------------+---------------------"); + + for (uint256 tier = 0; tier <= 6; tier++) { + uint256 localBond = fleet.tierBond(tier, false); + uint256 countryBond = fleet.tierBond(tier, true); + + // Verify geometric progression (2× per tier) + if (tier > 0) { + assertEq(localBond, fleet.tierBond(tier - 1, false) * 2, "Local should double each tier"); + assertEq(countryBond, fleet.tierBond(tier - 1, true) * 2, "Country should double each tier"); + } + } + } + + // ══════════════════════════════════════════════════════════════════════════════════ + // Invariant Tests + // ══════════════════════════════════════════════════════════════════════════════════ + + /** + * @notice CRITICAL: Core invariants that must ALWAYS hold. + */ + function test_invariant_coreGuarantees() public { + FleetIdentity fleet = _deployFleet(); + + // Invariant 1: Country multiplier is exactly 16 + assertEq(fleet.COUNTRY_BOND_MULTIPLIER(), 16, "INVARIANT: Country multiplier must be 16"); + + // Invariant 2: Tier capacity allows fair competition + assertEq(fleet.TIER_CAPACITY(), 10, "INVARIANT: Tier capacity must be 10"); + + // Invariant 3: Bundle size reasonable for discovery + assertEq(fleet.MAX_BONDED_UUID_BUNDLE_SIZE(), 20, "INVARIANT: Bundle size must be 20"); + + // Invariant 4: Bond doubles per tier (geometric) + for (uint256 t = 1; t <= 5; t++) { + assertEq( + fleet.tierBond(t, false), + fleet.tierBond(t - 1, false) * 2, + "INVARIANT: Bond must double per tier" + ); + } + + emit log_string("[PASS] All core invariants verified"); + } + + /** + * @notice Bundle always respects tier-descent priority. + */ + function test_invariant_tierDescentPriority() public { + FleetIdentity fleet = _deployFleet(); + uint16 targetAdmin = adminAreas[0]; + + // Mixed setup: locals at tier 1, country at tier 2 + for (uint256 i = 0; i < 4; i++) { + vm.prank(localPlayers[i]); + fleet.registerFleetLocal(_uuid(1000 + i), COUNTRY_US, targetAdmin, 1); + } + for (uint256 i = 0; i < 4; i++) { + vm.prank(countryPlayers[i]); + fleet.registerFleetCountry(_uuid(2000 + i), COUNTRY_US, 2); + } + + (bytes16[] memory uuids, uint256 count) = fleet.buildHighestBondedUuidBundle(COUNTRY_US, targetAdmin); + + // Tier 2 (country) must come before tier 1 (local) - higher tier wins + for (uint256 i = 0; i < 4; i++) { + assertEq(uuids[i], _uuid(2000 + i), "INVARIANT: Higher tier must come first"); + } + for (uint256 i = 0; i < 4; i++) { + assertEq(uuids[4 + i], _uuid(1000 + i), "Lower tier follows"); + } + + assertEq(count, 8); + } + + // ══════════════════════════════════════════════════════════════════════════════════ + // Fuzz Tests + // ══════════════════════════════════════════════════════════════════════════════════ + + /** + * @notice Fuzz test to verify bundle properties across random market conditions. + */ + function testFuzz_bundleProperties(uint8 numLocals, uint8 numCountry) public { + // Bound inputs to reasonable ranges + numLocals = uint8(bound(numLocals, 1, 16)); + numCountry = uint8(bound(numCountry, 1, 12)); + + FleetIdentity fleet = _deployFleet(); + uint16 targetAdmin = adminAreas[0]; + + // Register local players (spread across tiers for variety) + for (uint256 i = 0; i < numLocals; i++) { + vm.prank(localPlayers[i % NUM_LOCAL_PLAYERS]); + fleet.registerFleetLocal(_uuid(8000 + i), COUNTRY_US, targetAdmin, i / 4); + } + + // Register country players + for (uint256 i = 0; i < numCountry; i++) { + vm.prank(countryPlayers[i % NUM_COUNTRY_PLAYERS]); + fleet.registerFleetCountry(_uuid(9000 + i), COUNTRY_US, i / 4); + } + + // Get bundle + (, uint256 count) = fleet.buildHighestBondedUuidBundle(COUNTRY_US, targetAdmin); + + // Properties that must always hold: + + // 1. Bundle never exceeds max size + assertLe(count, fleet.MAX_BONDED_UUID_BUNDLE_SIZE(), "Bundle must not exceed max"); + + // 2. Bundle includes as many as possible (up to registered count) + uint256 totalRegistered = uint256(numLocals) + uint256(numCountry); + uint256 expectedMax = totalRegistered < 20 ? totalRegistered : 20; + assertEq(count, expectedMax, "Bundle should maximize utilization"); + } + + /** + * @notice Fuzz that 16x multiplier always holds at any tier. + */ + function testFuzz_constantMultiplier(uint8 tier) public { + tier = uint8(bound(tier, 0, 20)); + FleetIdentity fleet = _deployFleet(); + + uint256 localBond = fleet.tierBond(tier, false); + uint256 countryBond = fleet.tierBond(tier, true); + + assertEq(countryBond, localBond * 16, "16x multiplier must hold at all tiers"); + } +} diff --git a/test/ServiceProvider.t.sol b/test/ServiceProvider.t.sol new file mode 100644 index 00000000..fd55d144 --- /dev/null +++ b/test/ServiceProvider.t.sol @@ -0,0 +1,159 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.24; + +import {Test} from "forge-std/Test.sol"; +import {ServiceProvider} from "../src/swarms/ServiceProvider.sol"; + +contract ServiceProviderTest is Test { + ServiceProvider provider; + + address alice = address(0xA); + address bob = address(0xB); + + string constant URL_1 = "https://backend.swarm.example.com/api/v1"; + string constant URL_2 = "https://relay.nodle.network:8443"; + string constant URL_3 = "https://provider.third.io"; + + event ProviderRegistered(address indexed owner, string url, uint256 indexed tokenId); + event ProviderBurned(address indexed owner, uint256 indexed tokenId); + + function setUp() public { + provider = new ServiceProvider(); + } + + // ============================== + // registerProvider + // ============================== + + function test_registerProvider_mintsAndStoresURL() public { + vm.prank(alice); + uint256 tokenId = provider.registerProvider(URL_1); + + assertEq(provider.ownerOf(tokenId), alice); + assertEq(keccak256(bytes(provider.providerUrls(tokenId))), keccak256(bytes(URL_1))); + } + + function test_registerProvider_deterministicTokenId() public { + vm.prank(alice); + uint256 tokenId = provider.registerProvider(URL_1); + + assertEq(tokenId, uint256(keccak256(bytes(URL_1)))); + } + + function test_registerProvider_emitsEvent() public { + uint256 expectedTokenId = uint256(keccak256(bytes(URL_1))); + + vm.expectEmit(true, true, true, true); + emit ProviderRegistered(alice, URL_1, expectedTokenId); + + vm.prank(alice); + provider.registerProvider(URL_1); + } + + function test_registerProvider_multipleProviders() public { + vm.prank(alice); + uint256 id1 = provider.registerProvider(URL_1); + + vm.prank(bob); + uint256 id2 = provider.registerProvider(URL_2); + + assertEq(provider.ownerOf(id1), alice); + assertEq(provider.ownerOf(id2), bob); + assertTrue(id1 != id2); + } + + function test_RevertIf_registerProvider_emptyURL() public { + vm.prank(alice); + vm.expectRevert(ServiceProvider.EmptyURL.selector); + provider.registerProvider(""); + } + + function test_RevertIf_registerProvider_duplicateURL() public { + vm.prank(alice); + provider.registerProvider(URL_1); + + vm.prank(bob); + vm.expectRevert(); // ERC721: token already minted + provider.registerProvider(URL_1); + } + + // ============================== + // burn + // ============================== + + function test_burn_deletesURLAndToken() public { + vm.prank(alice); + uint256 tokenId = provider.registerProvider(URL_1); + + vm.prank(alice); + provider.burn(tokenId); + + // URL mapping cleared + assertEq(bytes(provider.providerUrls(tokenId)).length, 0); + + // Token no longer exists + vm.expectRevert(); // ownerOf reverts for non-existent token + provider.ownerOf(tokenId); + } + + function test_burn_emitsEvent() public { + vm.prank(alice); + uint256 tokenId = provider.registerProvider(URL_1); + + vm.expectEmit(true, true, true, true); + emit ProviderBurned(alice, tokenId); + + vm.prank(alice); + provider.burn(tokenId); + } + + function test_RevertIf_burn_notOwner() public { + vm.prank(alice); + uint256 tokenId = provider.registerProvider(URL_1); + + vm.prank(bob); + vm.expectRevert(ServiceProvider.NotTokenOwner.selector); + provider.burn(tokenId); + } + + function test_burn_allowsReregistration() public { + vm.prank(alice); + uint256 tokenId = provider.registerProvider(URL_1); + + vm.prank(alice); + provider.burn(tokenId); + + // Same URL can now be registered by someone else + vm.prank(bob); + uint256 newTokenId = provider.registerProvider(URL_1); + + assertEq(newTokenId, tokenId); // Same deterministic ID + assertEq(provider.ownerOf(newTokenId), bob); + } + + // ============================== + // Fuzz Tests + // ============================== + + function testFuzz_registerProvider_anyValidURL(string calldata url) public { + vm.assume(bytes(url).length > 0); + + vm.prank(alice); + uint256 tokenId = provider.registerProvider(url); + + assertEq(tokenId, uint256(keccak256(bytes(url)))); + assertEq(provider.ownerOf(tokenId), alice); + } + + function testFuzz_burn_onlyOwner(address caller) public { + vm.assume(caller != alice); + vm.assume(caller != address(0)); + + vm.prank(alice); + uint256 tokenId = provider.registerProvider(URL_1); + + vm.prank(caller); + vm.expectRevert(ServiceProvider.NotTokenOwner.selector); + provider.burn(tokenId); + } +} diff --git a/test/SwarmRegistryL1.t.sol b/test/SwarmRegistryL1.t.sol new file mode 100644 index 00000000..a896ae0d --- /dev/null +++ b/test/SwarmRegistryL1.t.sol @@ -0,0 +1,1066 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.24; + +import {Test} from "forge-std/Test.sol"; +import "../src/swarms/SwarmRegistryL1.sol"; +import {FleetIdentity} from "../src/swarms/FleetIdentity.sol"; +import {ServiceProvider} from "../src/swarms/ServiceProvider.sol"; +import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; + +contract MockBondTokenL1 is ERC20 { + constructor() ERC20("Mock Bond", "MBOND") {} + + function mint(address to, uint256 amount) external { + _mint(to, amount); + } +} + +contract SwarmRegistryL1Test is Test { + SwarmRegistryL1 swarmRegistry; + FleetIdentity fleetContract; + ServiceProvider providerContract; + MockBondTokenL1 bondToken; + + address fleetOwner = address(0x1); + address providerOwner = address(0x2); + address caller = address(0x3); + + uint256 constant FLEET_BOND = 100 ether; + + // Region constants for fleet registration + uint16 constant US = 840; + uint16 constant ADMIN_CA = 6; // California + + event SwarmRegistered(uint256 indexed swarmId, bytes16 indexed fleetUuid, uint256 indexed providerId, address owner); + event SwarmStatusChanged(uint256 indexed swarmId, SwarmRegistryL1.SwarmStatus status); + event SwarmFilterUpdated(uint256 indexed swarmId, address indexed owner, uint32 filterSize); + event SwarmProviderUpdated(uint256 indexed swarmId, uint256 indexed oldProvider, uint256 indexed newProvider); + event SwarmDeleted(uint256 indexed swarmId, bytes16 indexed fleetUuid, address indexed owner); + event SwarmPurged(uint256 indexed swarmId, bytes16 indexed fleetUuid, address indexed purgedBy); + + function setUp() public { + bondToken = new MockBondTokenL1(); + fleetContract = new FleetIdentity(address(bondToken), FLEET_BOND); + providerContract = new ServiceProvider(); + swarmRegistry = new SwarmRegistryL1(address(fleetContract), address(providerContract)); + + // Fund fleet owner and approve + bondToken.mint(fleetOwner, 1_000_000 ether); + vm.prank(fleetOwner); + bondToken.approve(address(fleetContract), type(uint256).max); + } + + // ============================== + // Helpers + // ============================== + + function _registerFleet(address owner, bytes memory seed) internal returns (uint256) { + vm.prank(owner); + return fleetContract.registerFleetLocal(bytes16(keccak256(seed)), US, ADMIN_CA, 0); + } + + function _getFleetUuid(uint256 fleetId) internal pure returns (bytes16) { + return bytes16(uint128(fleetId)); + } + + function _registerProvider(address owner, string memory url) internal returns (uint256) { + vm.prank(owner); + return providerContract.registerProvider(url); + } + + function _registerSwarm( + address owner, + uint256 fleetId, + uint256 providerId, + bytes memory filter, + uint8 fpSize, + SwarmRegistryL1.TagType tagType + ) internal returns (uint256) { + bytes16 fleetUuid = _getFleetUuid(fleetId); + vm.prank(owner); + return swarmRegistry.registerSwarm(fleetUuid, providerId, filter, fpSize, tagType); + } + + function getExpectedValues(bytes memory tagId, uint256 m, uint8 fpSize) + public + pure + returns (uint32 h1, uint32 h2, uint32 h3, uint256 fp) + { + bytes32 h = keccak256(tagId); + h1 = uint32(uint256(h)) % uint32(m); + h2 = uint32(uint256(h) >> 32) % uint32(m); + h3 = uint32(uint256(h) >> 64) % uint32(m); + uint256 fpMask = (uint256(1) << fpSize) - 1; + fp = (uint256(h) >> 96) & fpMask; + } + + function _write16Bit(bytes memory data, uint256 slotIndex, uint16 value) internal pure { + uint256 bitOffset = slotIndex * 16; + uint256 byteOffset = bitOffset / 8; + data[byteOffset] = bytes1(uint8(value >> 8)); + data[byteOffset + 1] = bytes1(uint8(value)); + } + + function _write8Bit(bytes memory data, uint256 slotIndex, uint8 value) internal pure { + data[slotIndex] = bytes1(value); + } + + // ============================== + // Constructor + // ============================== + + function test_constructor_setsImmutables() public view { + assertEq(address(swarmRegistry.FLEET_CONTRACT()), address(fleetContract)); + assertEq(address(swarmRegistry.PROVIDER_CONTRACT()), address(providerContract)); + } + + function test_RevertIf_constructor_zeroFleetAddress() public { + vm.expectRevert(SwarmRegistryL1.InvalidSwarmData.selector); + new SwarmRegistryL1(address(0), address(providerContract)); + } + + function test_RevertIf_constructor_zeroProviderAddress() public { + vm.expectRevert(SwarmRegistryL1.InvalidSwarmData.selector); + new SwarmRegistryL1(address(fleetContract), address(0)); + } + + function test_RevertIf_constructor_bothZero() public { + vm.expectRevert(SwarmRegistryL1.InvalidSwarmData.selector); + new SwarmRegistryL1(address(0), address(0)); + } + + // ============================== + // registerSwarm — happy path + // ============================== + + function test_registerSwarm_basicFlow() public { + uint256 fleetId = _registerFleet(fleetOwner, "my-fleet"); + uint256 providerId = _registerProvider(providerOwner, "https://api.example.com"); + + uint256 swarmId = _registerSwarm( + fleetOwner, fleetId, providerId, new bytes(100), 16, SwarmRegistryL1.TagType.IBEACON_INCLUDES_MAC + ); + + // Swarm ID is deterministic hash of (fleetUuid, providerId, filter) + uint256 expectedId = swarmRegistry.computeSwarmId(_getFleetUuid(fleetId), providerId, new bytes(100)); + assertEq(swarmId, expectedId); + } + + function test_registerSwarm_storesMetadataCorrectly() public { + uint256 fleetId = _registerFleet(fleetOwner, "f1"); + uint256 providerId = _registerProvider(providerOwner, "url1"); + + uint256 swarmId = + _registerSwarm(fleetOwner, fleetId, providerId, new bytes(50), 8, SwarmRegistryL1.TagType.VENDOR_ID); + + ( + bytes16 storedFleetUuid, + uint256 storedProviderId, + address filterPointer, + uint8 storedFpSize, + SwarmRegistryL1.TagType storedTagType, + SwarmRegistryL1.SwarmStatus storedStatus + ) = swarmRegistry.swarms(swarmId); + + assertEq(storedFleetUuid, _getFleetUuid(fleetId)); + assertEq(storedProviderId, providerId); + assertTrue(filterPointer != address(0)); + assertEq(storedFpSize, 8); + assertEq(uint8(storedTagType), uint8(SwarmRegistryL1.TagType.VENDOR_ID)); + assertEq(uint8(storedStatus), uint8(SwarmRegistryL1.SwarmStatus.REGISTERED)); + } + + function test_registerSwarm_deterministicId() public { + uint256 fleetId = _registerFleet(fleetOwner, "f1"); + uint256 providerId = _registerProvider(providerOwner, "url1"); + + bytes memory filter = new bytes(32); + + uint256 expectedId = swarmRegistry.computeSwarmId(_getFleetUuid(fleetId), providerId, filter); + + uint256 swarmId = _registerSwarm(fleetOwner, fleetId, providerId, filter, 8, SwarmRegistryL1.TagType.GENERIC); + assertEq(swarmId, expectedId); + } + + function test_RevertIf_registerSwarm_duplicateSwarm() public { + uint256 fleetId = _registerFleet(fleetOwner, "f1"); + uint256 providerId = _registerProvider(providerOwner, "url1"); + + _registerSwarm(fleetOwner, fleetId, providerId, new bytes(32), 8, SwarmRegistryL1.TagType.GENERIC); + + vm.prank(fleetOwner); + vm.expectRevert(SwarmRegistryL1.SwarmAlreadyExists.selector); + swarmRegistry.registerSwarm(_getFleetUuid(fleetId), providerId, new bytes(32), 8, SwarmRegistryL1.TagType.GENERIC); + } + + function test_registerSwarm_emitsSwarmRegistered() public { + uint256 fleetId = _registerFleet(fleetOwner, "f1"); + uint256 providerId = _registerProvider(providerOwner, "url1"); + + bytes memory filter = new bytes(50); + uint256 expectedId = swarmRegistry.computeSwarmId(_getFleetUuid(fleetId), providerId, filter); + + vm.expectEmit(true, true, true, true); + emit SwarmRegistered(expectedId, _getFleetUuid(fleetId), providerId, fleetOwner); + + _registerSwarm(fleetOwner, fleetId, providerId, filter, 16, SwarmRegistryL1.TagType.GENERIC); + } + + function test_registerSwarm_linksUuidSwarms() public { + uint256 fleetId = _registerFleet(fleetOwner, "f1"); + uint256 providerId1 = _registerProvider(providerOwner, "url1"); + uint256 providerId2 = _registerProvider(providerOwner, "url2"); + + uint256 swarmId1 = + _registerSwarm(fleetOwner, fleetId, providerId1, new bytes(50), 8, SwarmRegistryL1.TagType.GENERIC); + uint256 swarmId2 = + _registerSwarm(fleetOwner, fleetId, providerId2, new bytes(50), 8, SwarmRegistryL1.TagType.GENERIC); + + assertEq(swarmRegistry.uuidSwarms(_getFleetUuid(fleetId), 0), swarmId1); + assertEq(swarmRegistry.uuidSwarms(_getFleetUuid(fleetId), 1), swarmId2); + } + + function test_registerSwarm_allTagTypes() public { + uint256 fleetId1 = _registerFleet(fleetOwner, "f1"); + uint256 fleetId2 = _registerFleet(fleetOwner, "f2"); + uint256 fleetId3 = _registerFleet(fleetOwner, "f3"); + uint256 fleetId4 = _registerFleet(fleetOwner, "f4"); + uint256 providerId = _registerProvider(providerOwner, "url"); + + uint256 s1 = _registerSwarm( + fleetOwner, fleetId1, providerId, new bytes(32), 8, SwarmRegistryL1.TagType.IBEACON_PAYLOAD_ONLY + ); + uint256 s2 = _registerSwarm( + fleetOwner, fleetId2, providerId, new bytes(32), 8, SwarmRegistryL1.TagType.IBEACON_INCLUDES_MAC + ); + uint256 s3 = + _registerSwarm(fleetOwner, fleetId3, providerId, new bytes(32), 8, SwarmRegistryL1.TagType.VENDOR_ID); + uint256 s4 = _registerSwarm(fleetOwner, fleetId4, providerId, new bytes(32), 8, SwarmRegistryL1.TagType.GENERIC); + + (,,,, SwarmRegistryL1.TagType t1,) = swarmRegistry.swarms(s1); + (,,,, SwarmRegistryL1.TagType t2,) = swarmRegistry.swarms(s2); + (,,,, SwarmRegistryL1.TagType t3,) = swarmRegistry.swarms(s3); + (,,,, SwarmRegistryL1.TagType t4,) = swarmRegistry.swarms(s4); + + assertEq(uint8(t1), uint8(SwarmRegistryL1.TagType.IBEACON_PAYLOAD_ONLY)); + assertEq(uint8(t2), uint8(SwarmRegistryL1.TagType.IBEACON_INCLUDES_MAC)); + assertEq(uint8(t3), uint8(SwarmRegistryL1.TagType.VENDOR_ID)); + assertEq(uint8(t4), uint8(SwarmRegistryL1.TagType.GENERIC)); + } + + // ============================== + // registerSwarm — reverts + // ============================== + + function test_RevertIf_registerSwarm_notUuidOwner() public { + uint256 fleetId = _registerFleet(fleetOwner, "my-fleet"); + + vm.prank(caller); + vm.expectRevert(SwarmRegistryL1.NotUuidOwner.selector); + swarmRegistry.registerSwarm(_getFleetUuid(fleetId), 1, new bytes(10), 16, SwarmRegistryL1.TagType.GENERIC); + } + + function test_RevertIf_registerSwarm_fingerprintSizeZero() public { + uint256 fleetId = _registerFleet(fleetOwner, "f1"); + uint256 providerId = _registerProvider(providerOwner, "url1"); + + vm.prank(fleetOwner); + vm.expectRevert(SwarmRegistryL1.InvalidFingerprintSize.selector); + swarmRegistry.registerSwarm(_getFleetUuid(fleetId), providerId, new bytes(32), 0, SwarmRegistryL1.TagType.GENERIC); + } + + function test_RevertIf_registerSwarm_fingerprintSizeExceedsMax() public { + uint256 fleetId = _registerFleet(fleetOwner, "f1"); + uint256 providerId = _registerProvider(providerOwner, "url1"); + + vm.prank(fleetOwner); + vm.expectRevert(SwarmRegistryL1.InvalidFingerprintSize.selector); + swarmRegistry.registerSwarm(_getFleetUuid(fleetId), providerId, new bytes(32), 17, SwarmRegistryL1.TagType.GENERIC); + } + + function test_RevertIf_registerSwarm_emptyFilter() public { + uint256 fleetId = _registerFleet(fleetOwner, "f1"); + uint256 providerId = _registerProvider(providerOwner, "url1"); + + vm.prank(fleetOwner); + vm.expectRevert(SwarmRegistryL1.InvalidFilterSize.selector); + swarmRegistry.registerSwarm(_getFleetUuid(fleetId), providerId, new bytes(0), 8, SwarmRegistryL1.TagType.GENERIC); + } + + function test_RevertIf_registerSwarm_filterTooLarge() public { + uint256 fleetId = _registerFleet(fleetOwner, "f1"); + uint256 providerId = _registerProvider(providerOwner, "url1"); + + vm.prank(fleetOwner); + vm.expectRevert(SwarmRegistryL1.InvalidFilterSize.selector); + swarmRegistry.registerSwarm(_getFleetUuid(fleetId), providerId, new bytes(24577), 8, SwarmRegistryL1.TagType.GENERIC); + } + + function test_registerSwarm_maxFingerprintSize() public { + uint256 fleetId = _registerFleet(fleetOwner, "f1"); + uint256 providerId = _registerProvider(providerOwner, "url1"); + + // fpSize=16 is MAX_FINGERPRINT_SIZE, should succeed + uint256 swarmId = + _registerSwarm(fleetOwner, fleetId, providerId, new bytes(100), 16, SwarmRegistryL1.TagType.GENERIC); + assertTrue(swarmId != 0); + } + + function test_registerSwarm_maxFilterSize() public { + uint256 fleetId = _registerFleet(fleetOwner, "f1"); + uint256 providerId = _registerProvider(providerOwner, "url1"); + + // Exactly 24576 bytes should succeed + uint256 swarmId = + _registerSwarm(fleetOwner, fleetId, providerId, new bytes(24576), 8, SwarmRegistryL1.TagType.GENERIC); + assertTrue(swarmId != 0); + } + + // ============================== + // acceptSwarm / rejectSwarm + // ============================== + + function test_acceptSwarm_setsStatusAndEmits() public { + uint256 fleetId = _registerFleet(fleetOwner, "f1"); + uint256 providerId = _registerProvider(providerOwner, "url1"); + uint256 swarmId = + _registerSwarm(fleetOwner, fleetId, providerId, new bytes(50), 8, SwarmRegistryL1.TagType.GENERIC); + + vm.expectEmit(true, true, true, true); + emit SwarmStatusChanged(swarmId, SwarmRegistryL1.SwarmStatus.ACCEPTED); + + vm.prank(providerOwner); + swarmRegistry.acceptSwarm(swarmId); + + (,,,,, SwarmRegistryL1.SwarmStatus status) = swarmRegistry.swarms(swarmId); + assertEq(uint8(status), uint8(SwarmRegistryL1.SwarmStatus.ACCEPTED)); + } + + function test_rejectSwarm_setsStatusAndEmits() public { + uint256 fleetId = _registerFleet(fleetOwner, "f1"); + uint256 providerId = _registerProvider(providerOwner, "url1"); + uint256 swarmId = + _registerSwarm(fleetOwner, fleetId, providerId, new bytes(50), 8, SwarmRegistryL1.TagType.GENERIC); + + vm.expectEmit(true, true, true, true); + emit SwarmStatusChanged(swarmId, SwarmRegistryL1.SwarmStatus.REJECTED); + + vm.prank(providerOwner); + swarmRegistry.rejectSwarm(swarmId); + + (,,,,, SwarmRegistryL1.SwarmStatus status) = swarmRegistry.swarms(swarmId); + assertEq(uint8(status), uint8(SwarmRegistryL1.SwarmStatus.REJECTED)); + } + + function test_RevertIf_acceptSwarm_notProviderOwner() public { + uint256 fleetId = _registerFleet(fleetOwner, "f1"); + uint256 providerId = _registerProvider(providerOwner, "url1"); + uint256 swarmId = + _registerSwarm(fleetOwner, fleetId, providerId, new bytes(50), 8, SwarmRegistryL1.TagType.GENERIC); + + vm.prank(caller); + vm.expectRevert(SwarmRegistryL1.NotProviderOwner.selector); + swarmRegistry.acceptSwarm(swarmId); + } + + function test_RevertIf_rejectSwarm_notProviderOwner() public { + uint256 fleetId = _registerFleet(fleetOwner, "f1"); + uint256 providerId = _registerProvider(providerOwner, "url1"); + uint256 swarmId = + _registerSwarm(fleetOwner, fleetId, providerId, new bytes(50), 8, SwarmRegistryL1.TagType.GENERIC); + + vm.prank(fleetOwner); // fleet owner != provider owner + vm.expectRevert(SwarmRegistryL1.NotProviderOwner.selector); + swarmRegistry.rejectSwarm(swarmId); + } + + function test_acceptSwarm_afterReject() public { + uint256 fleetId = _registerFleet(fleetOwner, "f1"); + uint256 providerId = _registerProvider(providerOwner, "url1"); + uint256 swarmId = + _registerSwarm(fleetOwner, fleetId, providerId, new bytes(50), 8, SwarmRegistryL1.TagType.GENERIC); + + vm.prank(providerOwner); + swarmRegistry.rejectSwarm(swarmId); + + // Provider changes mind + vm.prank(providerOwner); + swarmRegistry.acceptSwarm(swarmId); + + (,,,,, SwarmRegistryL1.SwarmStatus status) = swarmRegistry.swarms(swarmId); + assertEq(uint8(status), uint8(SwarmRegistryL1.SwarmStatus.ACCEPTED)); + } + + // ============================== + // checkMembership — XOR logic + // ============================== + + function test_checkMembership_XORLogic16Bit() public { + uint256 fleetId = _registerFleet(fleetOwner, "f1"); + uint256 providerId = _registerProvider(providerOwner, "u1"); + + bytes memory tagId = hex"1122334455"; + uint8 fpSize = 16; + uint256 dataLen = 100; + uint256 m = (dataLen * 8) / fpSize; // 50 slots + + (uint32 h1, uint32 h2, uint32 h3, uint256 expectedFp) = getExpectedValues(tagId, m, fpSize); + + // Skip if collision (extremely unlikely with 50 slots) + if (h1 == h2 || h1 == h3 || h2 == h3) { + return; + } + + bytes memory filter = new bytes(dataLen); + _write16Bit(filter, h1, uint16(expectedFp)); + + uint256 swarmId = + _registerSwarm(fleetOwner, fleetId, providerId, filter, fpSize, SwarmRegistryL1.TagType.GENERIC); + + // Positive check + assertTrue(swarmRegistry.checkMembership(swarmId, keccak256(tagId)), "Valid tag should pass"); + + // Negative check + assertFalse(swarmRegistry.checkMembership(swarmId, keccak256(hex"999999")), "Invalid tag should fail"); + } + + function test_checkMembership_XORLogic8Bit() public { + uint256 fleetId = _registerFleet(fleetOwner, "f1"); + uint256 providerId = _registerProvider(providerOwner, "u1"); + + bytes memory tagId = hex"AABBCCDD"; + uint8 fpSize = 8; + // SSTORE2 prepends 0x00 STOP byte, so on-chain: + // extcodesize = rawLen + 1, dataLen = extcodesize - 1 = rawLen + // But SSTORE2.read offsets reads by +1 (skips STOP byte), so + // the data bytes read on-chain map 1:1 to the bytes we pass in. + // Therefore m = (rawLen * 8) / fpSize and slot indices match directly. + uint256 rawLen = 80; + uint256 m = (rawLen * 8) / fpSize; // 80 + + (uint32 h1, uint32 h2, uint32 h3, uint256 expectedFp) = getExpectedValues(tagId, m, fpSize); + + if (h1 == h2 || h1 == h3 || h2 == h3) { + return; + } + + bytes memory filter = new bytes(rawLen); + _write8Bit(filter, h1, uint8(expectedFp)); + + uint256 swarmId = + _registerSwarm(fleetOwner, fleetId, providerId, filter, fpSize, SwarmRegistryL1.TagType.GENERIC); + + assertTrue(swarmRegistry.checkMembership(swarmId, keccak256(tagId)), "8-bit valid tag should pass"); + assertFalse(swarmRegistry.checkMembership(swarmId, keccak256(hex"FFFFFF")), "8-bit invalid tag should fail"); + } + + function test_RevertIf_checkMembership_swarmNotFound() public { + vm.expectRevert(SwarmRegistryL1.SwarmNotFound.selector); + swarmRegistry.checkMembership(999, keccak256("anything")); + } + + function test_checkMembership_allZeroFilter_returnsConsistent() public { + uint256 fleetId = _registerFleet(fleetOwner, "f1"); + uint256 providerId = _registerProvider(providerOwner, "u1"); + + // All-zero filter: f1^f2^f3 = 0^0^0 = 0 + // Only matches if expectedFp is also 0 + bytes memory filter = new bytes(64); + uint256 swarmId = _registerSwarm(fleetOwner, fleetId, providerId, filter, 16, SwarmRegistryL1.TagType.GENERIC); + + // Some tags will match (those with expectedFp=0), most won't + // The point is it doesn't revert + swarmRegistry.checkMembership(swarmId, keccak256("test1")); + swarmRegistry.checkMembership(swarmId, keccak256("test2")); + } + + // ============================== + // Multiple swarms per fleet + // ============================== + + function test_multipleSwarms_sameFleet() public { + uint256 fleetId = _registerFleet(fleetOwner, "f1"); + uint256 providerId1 = _registerProvider(providerOwner, "url1"); + uint256 providerId2 = _registerProvider(providerOwner, "url2"); + uint256 providerId3 = _registerProvider(providerOwner, "url3"); + + uint256 s1 = _registerSwarm(fleetOwner, fleetId, providerId1, new bytes(32), 8, SwarmRegistryL1.TagType.GENERIC); + uint256 s2 = + _registerSwarm(fleetOwner, fleetId, providerId2, new bytes(64), 16, SwarmRegistryL1.TagType.VENDOR_ID); + uint256 s3 = _registerSwarm( + fleetOwner, fleetId, providerId3, new bytes(50), 12, SwarmRegistryL1.TagType.IBEACON_PAYLOAD_ONLY + ); + + // IDs are distinct hashes + assertTrue(s1 != s2 && s2 != s3 && s1 != s3); + + assertEq(swarmRegistry.uuidSwarms(_getFleetUuid(fleetId), 0), s1); + assertEq(swarmRegistry.uuidSwarms(_getFleetUuid(fleetId), 1), s2); + assertEq(swarmRegistry.uuidSwarms(_getFleetUuid(fleetId), 2), s3); + } + + // ============================== + // Constants + // ============================== + + function test_constants() public view { + assertEq(swarmRegistry.MAX_FINGERPRINT_SIZE(), 16); + } + + // ============================== + // Fuzz + // ============================== + + function testFuzz_registerSwarm_validFingerprintSizes(uint8 fpSize) public { + fpSize = uint8(bound(fpSize, 1, 16)); + + uint256 fleetId = _registerFleet(fleetOwner, abi.encodePacked("fleet-", fpSize)); + uint256 providerId = _registerProvider(providerOwner, string(abi.encodePacked("url-", fpSize))); + + uint256 swarmId = + _registerSwarm(fleetOwner, fleetId, providerId, new bytes(64), fpSize, SwarmRegistryL1.TagType.GENERIC); + + (,,, uint8 storedFp,,) = swarmRegistry.swarms(swarmId); + assertEq(storedFp, fpSize); + } + + function testFuzz_registerSwarm_invalidFingerprintSizes(uint8 fpSize) public { + vm.assume(fpSize == 0 || fpSize > 16); + + uint256 fleetId = _registerFleet(fleetOwner, "f1"); + uint256 providerId = _registerProvider(providerOwner, "url1"); + + vm.prank(fleetOwner); + vm.expectRevert(SwarmRegistryL1.InvalidFingerprintSize.selector); + swarmRegistry.registerSwarm(_getFleetUuid(fleetId), providerId, new bytes(32), fpSize, SwarmRegistryL1.TagType.GENERIC); + } + + // ============================== + // updateSwarmFilter + // ============================== + + function test_updateSwarmFilter_updatesFilterAndResetsStatus() public { + uint256 fleetId = _registerFleet(fleetOwner, "f1"); + uint256 providerId = _registerProvider(providerOwner, "url1"); + uint256 swarmId = + _registerSwarm(fleetOwner, fleetId, providerId, new bytes(50), 8, SwarmRegistryL1.TagType.GENERIC); + + // Provider accepts + vm.prank(providerOwner); + swarmRegistry.acceptSwarm(swarmId); + + // Fleet owner updates filter + bytes memory newFilter = new bytes(100); + vm.expectEmit(true, true, true, true); + emit SwarmFilterUpdated(swarmId, fleetOwner, 100); + + vm.prank(fleetOwner); + swarmRegistry.updateSwarmFilter(swarmId, newFilter); + + // Status should be reset to REGISTERED + (,,,,, SwarmRegistryL1.SwarmStatus status) = swarmRegistry.swarms(swarmId); + assertEq(uint8(status), uint8(SwarmRegistryL1.SwarmStatus.REGISTERED)); + } + + function test_updateSwarmFilter_changesFilterPointer() public { + uint256 fleetId = _registerFleet(fleetOwner, "f1"); + uint256 providerId = _registerProvider(providerOwner, "url1"); + uint256 swarmId = + _registerSwarm(fleetOwner, fleetId, providerId, new bytes(50), 8, SwarmRegistryL1.TagType.GENERIC); + + (,, address oldPointer,,,) = swarmRegistry.swarms(swarmId); + + bytes memory newFilter = new bytes(100); + vm.prank(fleetOwner); + swarmRegistry.updateSwarmFilter(swarmId, newFilter); + + (,, address newPointer,,,) = swarmRegistry.swarms(swarmId); + assertTrue(newPointer != oldPointer); + assertTrue(newPointer != address(0)); + } + + function test_RevertIf_updateSwarmFilter_swarmNotFound() public { + vm.prank(fleetOwner); + vm.expectRevert(SwarmRegistryL1.SwarmNotFound.selector); + swarmRegistry.updateSwarmFilter(999, new bytes(50)); + } + + function test_RevertIf_updateSwarmFilter_notFleetOwner() public { + uint256 fleetId = _registerFleet(fleetOwner, "f1"); + uint256 providerId = _registerProvider(providerOwner, "url1"); + uint256 swarmId = + _registerSwarm(fleetOwner, fleetId, providerId, new bytes(50), 8, SwarmRegistryL1.TagType.GENERIC); + + vm.prank(caller); + vm.expectRevert(SwarmRegistryL1.NotUuidOwner.selector); + swarmRegistry.updateSwarmFilter(swarmId, new bytes(100)); + } + + function test_RevertIf_updateSwarmFilter_emptyFilter() public { + uint256 fleetId = _registerFleet(fleetOwner, "f1"); + uint256 providerId = _registerProvider(providerOwner, "url1"); + uint256 swarmId = + _registerSwarm(fleetOwner, fleetId, providerId, new bytes(50), 8, SwarmRegistryL1.TagType.GENERIC); + + vm.prank(fleetOwner); + vm.expectRevert(SwarmRegistryL1.InvalidFilterSize.selector); + swarmRegistry.updateSwarmFilter(swarmId, new bytes(0)); + } + + function test_RevertIf_updateSwarmFilter_filterTooLarge() public { + uint256 fleetId = _registerFleet(fleetOwner, "f1"); + uint256 providerId = _registerProvider(providerOwner, "url1"); + uint256 swarmId = + _registerSwarm(fleetOwner, fleetId, providerId, new bytes(50), 8, SwarmRegistryL1.TagType.GENERIC); + + vm.prank(fleetOwner); + vm.expectRevert(SwarmRegistryL1.InvalidFilterSize.selector); + swarmRegistry.updateSwarmFilter(swarmId, new bytes(24577)); + } + + // ============================== + // updateSwarmProvider + // ============================== + + function test_updateSwarmProvider_updatesProviderAndResetsStatus() public { + uint256 fleetId = _registerFleet(fleetOwner, "f1"); + uint256 providerId1 = _registerProvider(providerOwner, "url1"); + uint256 providerId2 = _registerProvider(providerOwner, "url2"); + + uint256 swarmId = + _registerSwarm(fleetOwner, fleetId, providerId1, new bytes(50), 8, SwarmRegistryL1.TagType.GENERIC); + + // Provider accepts + vm.prank(providerOwner); + swarmRegistry.acceptSwarm(swarmId); + + // Fleet owner updates provider + vm.expectEmit(true, true, true, true); + emit SwarmProviderUpdated(swarmId, providerId1, providerId2); + + vm.prank(fleetOwner); + swarmRegistry.updateSwarmProvider(swarmId, providerId2); + + // Check new provider and status reset + (, uint256 newProviderId,,,, SwarmRegistryL1.SwarmStatus status) = swarmRegistry.swarms(swarmId); + assertEq(newProviderId, providerId2); + assertEq(uint8(status), uint8(SwarmRegistryL1.SwarmStatus.REGISTERED)); + } + + function test_RevertIf_updateSwarmProvider_swarmNotFound() public { + uint256 providerId = _registerProvider(providerOwner, "url1"); + + vm.prank(fleetOwner); + vm.expectRevert(SwarmRegistryL1.SwarmNotFound.selector); + swarmRegistry.updateSwarmProvider(999, providerId); + } + + function test_RevertIf_updateSwarmProvider_notFleetOwner() public { + uint256 fleetId = _registerFleet(fleetOwner, "f1"); + uint256 providerId1 = _registerProvider(providerOwner, "url1"); + uint256 providerId2 = _registerProvider(providerOwner, "url2"); + + uint256 swarmId = + _registerSwarm(fleetOwner, fleetId, providerId1, new bytes(50), 8, SwarmRegistryL1.TagType.GENERIC); + + vm.prank(caller); + vm.expectRevert(SwarmRegistryL1.NotUuidOwner.selector); + swarmRegistry.updateSwarmProvider(swarmId, providerId2); + } + + function test_RevertIf_updateSwarmProvider_providerDoesNotExist() public { + uint256 fleetId = _registerFleet(fleetOwner, "f1"); + uint256 providerId = _registerProvider(providerOwner, "url1"); + + uint256 swarmId = + _registerSwarm(fleetOwner, fleetId, providerId, new bytes(50), 8, SwarmRegistryL1.TagType.GENERIC); + + vm.prank(fleetOwner); + // ERC721 reverts before our custom error is reached + vm.expectRevert(); + swarmRegistry.updateSwarmProvider(swarmId, 99999); + } + + // ============================== + // deleteSwarm + // ============================== + + function test_deleteSwarm_removesSwarmAndEmits() public { + uint256 fleetId = _registerFleet(fleetOwner, "f1"); + uint256 providerId = _registerProvider(providerOwner, "url1"); + uint256 swarmId = + _registerSwarm(fleetOwner, fleetId, providerId, new bytes(50), 8, SwarmRegistryL1.TagType.GENERIC); + + vm.expectEmit(true, true, true, true); + emit SwarmDeleted(swarmId, _getFleetUuid(fleetId), fleetOwner); + + vm.prank(fleetOwner); + swarmRegistry.deleteSwarm(swarmId); + + // Swarm should be zeroed + (,, address pointer,,,) = swarmRegistry.swarms(swarmId); + assertEq(pointer, address(0)); + } + + function test_deleteSwarm_removesFromUuidSwarms() public { + uint256 fleetId = _registerFleet(fleetOwner, "f1"); + uint256 providerId1 = _registerProvider(providerOwner, "url1"); + uint256 providerId2 = _registerProvider(providerOwner, "url2"); + + uint256 swarm1 = + _registerSwarm(fleetOwner, fleetId, providerId1, new bytes(50), 8, SwarmRegistryL1.TagType.GENERIC); + uint256 swarm2 = + _registerSwarm(fleetOwner, fleetId, providerId2, new bytes(50), 8, SwarmRegistryL1.TagType.GENERIC); + + // Delete first swarm + vm.prank(fleetOwner); + swarmRegistry.deleteSwarm(swarm1); + + // Only swarm2 should remain in fleetSwarms + assertEq(swarmRegistry.uuidSwarms(_getFleetUuid(fleetId), 0), swarm2); + vm.expectRevert(); + swarmRegistry.uuidSwarms(_getFleetUuid(fleetId), 1); // Should be out of bounds + } + + function test_deleteSwarm_swapAndPop() public { + uint256 fleetId = _registerFleet(fleetOwner, "f1"); + uint256 providerId1 = _registerProvider(providerOwner, "url1"); + uint256 providerId2 = _registerProvider(providerOwner, "url2"); + uint256 providerId3 = _registerProvider(providerOwner, "url3"); + + uint256 swarm1 = + _registerSwarm(fleetOwner, fleetId, providerId1, new bytes(50), 8, SwarmRegistryL1.TagType.GENERIC); + uint256 swarm2 = + _registerSwarm(fleetOwner, fleetId, providerId2, new bytes(50), 8, SwarmRegistryL1.TagType.GENERIC); + uint256 swarm3 = + _registerSwarm(fleetOwner, fleetId, providerId3, new bytes(50), 8, SwarmRegistryL1.TagType.GENERIC); + + // Delete middle swarm + vm.prank(fleetOwner); + swarmRegistry.deleteSwarm(swarm2); + + // swarm3 should be swapped to index 1 + assertEq(swarmRegistry.uuidSwarms(_getFleetUuid(fleetId), 0), swarm1); + assertEq(swarmRegistry.uuidSwarms(_getFleetUuid(fleetId), 1), swarm3); + vm.expectRevert(); + swarmRegistry.uuidSwarms(_getFleetUuid(fleetId), 2); // Should be out of bounds + } + + function test_RevertIf_deleteSwarm_swarmNotFound() public { + vm.prank(fleetOwner); + vm.expectRevert(SwarmRegistryL1.SwarmNotFound.selector); + swarmRegistry.deleteSwarm(999); + } + + function test_RevertIf_deleteSwarm_notFleetOwner() public { + uint256 fleetId = _registerFleet(fleetOwner, "f1"); + uint256 providerId = _registerProvider(providerOwner, "url1"); + uint256 swarmId = + _registerSwarm(fleetOwner, fleetId, providerId, new bytes(50), 8, SwarmRegistryL1.TagType.GENERIC); + + vm.prank(caller); + vm.expectRevert(SwarmRegistryL1.NotUuidOwner.selector); + swarmRegistry.deleteSwarm(swarmId); + } + + function test_deleteSwarm_afterUpdate() public { + uint256 fleetId = _registerFleet(fleetOwner, "f1"); + uint256 providerId = _registerProvider(providerOwner, "url1"); + uint256 swarmId = + _registerSwarm(fleetOwner, fleetId, providerId, new bytes(50), 8, SwarmRegistryL1.TagType.GENERIC); + + // Update then delete + vm.prank(fleetOwner); + swarmRegistry.updateSwarmFilter(swarmId, new bytes(100)); + + vm.prank(fleetOwner); + swarmRegistry.deleteSwarm(swarmId); + + (,, address pointer,,,) = swarmRegistry.swarms(swarmId); + assertEq(pointer, address(0)); + } + + function test_deleteSwarm_updatesSwarmIndexInUuid() public { + uint256 fleetId = _registerFleet(fleetOwner, "f1"); + uint256 p1 = _registerProvider(providerOwner, "url1"); + uint256 p2 = _registerProvider(providerOwner, "url2"); + uint256 p3 = _registerProvider(providerOwner, "url3"); + + uint256 s1 = _registerSwarm(fleetOwner, fleetId, p1, new bytes(50), 8, SwarmRegistryL1.TagType.GENERIC); + uint256 s2 = _registerSwarm(fleetOwner, fleetId, p2, new bytes(50), 8, SwarmRegistryL1.TagType.GENERIC); + uint256 s3 = _registerSwarm(fleetOwner, fleetId, p3, new bytes(50), 8, SwarmRegistryL1.TagType.GENERIC); + + // Verify initial indices + assertEq(swarmRegistry.swarmIndexInUuid(s1), 0); + assertEq(swarmRegistry.swarmIndexInUuid(s2), 1); + assertEq(swarmRegistry.swarmIndexInUuid(s3), 2); + + // Delete s1 — s3 should be swapped to index 0 + vm.prank(fleetOwner); + swarmRegistry.deleteSwarm(s1); + + assertEq(swarmRegistry.swarmIndexInUuid(s3), 0); + assertEq(swarmRegistry.swarmIndexInUuid(s2), 1); + assertEq(swarmRegistry.swarmIndexInUuid(s1), 0); // deleted, reset to 0 + } + + // ============================== + // isSwarmValid + // ============================== + + function test_isSwarmValid_bothValid() public { + uint256 fleetId = _registerFleet(fleetOwner, "f1"); + uint256 providerId = _registerProvider(providerOwner, "url1"); + uint256 swarmId = + _registerSwarm(fleetOwner, fleetId, providerId, new bytes(50), 8, SwarmRegistryL1.TagType.GENERIC); + + (bool fleetValid, bool providerValid) = swarmRegistry.isSwarmValid(swarmId); + assertTrue(fleetValid); + assertTrue(providerValid); + } + + function test_isSwarmValid_providerBurned() public { + uint256 fleetId = _registerFleet(fleetOwner, "f1"); + uint256 providerId = _registerProvider(providerOwner, "url1"); + uint256 swarmId = + _registerSwarm(fleetOwner, fleetId, providerId, new bytes(50), 8, SwarmRegistryL1.TagType.GENERIC); + + // Burn provider + vm.prank(providerOwner); + providerContract.burn(providerId); + + (bool fleetValid, bool providerValid) = swarmRegistry.isSwarmValid(swarmId); + assertTrue(fleetValid); + assertFalse(providerValid); + } + + function test_isSwarmValid_fleetBurned() public { + uint256 fleetId = _registerFleet(fleetOwner, "f1"); + uint256 providerId = _registerProvider(providerOwner, "url1"); + uint256 swarmId = + _registerSwarm(fleetOwner, fleetId, providerId, new bytes(50), 8, SwarmRegistryL1.TagType.GENERIC); + + // Burn registered fleet token (operator = owner for fresh registration) + // This mints an owned-only token back to the owner + vm.prank(fleetOwner); + fleetContract.burn(fleetId); + + // After burning registered token, UUID transitions to Owned state + // Need to burn the owned-only token to fully release + bytes16 uuid = _getFleetUuid(fleetId); + uint256 ownedTokenId = uint256(uint128(uuid)); // owned token has regionKey=0 + vm.prank(fleetOwner); + fleetContract.burn(ownedTokenId); + + (bool fleetValid, bool providerValid) = swarmRegistry.isSwarmValid(swarmId); + assertFalse(fleetValid); + assertTrue(providerValid); + } + + function test_isSwarmValid_bothBurned() public { + uint256 fleetId = _registerFleet(fleetOwner, "f1"); + uint256 providerId = _registerProvider(providerOwner, "url1"); + uint256 swarmId = + _registerSwarm(fleetOwner, fleetId, providerId, new bytes(50), 8, SwarmRegistryL1.TagType.GENERIC); + + // Burn registered fleet token → mints owned-only token + vm.prank(fleetOwner); + fleetContract.burn(fleetId); + + // Burn owned-only token to fully release UUID + bytes16 uuid = _getFleetUuid(fleetId); + uint256 ownedTokenId = uint256(uint128(uuid)); + vm.prank(fleetOwner); + fleetContract.burn(ownedTokenId); + + vm.prank(providerOwner); + providerContract.burn(providerId); + + (bool fleetValid, bool providerValid) = swarmRegistry.isSwarmValid(swarmId); + assertFalse(fleetValid); + assertFalse(providerValid); + } + + function test_RevertIf_isSwarmValid_swarmNotFound() public { + vm.expectRevert(SwarmRegistryL1.SwarmNotFound.selector); + swarmRegistry.isSwarmValid(999); + } + + // ============================== + // purgeOrphanedSwarm + // ============================== + + function test_purgeOrphanedSwarm_providerBurned() public { + uint256 fleetId = _registerFleet(fleetOwner, "f1"); + uint256 providerId = _registerProvider(providerOwner, "url1"); + uint256 swarmId = + _registerSwarm(fleetOwner, fleetId, providerId, new bytes(50), 8, SwarmRegistryL1.TagType.GENERIC); + + // Burn provider + vm.prank(providerOwner); + providerContract.burn(providerId); + + // Anyone can purge + vm.expectEmit(true, true, true, true); + emit SwarmPurged(swarmId, _getFleetUuid(fleetId), caller); + + vm.prank(caller); + swarmRegistry.purgeOrphanedSwarm(swarmId); + + // Swarm should be zeroed + (,, address pointer,,,) = swarmRegistry.swarms(swarmId); + assertEq(pointer, address(0)); + } + + function test_purgeOrphanedSwarm_fleetBurned() public { + uint256 fleetId = _registerFleet(fleetOwner, "f1"); + uint256 providerId = _registerProvider(providerOwner, "url1"); + uint256 swarmId = + _registerSwarm(fleetOwner, fleetId, providerId, new bytes(50), 8, SwarmRegistryL1.TagType.GENERIC); + + // Burn registered fleet token → mints owned-only token + vm.prank(fleetOwner); + fleetContract.burn(fleetId); + + // Burn owned-only token to fully release UUID + bytes16 uuid = _getFleetUuid(fleetId); + uint256 ownedTokenId = uint256(uint128(uuid)); + vm.prank(fleetOwner); + fleetContract.burn(ownedTokenId); + + vm.prank(caller); + swarmRegistry.purgeOrphanedSwarm(swarmId); + + (,, address pointer,,,) = swarmRegistry.swarms(swarmId); + assertEq(pointer, address(0)); + } + + function test_purgeOrphanedSwarm_removesFromUuidSwarms() public { + uint256 fleetId = _registerFleet(fleetOwner, "f1"); + uint256 p1 = _registerProvider(providerOwner, "url1"); + uint256 p2 = _registerProvider(providerOwner, "url2"); + + uint256 s1 = _registerSwarm(fleetOwner, fleetId, p1, new bytes(50), 8, SwarmRegistryL1.TagType.GENERIC); + uint256 s2 = _registerSwarm(fleetOwner, fleetId, p2, new bytes(50), 8, SwarmRegistryL1.TagType.GENERIC); + + // Burn provider of s1 + vm.prank(providerOwner); + providerContract.burn(p1); + + vm.prank(caller); + swarmRegistry.purgeOrphanedSwarm(s1); + + // s2 should be swapped to index 0 + assertEq(swarmRegistry.uuidSwarms(_getFleetUuid(fleetId), 0), s2); + vm.expectRevert(); + swarmRegistry.uuidSwarms(_getFleetUuid(fleetId), 1); + } + + function test_RevertIf_purgeOrphanedSwarm_swarmNotFound() public { + vm.expectRevert(SwarmRegistryL1.SwarmNotFound.selector); + swarmRegistry.purgeOrphanedSwarm(999); + } + + function test_RevertIf_purgeOrphanedSwarm_swarmNotOrphaned() public { + uint256 fleetId = _registerFleet(fleetOwner, "f1"); + uint256 providerId = _registerProvider(providerOwner, "url1"); + uint256 swarmId = + _registerSwarm(fleetOwner, fleetId, providerId, new bytes(50), 8, SwarmRegistryL1.TagType.GENERIC); + + vm.expectRevert(SwarmRegistryL1.SwarmNotOrphaned.selector); + swarmRegistry.purgeOrphanedSwarm(swarmId); + } + + // ============================== + // Orphan guards on accept/reject/checkMembership + // ============================== + + function test_RevertIf_acceptSwarm_orphaned() public { + uint256 fleetId = _registerFleet(fleetOwner, "f1"); + uint256 providerId = _registerProvider(providerOwner, "url1"); + uint256 swarmId = + _registerSwarm(fleetOwner, fleetId, providerId, new bytes(50), 8, SwarmRegistryL1.TagType.GENERIC); + + // Burn provider + vm.prank(providerOwner); + providerContract.burn(providerId); + + vm.prank(providerOwner); + vm.expectRevert(SwarmRegistryL1.SwarmOrphaned.selector); + swarmRegistry.acceptSwarm(swarmId); + } + + function test_RevertIf_rejectSwarm_orphaned() public { + uint256 fleetId = _registerFleet(fleetOwner, "f1"); + uint256 providerId = _registerProvider(providerOwner, "url1"); + uint256 swarmId = + _registerSwarm(fleetOwner, fleetId, providerId, new bytes(50), 8, SwarmRegistryL1.TagType.GENERIC); + + // Burn registered fleet token → mints owned-only token + vm.prank(fleetOwner); + fleetContract.burn(fleetId); + + // Burn owned-only token to fully release UUID + bytes16 uuid = _getFleetUuid(fleetId); + uint256 ownedTokenId = uint256(uint128(uuid)); + vm.prank(fleetOwner); + fleetContract.burn(ownedTokenId); + + vm.prank(providerOwner); + vm.expectRevert(SwarmRegistryL1.SwarmOrphaned.selector); + swarmRegistry.rejectSwarm(swarmId); + } + + function test_RevertIf_checkMembership_orphaned() public { + uint256 fleetId = _registerFleet(fleetOwner, "f1"); + uint256 providerId = _registerProvider(providerOwner, "url1"); + uint256 swarmId = + _registerSwarm(fleetOwner, fleetId, providerId, new bytes(50), 8, SwarmRegistryL1.TagType.GENERIC); + + // Burn provider + vm.prank(providerOwner); + providerContract.burn(providerId); + + vm.expectRevert(SwarmRegistryL1.SwarmOrphaned.selector); + swarmRegistry.checkMembership(swarmId, keccak256("test")); + } + + function test_RevertIf_acceptSwarm_fleetBurned() public { + uint256 fleetId = _registerFleet(fleetOwner, "f1"); + uint256 providerId = _registerProvider(providerOwner, "url1"); + uint256 swarmId = + _registerSwarm(fleetOwner, fleetId, providerId, new bytes(50), 8, SwarmRegistryL1.TagType.GENERIC); + + // Burn registered fleet token → mints owned-only token + vm.prank(fleetOwner); + fleetContract.burn(fleetId); + + // Burn owned-only token to fully release UUID + bytes16 uuid = _getFleetUuid(fleetId); + uint256 ownedTokenId = uint256(uint128(uuid)); + vm.prank(fleetOwner); + fleetContract.burn(ownedTokenId); + + vm.prank(providerOwner); + vm.expectRevert(SwarmRegistryL1.SwarmOrphaned.selector); + swarmRegistry.acceptSwarm(swarmId); + } + + function test_purge_thenAcceptReverts() public { + uint256 fleetId = _registerFleet(fleetOwner, "f1"); + uint256 providerId = _registerProvider(providerOwner, "url1"); + uint256 swarmId = + _registerSwarm(fleetOwner, fleetId, providerId, new bytes(50), 8, SwarmRegistryL1.TagType.GENERIC); + + vm.prank(providerOwner); + providerContract.burn(providerId); + + vm.prank(caller); + swarmRegistry.purgeOrphanedSwarm(swarmId); + + // After purge, swarm no longer exists + vm.prank(providerOwner); + vm.expectRevert(SwarmRegistryL1.SwarmNotFound.selector); + swarmRegistry.acceptSwarm(swarmId); + } +} diff --git a/test/SwarmRegistryUniversal.t.sol b/test/SwarmRegistryUniversal.t.sol new file mode 100644 index 00000000..f1eb9612 --- /dev/null +++ b/test/SwarmRegistryUniversal.t.sol @@ -0,0 +1,1205 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.24; + +import {Test} from "forge-std/Test.sol"; +import "../src/swarms/SwarmRegistryUniversal.sol"; +import {FleetIdentity} from "../src/swarms/FleetIdentity.sol"; +import {ServiceProvider} from "../src/swarms/ServiceProvider.sol"; +import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; + +contract MockBondTokenUniv is ERC20 { + constructor() ERC20("Mock Bond", "MBOND") {} + + function mint(address to, uint256 amount) external { + _mint(to, amount); + } +} + +contract SwarmRegistryUniversalTest is Test { + SwarmRegistryUniversal swarmRegistry; + FleetIdentity fleetContract; + ServiceProvider providerContract; + MockBondTokenUniv bondToken; + + address fleetOwner = address(0x1); + address providerOwner = address(0x2); + address caller = address(0x3); + + uint256 constant FLEET_BOND = 100 ether; + + // Region constants for fleet registration + uint16 constant US = 840; + uint16 constant ADMIN_CA = 6; // California + + event SwarmRegistered( + uint256 indexed swarmId, bytes16 indexed fleetUuid, uint256 indexed providerId, address owner, uint32 filterSize + ); + event SwarmStatusChanged(uint256 indexed swarmId, SwarmRegistryUniversal.SwarmStatus status); + event SwarmFilterUpdated(uint256 indexed swarmId, address indexed owner, uint32 newFilterSize); + event SwarmProviderUpdated(uint256 indexed swarmId, uint256 indexed oldProviderId, uint256 indexed newProviderId); + event SwarmDeleted(uint256 indexed swarmId, bytes16 indexed fleetUuid, address indexed owner); + event SwarmPurged(uint256 indexed swarmId, bytes16 indexed fleetUuid, address indexed purgedBy); + + function setUp() public { + bondToken = new MockBondTokenUniv(); + fleetContract = new FleetIdentity(address(bondToken), FLEET_BOND); + providerContract = new ServiceProvider(); + swarmRegistry = new SwarmRegistryUniversal(address(fleetContract), address(providerContract)); + + // Fund fleet owner and approve + bondToken.mint(fleetOwner, 1_000_000 ether); + vm.prank(fleetOwner); + bondToken.approve(address(fleetContract), type(uint256).max); + } + + // ============================== + // Helpers + // ============================== + + function _registerFleet(address owner, bytes memory seed) internal returns (uint256) { + vm.prank(owner); + return fleetContract.registerFleetLocal(bytes16(keccak256(seed)), US, ADMIN_CA, 0); + } + + function _getFleetUuid(uint256 fleetId) internal pure returns (bytes16) { + return bytes16(uint128(fleetId)); + } + + function _registerProvider(address owner, string memory url) internal returns (uint256) { + vm.prank(owner); + return providerContract.registerProvider(url); + } + + function _registerSwarm( + address owner, + uint256 fleetId, + uint256 providerId, + bytes memory filter, + uint8 fpSize, + SwarmRegistryUniversal.TagType tagType + ) internal returns (uint256) { + bytes16 fleetUuid = _getFleetUuid(fleetId); + vm.prank(owner); + return swarmRegistry.registerSwarm(fleetUuid, providerId, filter, fpSize, tagType); + } + + function getExpectedValues(bytes memory tagId, uint256 m, uint8 fpSize) + public + pure + returns (uint32 h1, uint32 h2, uint32 h3, uint256 fp) + { + bytes32 h = keccak256(tagId); + h1 = uint32(uint256(h)) % uint32(m); + h2 = uint32(uint256(h) >> 32) % uint32(m); + h3 = uint32(uint256(h) >> 64) % uint32(m); + uint256 fpMask = (uint256(1) << fpSize) - 1; + fp = (uint256(h) >> 96) & fpMask; + } + + function _write16Bit(bytes memory data, uint256 slotIndex, uint16 value) internal pure { + uint256 byteOffset = (slotIndex * 16) / 8; + data[byteOffset] = bytes1(uint8(value >> 8)); + data[byteOffset + 1] = bytes1(uint8(value)); + } + + function _write8Bit(bytes memory data, uint256 slotIndex, uint8 value) internal pure { + data[slotIndex] = bytes1(value); + } + + // ============================== + // Constructor + // ============================== + + function test_constructor_setsImmutables() public view { + assertEq(address(swarmRegistry.FLEET_CONTRACT()), address(fleetContract)); + assertEq(address(swarmRegistry.PROVIDER_CONTRACT()), address(providerContract)); + } + + function test_RevertIf_constructor_zeroFleetAddress() public { + vm.expectRevert(SwarmRegistryUniversal.InvalidSwarmData.selector); + new SwarmRegistryUniversal(address(0), address(providerContract)); + } + + function test_RevertIf_constructor_zeroProviderAddress() public { + vm.expectRevert(SwarmRegistryUniversal.InvalidSwarmData.selector); + new SwarmRegistryUniversal(address(fleetContract), address(0)); + } + + function test_RevertIf_constructor_bothZero() public { + vm.expectRevert(SwarmRegistryUniversal.InvalidSwarmData.selector); + new SwarmRegistryUniversal(address(0), address(0)); + } + + // ============================== + // registerSwarm — happy path + // ============================== + + function test_registerSwarm_basicFlow() public { + uint256 fleetId = _registerFleet(fleetOwner, "my-fleet"); + uint256 providerId = _registerProvider(providerOwner, "https://api.example.com"); + + uint256 swarmId = _registerSwarm( + fleetOwner, fleetId, providerId, new bytes(100), 16, SwarmRegistryUniversal.TagType.IBEACON_INCLUDES_MAC + ); + + // Swarm ID is deterministic hash of (fleetUuid, providerId, filter) + uint256 expectedId = swarmRegistry.computeSwarmId(_getFleetUuid(fleetId), providerId, new bytes(100)); + assertEq(swarmId, expectedId); + } + + function test_registerSwarm_storesMetadataCorrectly() public { + uint256 fleetId = _registerFleet(fleetOwner, "f1"); + uint256 providerId = _registerProvider(providerOwner, "url1"); + + uint256 swarmId = + _registerSwarm(fleetOwner, fleetId, providerId, new bytes(50), 12, SwarmRegistryUniversal.TagType.VENDOR_ID); + + ( + bytes16 storedFleetUuid, + uint256 storedProviderId, + uint32 storedFilterLen, + uint8 storedFpSize, + SwarmRegistryUniversal.TagType storedTagType, + SwarmRegistryUniversal.SwarmStatus storedStatus + ) = swarmRegistry.swarms(swarmId); + + assertEq(storedFleetUuid, _getFleetUuid(fleetId)); + assertEq(storedProviderId, providerId); + assertEq(storedFilterLen, 50); + assertEq(storedFpSize, 12); + assertEq(uint8(storedTagType), uint8(SwarmRegistryUniversal.TagType.VENDOR_ID)); + assertEq(uint8(storedStatus), uint8(SwarmRegistryUniversal.SwarmStatus.REGISTERED)); + } + + function test_registerSwarm_storesFilterData() public { + uint256 fleetId = _registerFleet(fleetOwner, "f1"); + uint256 providerId = _registerProvider(providerOwner, "url1"); + + bytes memory filter = new bytes(100); + // Write some non-zero data + filter[0] = 0xAB; + filter[50] = 0xCD; + filter[99] = 0xEF; + + uint256 swarmId = + _registerSwarm(fleetOwner, fleetId, providerId, filter, 16, SwarmRegistryUniversal.TagType.GENERIC); + + bytes memory storedFilter = swarmRegistry.getFilterData(swarmId); + assertEq(storedFilter.length, 100); + assertEq(uint8(storedFilter[0]), 0xAB); + assertEq(uint8(storedFilter[50]), 0xCD); + assertEq(uint8(storedFilter[99]), 0xEF); + } + + function test_registerSwarm_deterministicId() public { + uint256 fleetId = _registerFleet(fleetOwner, "f1"); + uint256 providerId = _registerProvider(providerOwner, "url1"); + + bytes memory filter = new bytes(32); + + uint256 expectedId = swarmRegistry.computeSwarmId(_getFleetUuid(fleetId), providerId, filter); + + uint256 swarmId = + _registerSwarm(fleetOwner, fleetId, providerId, filter, 8, SwarmRegistryUniversal.TagType.GENERIC); + assertEq(swarmId, expectedId); + } + + function test_RevertIf_registerSwarm_duplicateSwarm() public { + uint256 fleetId = _registerFleet(fleetOwner, "f1"); + uint256 providerId = _registerProvider(providerOwner, "url1"); + + _registerSwarm(fleetOwner, fleetId, providerId, new bytes(32), 8, SwarmRegistryUniversal.TagType.GENERIC); + + vm.prank(fleetOwner); + vm.expectRevert(SwarmRegistryUniversal.SwarmAlreadyExists.selector); + swarmRegistry.registerSwarm(_getFleetUuid(fleetId), providerId, new bytes(32), 8, SwarmRegistryUniversal.TagType.GENERIC); + } + + function test_registerSwarm_emitsSwarmRegistered() public { + uint256 fleetId = _registerFleet(fleetOwner, "f1"); + uint256 providerId = _registerProvider(providerOwner, "url1"); + + bytes memory filter = new bytes(50); + uint256 expectedId = swarmRegistry.computeSwarmId(_getFleetUuid(fleetId), providerId, filter); + + vm.expectEmit(true, true, true, true); + emit SwarmRegistered(expectedId, _getFleetUuid(fleetId), providerId, fleetOwner, 50); + + _registerSwarm(fleetOwner, fleetId, providerId, filter, 16, SwarmRegistryUniversal.TagType.GENERIC); + } + + function test_registerSwarm_linksUuidSwarms() public { + uint256 fleetId = _registerFleet(fleetOwner, "f1"); + uint256 providerId1 = _registerProvider(providerOwner, "url1"); + uint256 providerId2 = _registerProvider(providerOwner, "url2"); + + uint256 s1 = + _registerSwarm(fleetOwner, fleetId, providerId1, new bytes(50), 8, SwarmRegistryUniversal.TagType.GENERIC); + uint256 s2 = + _registerSwarm(fleetOwner, fleetId, providerId2, new bytes(50), 8, SwarmRegistryUniversal.TagType.GENERIC); + + assertEq(swarmRegistry.uuidSwarms(_getFleetUuid(fleetId), 0), s1); + assertEq(swarmRegistry.uuidSwarms(_getFleetUuid(fleetId), 1), s2); + } + + function test_registerSwarm_allTagTypes() public { + uint256 fleetId1 = _registerFleet(fleetOwner, "f1"); + uint256 fleetId2 = _registerFleet(fleetOwner, "f2"); + uint256 fleetId3 = _registerFleet(fleetOwner, "f3"); + uint256 fleetId4 = _registerFleet(fleetOwner, "f4"); + uint256 providerId = _registerProvider(providerOwner, "url"); + + uint256 s1 = _registerSwarm( + fleetOwner, fleetId1, providerId, new bytes(32), 8, SwarmRegistryUniversal.TagType.IBEACON_PAYLOAD_ONLY + ); + uint256 s2 = _registerSwarm( + fleetOwner, fleetId2, providerId, new bytes(32), 8, SwarmRegistryUniversal.TagType.IBEACON_INCLUDES_MAC + ); + uint256 s3 = + _registerSwarm(fleetOwner, fleetId3, providerId, new bytes(32), 8, SwarmRegistryUniversal.TagType.VENDOR_ID); + uint256 s4 = + _registerSwarm(fleetOwner, fleetId4, providerId, new bytes(32), 8, SwarmRegistryUniversal.TagType.GENERIC); + + (,,,, SwarmRegistryUniversal.TagType t1,) = swarmRegistry.swarms(s1); + (,,,, SwarmRegistryUniversal.TagType t2,) = swarmRegistry.swarms(s2); + (,,,, SwarmRegistryUniversal.TagType t3,) = swarmRegistry.swarms(s3); + (,,,, SwarmRegistryUniversal.TagType t4,) = swarmRegistry.swarms(s4); + + assertEq(uint8(t1), uint8(SwarmRegistryUniversal.TagType.IBEACON_PAYLOAD_ONLY)); + assertEq(uint8(t2), uint8(SwarmRegistryUniversal.TagType.IBEACON_INCLUDES_MAC)); + assertEq(uint8(t3), uint8(SwarmRegistryUniversal.TagType.VENDOR_ID)); + assertEq(uint8(t4), uint8(SwarmRegistryUniversal.TagType.GENERIC)); + } + + // ============================== + // registerSwarm — reverts + // ============================== + + function test_RevertIf_registerSwarm_notFleetOwner() public { + uint256 fleetId = _registerFleet(fleetOwner, "my-fleet"); + + vm.prank(caller); + vm.expectRevert(SwarmRegistryUniversal.NotUuidOwner.selector); + swarmRegistry.registerSwarm(_getFleetUuid(fleetId), 1, new bytes(10), 16, SwarmRegistryUniversal.TagType.GENERIC); + } + + function test_RevertIf_registerSwarm_fingerprintSizeZero() public { + uint256 fleetId = _registerFleet(fleetOwner, "f1"); + uint256 providerId = _registerProvider(providerOwner, "url1"); + + vm.prank(fleetOwner); + vm.expectRevert(SwarmRegistryUniversal.InvalidFingerprintSize.selector); + swarmRegistry.registerSwarm(_getFleetUuid(fleetId), providerId, new bytes(32), 0, SwarmRegistryUniversal.TagType.GENERIC); + } + + function test_RevertIf_registerSwarm_fingerprintSizeExceedsMax() public { + uint256 fleetId = _registerFleet(fleetOwner, "f1"); + uint256 providerId = _registerProvider(providerOwner, "url1"); + + vm.prank(fleetOwner); + vm.expectRevert(SwarmRegistryUniversal.InvalidFingerprintSize.selector); + swarmRegistry.registerSwarm(_getFleetUuid(fleetId), providerId, new bytes(32), 17, SwarmRegistryUniversal.TagType.GENERIC); + } + + function test_RevertIf_registerSwarm_emptyFilter() public { + uint256 fleetId = _registerFleet(fleetOwner, "f1"); + uint256 providerId = _registerProvider(providerOwner, "url1"); + + vm.prank(fleetOwner); + vm.expectRevert(SwarmRegistryUniversal.InvalidFilterSize.selector); + swarmRegistry.registerSwarm(_getFleetUuid(fleetId), providerId, new bytes(0), 8, SwarmRegistryUniversal.TagType.GENERIC); + } + + function test_RevertIf_registerSwarm_filterTooLarge() public { + uint256 fleetId = _registerFleet(fleetOwner, "f1"); + uint256 providerId = _registerProvider(providerOwner, "url1"); + + vm.prank(fleetOwner); + vm.expectRevert(SwarmRegistryUniversal.FilterTooLarge.selector); + swarmRegistry.registerSwarm(_getFleetUuid(fleetId), providerId, new bytes(24577), 8, SwarmRegistryUniversal.TagType.GENERIC); + } + + function test_registerSwarm_maxFingerprintSize() public { + uint256 fleetId = _registerFleet(fleetOwner, "f1"); + uint256 providerId = _registerProvider(providerOwner, "url1"); + + uint256 swarmId = + _registerSwarm(fleetOwner, fleetId, providerId, new bytes(100), 16, SwarmRegistryUniversal.TagType.GENERIC); + assertTrue(swarmId != 0); + } + + function test_registerSwarm_maxFilterSize() public { + uint256 fleetId = _registerFleet(fleetOwner, "f1"); + uint256 providerId = _registerProvider(providerOwner, "url1"); + + // Exactly MAX_FILTER_SIZE (24576) should succeed + uint256 swarmId = + _registerSwarm(fleetOwner, fleetId, providerId, new bytes(24576), 8, SwarmRegistryUniversal.TagType.GENERIC); + assertTrue(swarmId != 0); + } + + function test_registerSwarm_minFilterSize() public { + uint256 fleetId = _registerFleet(fleetOwner, "f1"); + uint256 providerId = _registerProvider(providerOwner, "url1"); + + // 1 byte filter + uint256 swarmId = + _registerSwarm(fleetOwner, fleetId, providerId, new bytes(1), 8, SwarmRegistryUniversal.TagType.GENERIC); + assertTrue(swarmId != 0); + } + + // ============================== + // acceptSwarm / rejectSwarm + // ============================== + + function test_acceptSwarm_setsStatusAndEmits() public { + uint256 fleetId = _registerFleet(fleetOwner, "f1"); + uint256 providerId = _registerProvider(providerOwner, "url1"); + uint256 swarmId = + _registerSwarm(fleetOwner, fleetId, providerId, new bytes(50), 8, SwarmRegistryUniversal.TagType.GENERIC); + + vm.expectEmit(true, true, true, true); + emit SwarmStatusChanged(swarmId, SwarmRegistryUniversal.SwarmStatus.ACCEPTED); + + vm.prank(providerOwner); + swarmRegistry.acceptSwarm(swarmId); + + (,,,,, SwarmRegistryUniversal.SwarmStatus status) = swarmRegistry.swarms(swarmId); + assertEq(uint8(status), uint8(SwarmRegistryUniversal.SwarmStatus.ACCEPTED)); + } + + function test_rejectSwarm_setsStatusAndEmits() public { + uint256 fleetId = _registerFleet(fleetOwner, "f1"); + uint256 providerId = _registerProvider(providerOwner, "url1"); + uint256 swarmId = + _registerSwarm(fleetOwner, fleetId, providerId, new bytes(50), 8, SwarmRegistryUniversal.TagType.GENERIC); + + vm.expectEmit(true, true, true, true); + emit SwarmStatusChanged(swarmId, SwarmRegistryUniversal.SwarmStatus.REJECTED); + + vm.prank(providerOwner); + swarmRegistry.rejectSwarm(swarmId); + + (,,,,, SwarmRegistryUniversal.SwarmStatus status) = swarmRegistry.swarms(swarmId); + assertEq(uint8(status), uint8(SwarmRegistryUniversal.SwarmStatus.REJECTED)); + } + + function test_RevertIf_acceptSwarm_notProviderOwner() public { + uint256 fleetId = _registerFleet(fleetOwner, "f1"); + uint256 providerId = _registerProvider(providerOwner, "url1"); + uint256 swarmId = + _registerSwarm(fleetOwner, fleetId, providerId, new bytes(50), 8, SwarmRegistryUniversal.TagType.GENERIC); + + vm.prank(caller); + vm.expectRevert(SwarmRegistryUniversal.NotProviderOwner.selector); + swarmRegistry.acceptSwarm(swarmId); + } + + function test_RevertIf_rejectSwarm_notProviderOwner() public { + uint256 fleetId = _registerFleet(fleetOwner, "f1"); + uint256 providerId = _registerProvider(providerOwner, "url1"); + uint256 swarmId = + _registerSwarm(fleetOwner, fleetId, providerId, new bytes(50), 8, SwarmRegistryUniversal.TagType.GENERIC); + + vm.prank(fleetOwner); // fleet owner != provider owner + vm.expectRevert(SwarmRegistryUniversal.NotProviderOwner.selector); + swarmRegistry.rejectSwarm(swarmId); + } + + function test_RevertIf_acceptSwarm_fleetOwnerNotProvider() public { + uint256 fleetId = _registerFleet(fleetOwner, "f1"); + uint256 providerId = _registerProvider(providerOwner, "url1"); + uint256 swarmId = + _registerSwarm(fleetOwner, fleetId, providerId, new bytes(50), 8, SwarmRegistryUniversal.TagType.GENERIC); + + vm.prank(fleetOwner); + vm.expectRevert(SwarmRegistryUniversal.NotProviderOwner.selector); + swarmRegistry.acceptSwarm(swarmId); + } + + function test_acceptSwarm_afterReject() public { + uint256 fleetId = _registerFleet(fleetOwner, "f1"); + uint256 providerId = _registerProvider(providerOwner, "url1"); + uint256 swarmId = + _registerSwarm(fleetOwner, fleetId, providerId, new bytes(50), 8, SwarmRegistryUniversal.TagType.GENERIC); + + vm.prank(providerOwner); + swarmRegistry.rejectSwarm(swarmId); + + vm.prank(providerOwner); + swarmRegistry.acceptSwarm(swarmId); + + (,,,,, SwarmRegistryUniversal.SwarmStatus status) = swarmRegistry.swarms(swarmId); + assertEq(uint8(status), uint8(SwarmRegistryUniversal.SwarmStatus.ACCEPTED)); + } + + function test_rejectSwarm_afterAccept() public { + uint256 fleetId = _registerFleet(fleetOwner, "f1"); + uint256 providerId = _registerProvider(providerOwner, "url1"); + uint256 swarmId = + _registerSwarm(fleetOwner, fleetId, providerId, new bytes(50), 8, SwarmRegistryUniversal.TagType.GENERIC); + + vm.prank(providerOwner); + swarmRegistry.acceptSwarm(swarmId); + + vm.prank(providerOwner); + swarmRegistry.rejectSwarm(swarmId); + + (,,,,, SwarmRegistryUniversal.SwarmStatus status) = swarmRegistry.swarms(swarmId); + assertEq(uint8(status), uint8(SwarmRegistryUniversal.SwarmStatus.REJECTED)); + } + + // ============================== + // checkMembership — XOR logic + // ============================== + + function test_checkMembership_XORLogic16Bit() public { + uint256 fleetId = _registerFleet(fleetOwner, "f1"); + uint256 providerId = _registerProvider(providerOwner, "u1"); + + bytes memory tagId = hex"1122334455"; + uint8 fpSize = 16; + uint256 dataLen = 100; + uint256 m = (dataLen * 8) / fpSize; // 50 slots + + (uint32 h1, uint32 h2, uint32 h3, uint256 expectedFp) = getExpectedValues(tagId, m, fpSize); + + if (h1 == h2 || h1 == h3 || h2 == h3) { + return; + } + + bytes memory filter = new bytes(dataLen); + _write16Bit(filter, h1, uint16(expectedFp)); + + uint256 swarmId = + _registerSwarm(fleetOwner, fleetId, providerId, filter, fpSize, SwarmRegistryUniversal.TagType.GENERIC); + + bytes32 tagHash = keccak256(tagId); + assertTrue(swarmRegistry.checkMembership(swarmId, tagHash), "Tag should be member"); + + bytes32 fakeHash = keccak256("not-a-tag"); + assertFalse(swarmRegistry.checkMembership(swarmId, fakeHash), "Fake tag should not be member"); + } + + function test_checkMembership_XORLogic8Bit() public { + uint256 fleetId = _registerFleet(fleetOwner, "f1"); + uint256 providerId = _registerProvider(providerOwner, "u1"); + + bytes memory tagId = hex"AABBCCDD"; + uint8 fpSize = 8; + uint256 dataLen = 80; + uint256 m = (dataLen * 8) / fpSize; // 80 slots + + (uint32 h1, uint32 h2, uint32 h3, uint256 expectedFp) = getExpectedValues(tagId, m, fpSize); + + if (h1 == h2 || h1 == h3 || h2 == h3) { + return; + } + + bytes memory filter = new bytes(dataLen); + _write8Bit(filter, h1, uint8(expectedFp)); + + uint256 swarmId = + _registerSwarm(fleetOwner, fleetId, providerId, filter, fpSize, SwarmRegistryUniversal.TagType.GENERIC); + + assertTrue(swarmRegistry.checkMembership(swarmId, keccak256(tagId)), "8-bit valid tag should pass"); + assertFalse(swarmRegistry.checkMembership(swarmId, keccak256(hex"FFFFFF")), "8-bit invalid tag should fail"); + } + + function test_RevertIf_checkMembership_swarmNotFound() public { + vm.expectRevert(SwarmRegistryUniversal.SwarmNotFound.selector); + swarmRegistry.checkMembership(999, keccak256("anything")); + } + + function test_checkMembership_allZeroFilter_returnsConsistent() public { + uint256 fleetId = _registerFleet(fleetOwner, "f1"); + uint256 providerId = _registerProvider(providerOwner, "u1"); + + // All-zero filter: f1^f2^f3 = 0^0^0 = 0 + bytes memory filter = new bytes(64); + uint256 swarmId = + _registerSwarm(fleetOwner, fleetId, providerId, filter, 16, SwarmRegistryUniversal.TagType.GENERIC); + + // Should not revert regardless of result + swarmRegistry.checkMembership(swarmId, keccak256("test1")); + swarmRegistry.checkMembership(swarmId, keccak256("test2")); + } + + // ============================== + // getFilterData + // ============================== + + function test_getFilterData_returnsCorrectData() public { + uint256 fleetId = _registerFleet(fleetOwner, "f1"); + uint256 providerId = _registerProvider(providerOwner, "url1"); + + bytes memory filter = new bytes(100); + filter[0] = 0xFF; + filter[99] = 0x01; + + uint256 swarmId = + _registerSwarm(fleetOwner, fleetId, providerId, filter, 16, SwarmRegistryUniversal.TagType.GENERIC); + + bytes memory stored = swarmRegistry.getFilterData(swarmId); + assertEq(stored.length, 100); + assertEq(uint8(stored[0]), 0xFF); + assertEq(uint8(stored[99]), 0x01); + } + + function test_RevertIf_getFilterData_swarmNotFound() public { + vm.expectRevert(SwarmRegistryUniversal.SwarmNotFound.selector); + swarmRegistry.getFilterData(999); + } + + // ============================== + // Multiple swarms per fleet + // ============================== + + function test_multipleSwarms_sameFleet() public { + uint256 fleetId = _registerFleet(fleetOwner, "f1"); + uint256 providerId1 = _registerProvider(providerOwner, "url1"); + uint256 providerId2 = _registerProvider(providerOwner, "url2"); + uint256 providerId3 = _registerProvider(providerOwner, "url3"); + + uint256 s1 = + _registerSwarm(fleetOwner, fleetId, providerId1, new bytes(32), 8, SwarmRegistryUniversal.TagType.GENERIC); + uint256 s2 = _registerSwarm( + fleetOwner, fleetId, providerId2, new bytes(64), 16, SwarmRegistryUniversal.TagType.VENDOR_ID + ); + uint256 s3 = _registerSwarm( + fleetOwner, fleetId, providerId3, new bytes(50), 12, SwarmRegistryUniversal.TagType.IBEACON_PAYLOAD_ONLY + ); + + // IDs are distinct hashes + assertTrue(s1 != s2 && s2 != s3 && s1 != s3); + + assertEq(swarmRegistry.uuidSwarms(_getFleetUuid(fleetId), 0), s1); + assertEq(swarmRegistry.uuidSwarms(_getFleetUuid(fleetId), 1), s2); + assertEq(swarmRegistry.uuidSwarms(_getFleetUuid(fleetId), 2), s3); + } + + // ============================== + // Constants + // ============================== + + function test_constants() public view { + assertEq(swarmRegistry.MAX_FINGERPRINT_SIZE(), 16); + assertEq(swarmRegistry.MAX_FILTER_SIZE(), 24576); + } + + // ============================== + // Fuzz + // ============================== + + function testFuzz_registerSwarm_validFingerprintSizes(uint8 fpSize) public { + fpSize = uint8(bound(fpSize, 1, 16)); + + uint256 fleetId = _registerFleet(fleetOwner, abi.encodePacked("fleet-", fpSize)); + uint256 providerId = _registerProvider(providerOwner, string(abi.encodePacked("url-", fpSize))); + + uint256 swarmId = _registerSwarm( + fleetOwner, fleetId, providerId, new bytes(64), fpSize, SwarmRegistryUniversal.TagType.GENERIC + ); + + (,,, uint8 storedFp,,) = swarmRegistry.swarms(swarmId); + assertEq(storedFp, fpSize); + } + + function testFuzz_registerSwarm_invalidFingerprintSizes(uint8 fpSize) public { + vm.assume(fpSize == 0 || fpSize > 16); + + uint256 fleetId = _registerFleet(fleetOwner, "f1"); + uint256 providerId = _registerProvider(providerOwner, "url1"); + + vm.prank(fleetOwner); + vm.expectRevert(SwarmRegistryUniversal.InvalidFingerprintSize.selector); + swarmRegistry.registerSwarm(_getFleetUuid(fleetId), providerId, new bytes(32), fpSize, SwarmRegistryUniversal.TagType.GENERIC); + } + + function testFuzz_registerSwarm_filterSizeRange(uint256 size) public { + size = bound(size, 1, 24576); + + uint256 fleetId = _registerFleet(fleetOwner, abi.encodePacked("f-", size)); + uint256 providerId = _registerProvider(providerOwner, string(abi.encodePacked("url-", size))); + + uint256 swarmId = + _registerSwarm(fleetOwner, fleetId, providerId, new bytes(size), 8, SwarmRegistryUniversal.TagType.GENERIC); + + (,, uint32 storedLen,,,) = swarmRegistry.swarms(swarmId); + assertEq(storedLen, uint32(size)); + } + + // ============================== + // updateSwarmFilter + // ============================== + + function test_updateSwarmFilter_updatesFilterAndResetsStatus() public { + uint256 fleetId = _registerFleet(fleetOwner, "f1"); + uint256 providerId = _registerProvider(providerOwner, "url1"); + uint256 swarmId = + _registerSwarm(fleetOwner, fleetId, providerId, new bytes(50), 8, SwarmRegistryUniversal.TagType.GENERIC); + + // Provider accepts + vm.prank(providerOwner); + swarmRegistry.acceptSwarm(swarmId); + + // Fleet owner updates filter + bytes memory newFilter = new bytes(100); + for (uint256 i = 0; i < 100; i++) { + newFilter[i] = bytes1(uint8(i % 256)); + } + + vm.expectEmit(true, true, true, true); + emit SwarmFilterUpdated(swarmId, fleetOwner, 100); + + vm.prank(fleetOwner); + swarmRegistry.updateSwarmFilter(swarmId, newFilter); + + // Status should be reset to REGISTERED + (,, uint32 filterLength,,, SwarmRegistryUniversal.SwarmStatus status) = swarmRegistry.swarms(swarmId); + assertEq(uint8(status), uint8(SwarmRegistryUniversal.SwarmStatus.REGISTERED)); + assertEq(filterLength, 100); + } + + function test_updateSwarmFilter_changesFilterLength() public { + uint256 fleetId = _registerFleet(fleetOwner, "f1"); + uint256 providerId = _registerProvider(providerOwner, "url1"); + uint256 swarmId = + _registerSwarm(fleetOwner, fleetId, providerId, new bytes(50), 8, SwarmRegistryUniversal.TagType.GENERIC); + + (,, uint32 oldLen,,,) = swarmRegistry.swarms(swarmId); + assertEq(oldLen, 50); + + bytes memory newFilter = new bytes(100); + vm.prank(fleetOwner); + swarmRegistry.updateSwarmFilter(swarmId, newFilter); + + (,, uint32 newLen,,,) = swarmRegistry.swarms(swarmId); + assertEq(newLen, 100); + } + + function test_RevertIf_updateSwarmFilter_swarmNotFound() public { + vm.prank(fleetOwner); + vm.expectRevert(SwarmRegistryUniversal.SwarmNotFound.selector); + swarmRegistry.updateSwarmFilter(999, new bytes(50)); + } + + function test_RevertIf_updateSwarmFilter_notFleetOwner() public { + uint256 fleetId = _registerFleet(fleetOwner, "f1"); + uint256 providerId = _registerProvider(providerOwner, "url1"); + uint256 swarmId = + _registerSwarm(fleetOwner, fleetId, providerId, new bytes(50), 8, SwarmRegistryUniversal.TagType.GENERIC); + + vm.prank(caller); + vm.expectRevert(SwarmRegistryUniversal.NotUuidOwner.selector); + swarmRegistry.updateSwarmFilter(swarmId, new bytes(100)); + } + + function test_RevertIf_updateSwarmFilter_emptyFilter() public { + uint256 fleetId = _registerFleet(fleetOwner, "f1"); + uint256 providerId = _registerProvider(providerOwner, "url1"); + uint256 swarmId = + _registerSwarm(fleetOwner, fleetId, providerId, new bytes(50), 8, SwarmRegistryUniversal.TagType.GENERIC); + + vm.prank(fleetOwner); + vm.expectRevert(SwarmRegistryUniversal.InvalidFilterSize.selector); + swarmRegistry.updateSwarmFilter(swarmId, new bytes(0)); + } + + function test_RevertIf_updateSwarmFilter_filterTooLarge() public { + uint256 fleetId = _registerFleet(fleetOwner, "f1"); + uint256 providerId = _registerProvider(providerOwner, "url1"); + uint256 swarmId = + _registerSwarm(fleetOwner, fleetId, providerId, new bytes(50), 8, SwarmRegistryUniversal.TagType.GENERIC); + + vm.prank(fleetOwner); + vm.expectRevert(SwarmRegistryUniversal.FilterTooLarge.selector); + swarmRegistry.updateSwarmFilter(swarmId, new bytes(24577)); + } + + // ============================== + // updateSwarmProvider + // ============================== + + function test_updateSwarmProvider_updatesProviderAndResetsStatus() public { + uint256 fleetId = _registerFleet(fleetOwner, "f1"); + uint256 providerId1 = _registerProvider(providerOwner, "url1"); + uint256 providerId2 = _registerProvider(providerOwner, "url2"); + + uint256 swarmId = + _registerSwarm(fleetOwner, fleetId, providerId1, new bytes(50), 8, SwarmRegistryUniversal.TagType.GENERIC); + + // Provider accepts + vm.prank(providerOwner); + swarmRegistry.acceptSwarm(swarmId); + + // Fleet owner updates provider + vm.expectEmit(true, true, true, true); + emit SwarmProviderUpdated(swarmId, providerId1, providerId2); + + vm.prank(fleetOwner); + swarmRegistry.updateSwarmProvider(swarmId, providerId2); + + // Check new provider and status reset + (, uint256 newProviderId,,,, SwarmRegistryUniversal.SwarmStatus status) = swarmRegistry.swarms(swarmId); + assertEq(newProviderId, providerId2); + assertEq(uint8(status), uint8(SwarmRegistryUniversal.SwarmStatus.REGISTERED)); + } + + function test_RevertIf_updateSwarmProvider_swarmNotFound() public { + uint256 providerId = _registerProvider(providerOwner, "url1"); + + vm.prank(fleetOwner); + vm.expectRevert(SwarmRegistryUniversal.SwarmNotFound.selector); + swarmRegistry.updateSwarmProvider(999, providerId); + } + + function test_RevertIf_updateSwarmProvider_notFleetOwner() public { + uint256 fleetId = _registerFleet(fleetOwner, "f1"); + uint256 providerId1 = _registerProvider(providerOwner, "url1"); + uint256 providerId2 = _registerProvider(providerOwner, "url2"); + + uint256 swarmId = + _registerSwarm(fleetOwner, fleetId, providerId1, new bytes(50), 8, SwarmRegistryUniversal.TagType.GENERIC); + + vm.prank(caller); + vm.expectRevert(SwarmRegistryUniversal.NotUuidOwner.selector); + swarmRegistry.updateSwarmProvider(swarmId, providerId2); + } + + function test_RevertIf_updateSwarmProvider_providerDoesNotExist() public { + uint256 fleetId = _registerFleet(fleetOwner, "f1"); + uint256 providerId = _registerProvider(providerOwner, "url1"); + + uint256 swarmId = + _registerSwarm(fleetOwner, fleetId, providerId, new bytes(50), 8, SwarmRegistryUniversal.TagType.GENERIC); + + vm.prank(fleetOwner); + // ERC721 reverts before our custom error is reached + vm.expectRevert(); + swarmRegistry.updateSwarmProvider(swarmId, 99999); + } + + // ============================== + // deleteSwarm + // ============================== + + function test_deleteSwarm_removesSwarmAndEmits() public { + uint256 fleetId = _registerFleet(fleetOwner, "f1"); + uint256 providerId = _registerProvider(providerOwner, "url1"); + uint256 swarmId = + _registerSwarm(fleetOwner, fleetId, providerId, new bytes(50), 8, SwarmRegistryUniversal.TagType.GENERIC); + + vm.expectEmit(true, true, true, true); + emit SwarmDeleted(swarmId, _getFleetUuid(fleetId), fleetOwner); + + vm.prank(fleetOwner); + swarmRegistry.deleteSwarm(swarmId); + + // Swarm should be zeroed + (bytes16 fleetUuidAfter,, uint32 filterLength,,,) = swarmRegistry.swarms(swarmId); + assertEq(fleetUuidAfter, bytes16(0)); + assertEq(filterLength, 0); + } + + function test_deleteSwarm_removesFromUuidSwarms() public { + uint256 fleetId = _registerFleet(fleetOwner, "f1"); + uint256 providerId1 = _registerProvider(providerOwner, "url1"); + uint256 providerId2 = _registerProvider(providerOwner, "url2"); + + uint256 swarm1 = + _registerSwarm(fleetOwner, fleetId, providerId1, new bytes(50), 8, SwarmRegistryUniversal.TagType.GENERIC); + uint256 swarm2 = + _registerSwarm(fleetOwner, fleetId, providerId2, new bytes(50), 8, SwarmRegistryUniversal.TagType.GENERIC); + + // Delete first swarm + vm.prank(fleetOwner); + swarmRegistry.deleteSwarm(swarm1); + + // Only swarm2 should remain in fleetSwarms + assertEq(swarmRegistry.uuidSwarms(_getFleetUuid(fleetId), 0), swarm2); + vm.expectRevert(); + swarmRegistry.uuidSwarms(_getFleetUuid(fleetId), 1); // Should be out of bounds + } + + function test_deleteSwarm_swapAndPop() public { + uint256 fleetId = _registerFleet(fleetOwner, "f1"); + uint256 providerId1 = _registerProvider(providerOwner, "url1"); + uint256 providerId2 = _registerProvider(providerOwner, "url2"); + uint256 providerId3 = _registerProvider(providerOwner, "url3"); + + uint256 swarm1 = + _registerSwarm(fleetOwner, fleetId, providerId1, new bytes(50), 8, SwarmRegistryUniversal.TagType.GENERIC); + uint256 swarm2 = + _registerSwarm(fleetOwner, fleetId, providerId2, new bytes(50), 8, SwarmRegistryUniversal.TagType.GENERIC); + uint256 swarm3 = + _registerSwarm(fleetOwner, fleetId, providerId3, new bytes(50), 8, SwarmRegistryUniversal.TagType.GENERIC); + + // Delete middle swarm + vm.prank(fleetOwner); + swarmRegistry.deleteSwarm(swarm2); + + // swarm3 should be swapped to index 1 + assertEq(swarmRegistry.uuidSwarms(_getFleetUuid(fleetId), 0), swarm1); + assertEq(swarmRegistry.uuidSwarms(_getFleetUuid(fleetId), 1), swarm3); + vm.expectRevert(); + swarmRegistry.uuidSwarms(_getFleetUuid(fleetId), 2); // Should be out of bounds + } + + function test_deleteSwarm_clearsFilterData() public { + uint256 fleetId = _registerFleet(fleetOwner, "f1"); + uint256 providerId = _registerProvider(providerOwner, "url1"); + + bytes memory filterData = new bytes(50); + for (uint256 i = 0; i < 50; i++) { + filterData[i] = bytes1(uint8(i)); + } + + uint256 swarmId = + _registerSwarm(fleetOwner, fleetId, providerId, filterData, 8, SwarmRegistryUniversal.TagType.GENERIC); + + // Delete swarm + vm.prank(fleetOwner); + swarmRegistry.deleteSwarm(swarmId); + + // filterLength should be cleared + (,, uint32 filterLength,,,) = swarmRegistry.swarms(swarmId); + assertEq(filterLength, 0); + } + + function test_RevertIf_deleteSwarm_swarmNotFound() public { + vm.prank(fleetOwner); + vm.expectRevert(SwarmRegistryUniversal.SwarmNotFound.selector); + swarmRegistry.deleteSwarm(999); + } + + function test_RevertIf_deleteSwarm_notFleetOwner() public { + uint256 fleetId = _registerFleet(fleetOwner, "f1"); + uint256 providerId = _registerProvider(providerOwner, "url1"); + uint256 swarmId = + _registerSwarm(fleetOwner, fleetId, providerId, new bytes(50), 8, SwarmRegistryUniversal.TagType.GENERIC); + + vm.prank(caller); + vm.expectRevert(SwarmRegistryUniversal.NotUuidOwner.selector); + swarmRegistry.deleteSwarm(swarmId); + } + + function test_deleteSwarm_afterUpdate() public { + uint256 fleetId = _registerFleet(fleetOwner, "f1"); + uint256 providerId = _registerProvider(providerOwner, "url1"); + uint256 swarmId = + _registerSwarm(fleetOwner, fleetId, providerId, new bytes(50), 8, SwarmRegistryUniversal.TagType.GENERIC); + + // Update then delete + vm.prank(fleetOwner); + swarmRegistry.updateSwarmFilter(swarmId, new bytes(100)); + + vm.prank(fleetOwner); + swarmRegistry.deleteSwarm(swarmId); + + (bytes16 fleetUuidAfter,,,,,) = swarmRegistry.swarms(swarmId); + assertEq(fleetUuidAfter, bytes16(0)); + } + + function test_deleteSwarm_updatesSwarmIndexInUuid() public { + uint256 fleetId = _registerFleet(fleetOwner, "f1"); + uint256 p1 = _registerProvider(providerOwner, "url1"); + uint256 p2 = _registerProvider(providerOwner, "url2"); + uint256 p3 = _registerProvider(providerOwner, "url3"); + + uint256 s1 = _registerSwarm(fleetOwner, fleetId, p1, new bytes(50), 8, SwarmRegistryUniversal.TagType.GENERIC); + uint256 s2 = _registerSwarm(fleetOwner, fleetId, p2, new bytes(50), 8, SwarmRegistryUniversal.TagType.GENERIC); + uint256 s3 = _registerSwarm(fleetOwner, fleetId, p3, new bytes(50), 8, SwarmRegistryUniversal.TagType.GENERIC); + + // Verify initial indices + assertEq(swarmRegistry.swarmIndexInUuid(s1), 0); + assertEq(swarmRegistry.swarmIndexInUuid(s2), 1); + assertEq(swarmRegistry.swarmIndexInUuid(s3), 2); + + // Delete s1 — s3 should be swapped to index 0 + vm.prank(fleetOwner); + swarmRegistry.deleteSwarm(s1); + + assertEq(swarmRegistry.swarmIndexInUuid(s3), 0); + assertEq(swarmRegistry.swarmIndexInUuid(s2), 1); + assertEq(swarmRegistry.swarmIndexInUuid(s1), 0); // deleted, reset to 0 + } + + // ============================== + // isSwarmValid + // ============================== + + function test_isSwarmValid_bothValid() public { + uint256 fleetId = _registerFleet(fleetOwner, "f1"); + uint256 providerId = _registerProvider(providerOwner, "url1"); + uint256 swarmId = + _registerSwarm(fleetOwner, fleetId, providerId, new bytes(50), 8, SwarmRegistryUniversal.TagType.GENERIC); + + (bool fleetValid, bool providerValid) = swarmRegistry.isSwarmValid(swarmId); + assertTrue(fleetValid); + assertTrue(providerValid); + } + + function test_isSwarmValid_providerBurned() public { + uint256 fleetId = _registerFleet(fleetOwner, "f1"); + uint256 providerId = _registerProvider(providerOwner, "url1"); + uint256 swarmId = + _registerSwarm(fleetOwner, fleetId, providerId, new bytes(50), 8, SwarmRegistryUniversal.TagType.GENERIC); + + vm.prank(providerOwner); + providerContract.burn(providerId); + + (bool fleetValid, bool providerValid) = swarmRegistry.isSwarmValid(swarmId); + assertTrue(fleetValid); + assertFalse(providerValid); + } + + function test_isSwarmValid_fleetBurned() public { + uint256 fleetId = _registerFleet(fleetOwner, "f1"); + uint256 providerId = _registerProvider(providerOwner, "url1"); + uint256 swarmId = + _registerSwarm(fleetOwner, fleetId, providerId, new bytes(50), 8, SwarmRegistryUniversal.TagType.GENERIC); + + // Burn registered fleet token (operator = owner for fresh registration) + // This mints an owned-only token back to the owner + vm.prank(fleetOwner); + fleetContract.burn(fleetId); + + // After burning registered token, UUID transitions to Owned state + // Need to burn the owned-only token to fully release + bytes16 uuid = _getFleetUuid(fleetId); + uint256 ownedTokenId = uint256(uint128(uuid)); // owned token has regionKey=0 + vm.prank(fleetOwner); + fleetContract.burn(ownedTokenId); + + (bool fleetValid, bool providerValid) = swarmRegistry.isSwarmValid(swarmId); + assertFalse(fleetValid); + assertTrue(providerValid); + } + + function test_isSwarmValid_bothBurned() public { + uint256 fleetId = _registerFleet(fleetOwner, "f1"); + uint256 providerId = _registerProvider(providerOwner, "url1"); + uint256 swarmId = + _registerSwarm(fleetOwner, fleetId, providerId, new bytes(50), 8, SwarmRegistryUniversal.TagType.GENERIC); + + // Burn registered fleet token → mints owned-only token + vm.prank(fleetOwner); + fleetContract.burn(fleetId); + + // Burn owned-only token to fully release UUID + bytes16 uuid = _getFleetUuid(fleetId); + uint256 ownedTokenId = uint256(uint128(uuid)); + vm.prank(fleetOwner); + fleetContract.burn(ownedTokenId); + + vm.prank(providerOwner); + providerContract.burn(providerId); + + (bool fleetValid, bool providerValid) = swarmRegistry.isSwarmValid(swarmId); + assertFalse(fleetValid); + assertFalse(providerValid); + } + + function test_RevertIf_isSwarmValid_swarmNotFound() public { + vm.expectRevert(SwarmRegistryUniversal.SwarmNotFound.selector); + swarmRegistry.isSwarmValid(999); + } + + // ============================== + // purgeOrphanedSwarm + // ============================== + + function test_purgeOrphanedSwarm_providerBurned() public { + uint256 fleetId = _registerFleet(fleetOwner, "f1"); + uint256 providerId = _registerProvider(providerOwner, "url1"); + uint256 swarmId = + _registerSwarm(fleetOwner, fleetId, providerId, new bytes(50), 8, SwarmRegistryUniversal.TagType.GENERIC); + + vm.prank(providerOwner); + providerContract.burn(providerId); + + vm.expectEmit(true, true, true, true); + emit SwarmPurged(swarmId, _getFleetUuid(fleetId), caller); + + vm.prank(caller); + swarmRegistry.purgeOrphanedSwarm(swarmId); + + (,, uint32 filterLength,,,) = swarmRegistry.swarms(swarmId); + assertEq(filterLength, 0); + } + + function test_purgeOrphanedSwarm_fleetBurned() public { + uint256 fleetId = _registerFleet(fleetOwner, "f1"); + uint256 providerId = _registerProvider(providerOwner, "url1"); + uint256 swarmId = + _registerSwarm(fleetOwner, fleetId, providerId, new bytes(50), 8, SwarmRegistryUniversal.TagType.GENERIC); + + // Burn registered fleet token → mints owned-only token + vm.prank(fleetOwner); + fleetContract.burn(fleetId); + + // Burn owned-only token to fully release UUID + bytes16 uuid = _getFleetUuid(fleetId); + uint256 ownedTokenId = uint256(uint128(uuid)); + vm.prank(fleetOwner); + fleetContract.burn(ownedTokenId); + + vm.prank(caller); + swarmRegistry.purgeOrphanedSwarm(swarmId); + + (bytes16 fUuid,, uint32 filterLength,,,) = swarmRegistry.swarms(swarmId); + assertEq(fUuid, bytes16(0)); + assertEq(filterLength, 0); + } + + function test_purgeOrphanedSwarm_removesFromUuidSwarms() public { + uint256 fleetId = _registerFleet(fleetOwner, "f1"); + uint256 p1 = _registerProvider(providerOwner, "url1"); + uint256 p2 = _registerProvider(providerOwner, "url2"); + + uint256 s1 = _registerSwarm(fleetOwner, fleetId, p1, new bytes(50), 8, SwarmRegistryUniversal.TagType.GENERIC); + uint256 s2 = _registerSwarm(fleetOwner, fleetId, p2, new bytes(50), 8, SwarmRegistryUniversal.TagType.GENERIC); + + // Burn provider of s1 + vm.prank(providerOwner); + providerContract.burn(p1); + + vm.prank(caller); + swarmRegistry.purgeOrphanedSwarm(s1); + + // s2 should be swapped to index 0 + assertEq(swarmRegistry.uuidSwarms(_getFleetUuid(fleetId), 0), s2); + vm.expectRevert(); + swarmRegistry.uuidSwarms(_getFleetUuid(fleetId), 1); + } + + function test_purgeOrphanedSwarm_clearsFilterData() public { + uint256 fleetId = _registerFleet(fleetOwner, "f1"); + uint256 providerId = _registerProvider(providerOwner, "url1"); + + bytes memory filter = new bytes(50); + for (uint256 i = 0; i < 50; i++) { + filter[i] = bytes1(uint8(i)); + } + + uint256 swarmId = + _registerSwarm(fleetOwner, fleetId, providerId, filter, 8, SwarmRegistryUniversal.TagType.GENERIC); + + vm.prank(providerOwner); + providerContract.burn(providerId); + + vm.prank(caller); + swarmRegistry.purgeOrphanedSwarm(swarmId); + + // filterLength should be cleared + (,, uint32 filterLength,,,) = swarmRegistry.swarms(swarmId); + assertEq(filterLength, 0); + } + + function test_RevertIf_purgeOrphanedSwarm_swarmNotFound() public { + vm.expectRevert(SwarmRegistryUniversal.SwarmNotFound.selector); + swarmRegistry.purgeOrphanedSwarm(999); + } + + function test_RevertIf_purgeOrphanedSwarm_swarmNotOrphaned() public { + uint256 fleetId = _registerFleet(fleetOwner, "f1"); + uint256 providerId = _registerProvider(providerOwner, "url1"); + uint256 swarmId = + _registerSwarm(fleetOwner, fleetId, providerId, new bytes(50), 8, SwarmRegistryUniversal.TagType.GENERIC); + + vm.expectRevert(SwarmRegistryUniversal.SwarmNotOrphaned.selector); + swarmRegistry.purgeOrphanedSwarm(swarmId); + } + + // ============================== + // Orphan guards on accept/reject/checkMembership + // ============================== + + function test_RevertIf_acceptSwarm_orphaned() public { + uint256 fleetId = _registerFleet(fleetOwner, "f1"); + uint256 providerId = _registerProvider(providerOwner, "url1"); + uint256 swarmId = + _registerSwarm(fleetOwner, fleetId, providerId, new bytes(50), 8, SwarmRegistryUniversal.TagType.GENERIC); + + vm.prank(providerOwner); + providerContract.burn(providerId); + + vm.prank(providerOwner); + vm.expectRevert(SwarmRegistryUniversal.SwarmOrphaned.selector); + swarmRegistry.acceptSwarm(swarmId); + } + + function test_RevertIf_rejectSwarm_orphaned() public { + uint256 fleetId = _registerFleet(fleetOwner, "f1"); + uint256 providerId = _registerProvider(providerOwner, "url1"); + uint256 swarmId = + _registerSwarm(fleetOwner, fleetId, providerId, new bytes(50), 8, SwarmRegistryUniversal.TagType.GENERIC); + + // Burn registered fleet token → mints owned-only token + vm.prank(fleetOwner); + fleetContract.burn(fleetId); + + // Burn owned-only token to fully release UUID + bytes16 uuid = _getFleetUuid(fleetId); + uint256 ownedTokenId = uint256(uint128(uuid)); + vm.prank(fleetOwner); + fleetContract.burn(ownedTokenId); + + vm.prank(providerOwner); + vm.expectRevert(SwarmRegistryUniversal.SwarmOrphaned.selector); + swarmRegistry.rejectSwarm(swarmId); + } + + function test_RevertIf_checkMembership_orphaned() public { + uint256 fleetId = _registerFleet(fleetOwner, "f1"); + uint256 providerId = _registerProvider(providerOwner, "url1"); + uint256 swarmId = + _registerSwarm(fleetOwner, fleetId, providerId, new bytes(50), 8, SwarmRegistryUniversal.TagType.GENERIC); + + vm.prank(providerOwner); + providerContract.burn(providerId); + + vm.expectRevert(SwarmRegistryUniversal.SwarmOrphaned.selector); + swarmRegistry.checkMembership(swarmId, keccak256("test")); + } + + function test_RevertIf_acceptSwarm_fleetBurned() public { + uint256 fleetId = _registerFleet(fleetOwner, "f1"); + uint256 providerId = _registerProvider(providerOwner, "url1"); + uint256 swarmId = + _registerSwarm(fleetOwner, fleetId, providerId, new bytes(50), 8, SwarmRegistryUniversal.TagType.GENERIC); + + // Burn registered fleet token → mints owned-only token + vm.prank(fleetOwner); + fleetContract.burn(fleetId); + + // Burn owned-only token to fully release UUID + bytes16 uuid = _getFleetUuid(fleetId); + uint256 ownedTokenId = uint256(uint128(uuid)); + vm.prank(fleetOwner); + fleetContract.burn(ownedTokenId); + + vm.prank(providerOwner); + vm.expectRevert(SwarmRegistryUniversal.SwarmOrphaned.selector); + swarmRegistry.acceptSwarm(swarmId); + } + + function test_purge_thenAcceptReverts() public { + uint256 fleetId = _registerFleet(fleetOwner, "f1"); + uint256 providerId = _registerProvider(providerOwner, "url1"); + uint256 swarmId = + _registerSwarm(fleetOwner, fleetId, providerId, new bytes(50), 8, SwarmRegistryUniversal.TagType.GENERIC); + + vm.prank(providerOwner); + providerContract.burn(providerId); + + vm.prank(caller); + swarmRegistry.purgeOrphanedSwarm(swarmId); + + // After purge, swarm no longer exists + vm.prank(providerOwner); + vm.expectRevert(SwarmRegistryUniversal.SwarmNotFound.selector); + swarmRegistry.acceptSwarm(swarmId); + } +} diff --git a/test/contentsign/BaseContentSign.t.sol b/test/contentsign/BaseContentSign.t.sol index b52438b3..ef5a5389 100644 --- a/test/contentsign/BaseContentSign.t.sol +++ b/test/contentsign/BaseContentSign.t.sol @@ -2,7 +2,7 @@ pragma solidity ^0.8.20; -import {Test, console} from "forge-std/Test.sol"; +import {Test} from "forge-std/Test.sol"; import {BaseContentSign} from "../../src/contentsign/BaseContentSign.sol"; contract MockContentSign is BaseContentSign { diff --git a/test/contentsign/PaymentMiddleware.t.sol b/test/contentsign/PaymentMiddleware.t.sol index 3d5c24b0..5c7bb6e6 100644 --- a/test/contentsign/PaymentMiddleware.t.sol +++ b/test/contentsign/PaymentMiddleware.t.sol @@ -2,14 +2,13 @@ pragma solidity ^0.8.20; -import {Test, console} from "forge-std/Test.sol"; +import {Test} from "forge-std/Test.sol"; import {BaseContentSign} from "../../src/contentsign/BaseContentSign.sol"; import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; import {IERC20Errors} from "@openzeppelin/contracts/interfaces/draft-IERC6093.sol"; import {ERC721} from "@openzeppelin/contracts/token/ERC721/ERC721.sol"; import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; import {PaymentMiddleware} from "../../src/contentsign/PaymentMiddleware.sol"; -import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; contract MockToken is ERC20 { constructor() ERC20("Mock Token", "MTK") {} From 80c463e58ae744493ba3d69de984d1e2e462ca9a Mon Sep 17 00:00:00 2001 From: Alex Sedighi Date: Tue, 3 Mar 2026 11:14:48 +1300 Subject: [PATCH 02/15] test: increase coverage to 96%+ and add CI coverage checks on PRs --- .github/workflows/checks.yml | 49 +++++++++++++++++++++++++++++++ test/FleetIdentity.t.sol | 47 +++++++++++++++++++++++++++++ test/SwarmRegistryL1.t.sol | 30 +++++++++++++++++++ test/SwarmRegistryUniversal.t.sol | 18 ++++++++++++ 4 files changed, 144 insertions(+) diff --git a/.github/workflows/checks.yml b/.github/workflows/checks.yml index 1ceaaf35..500f15ff 100644 --- a/.github/workflows/checks.yml +++ b/.github/workflows/checks.yml @@ -35,3 +35,52 @@ jobs: - name: Run tests run: forge test + + Coverage: + runs-on: ubuntu-latest + if: github.event_name == 'pull_request' && github.base_ref == 'main' + container: + image: ghcr.io/nodlecode/devcontainer-rollup + options: --user root + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + submodules: recursive + + - name: Install dependencies + run: yarn + + - name: Run coverage + run: forge coverage --ir-minimum --report lcov --report-file coverage.lcov + + - name: Upload coverage report + uses: actions/upload-artifact@v4 + with: + name: coverage-report + path: coverage.lcov + retention-days: 30 + + - name: Check line coverage threshold + run: | + # Extract line coverage from lcov report + LINES_FOUND=$(grep -E "^LF:" coverage.lcov | awk -F: '{sum += $2} END {print sum}') + LINES_HIT=$(grep -E "^LH:" coverage.lcov | awk -F: '{sum += $2} END {print sum}') + + if [ "$LINES_FOUND" -eq 0 ]; then + echo "Error: No lines found in coverage report" + exit 1 + fi + + COVERAGE=$(awk "BEGIN {printf \"%.2f\", ($LINES_HIT / $LINES_FOUND) * 100}") + echo "Line coverage: $COVERAGE% ($LINES_HIT / $LINES_FOUND lines)" + + # Check if coverage is below 95% + THRESHOLD=95 + if awk "BEGIN {exit !($COVERAGE < $THRESHOLD)}"; then + echo "Error: Line coverage ($COVERAGE%) is below the required threshold ($THRESHOLD%)" + exit 1 + fi + + echo "Coverage check passed: $COVERAGE% >= $THRESHOLD%" diff --git a/test/FleetIdentity.t.sol b/test/FleetIdentity.t.sol index b382b32f..4ef24315 100644 --- a/test/FleetIdentity.t.sol +++ b/test/FleetIdentity.t.sol @@ -811,6 +811,53 @@ contract FleetIdentityTest is Test { assertEq(fleet.getActiveAdminAreas().length, 0); } + function test_activeAdminAreas_multipleCountries() public { + // Register admin areas in multiple countries + vm.prank(alice); + fleet.registerFleetLocal(UUID_1, US, ADMIN_CA, 0); + vm.prank(bob); + fleet.registerFleetLocal(UUID_2, DE, ADMIN_CA, 0); + + uint32[] memory areas = fleet.getActiveAdminAreas(); + assertEq(areas.length, 2); + } + + function test_adminAreaSwapAndPop_whenNotLastArea() public { + // Register two admin areas in the same country + vm.prank(alice); + uint256 id1 = fleet.registerFleetLocal(UUID_1, US, ADMIN_CA, 0); + vm.prank(bob); + fleet.registerFleetLocal(UUID_2, US, ADMIN_NY, 0); + + // Burn the first one (not the last in the array) to trigger swap-and-pop + vm.prank(alice); + fleet.burn(id1); + + // Should still have one admin area + uint32[] memory areas = fleet.getActiveAdminAreas(); + assertEq(areas.length, 1); + // The remaining area should be ADMIN_NY + assertEq(areas[0], fleet.makeAdminRegion(US, ADMIN_NY)); + } + + function test_countrySwapAndPop_whenNotLastCountry() public { + // Register admin areas in multiple countries + vm.prank(alice); + uint256 id1 = fleet.registerFleetLocal(UUID_1, US, ADMIN_CA, 0); + vm.prank(bob); + fleet.registerFleetLocal(UUID_2, DE, ADMIN_CA, 0); + vm.prank(carol); + fleet.registerFleetLocal(UUID_3, FR, ADMIN_CA, 0); + + // Burn the first country's fleet (not the last country in the array) to trigger swap-and-pop + vm.prank(alice); + fleet.burn(id1); + + // Should still have two countries + uint16[] memory countries = fleet.getActiveCountries(); + assertEq(countries.length, 2); + } + // --- Region key helpers --- function test_makeAdminRegion() public view { diff --git a/test/SwarmRegistryL1.t.sol b/test/SwarmRegistryL1.t.sol index a896ae0d..e2ba3d20 100644 --- a/test/SwarmRegistryL1.t.sol +++ b/test/SwarmRegistryL1.t.sol @@ -260,6 +260,24 @@ contract SwarmRegistryL1Test is Test { swarmRegistry.registerSwarm(_getFleetUuid(fleetId), 1, new bytes(10), 16, SwarmRegistryL1.TagType.GENERIC); } + function test_RevertIf_registerSwarm_zeroUuid() public { + uint256 providerId = _registerProvider(providerOwner, "url1"); + + vm.prank(fleetOwner); + vm.expectRevert(SwarmRegistryL1.InvalidUuid.selector); + swarmRegistry.registerSwarm(bytes16(0), providerId, new bytes(32), 8, SwarmRegistryL1.TagType.GENERIC); + } + + function test_RevertIf_registerSwarm_providerDoesNotExist() public { + uint256 fleetId = _registerFleet(fleetOwner, "f1"); + uint256 nonExistentProvider = 12345; + + vm.prank(fleetOwner); + // ERC721.ownerOf reverts for non-existent tokens before our ProviderDoesNotExist check + vm.expectRevert(); + swarmRegistry.registerSwarm(_getFleetUuid(fleetId), nonExistentProvider, new bytes(32), 8, SwarmRegistryL1.TagType.GENERIC); + } + function test_RevertIf_registerSwarm_fingerprintSizeZero() public { uint256 fleetId = _registerFleet(fleetOwner, "f1"); uint256 providerId = _registerProvider(providerOwner, "url1"); @@ -474,6 +492,18 @@ contract SwarmRegistryL1Test is Test { swarmRegistry.checkMembership(swarmId, keccak256("test2")); } + function test_checkMembership_tinyFilter_returnsFalse() public { + uint256 fleetId = _registerFleet(fleetOwner, "f1"); + uint256 providerId = _registerProvider(providerOwner, "u1"); + + // 1-byte filter with 16-bit fingerprint: m = (1*8)/16 = 0, returns false immediately + bytes memory filter = new bytes(1); + uint256 swarmId = _registerSwarm(fleetOwner, fleetId, providerId, filter, 16, SwarmRegistryL1.TagType.GENERIC); + + // Should return false (not revert) because m == 0 + assertFalse(swarmRegistry.checkMembership(swarmId, keccak256("test")), "m=0 should return false"); + } + // ============================== // Multiple swarms per fleet // ============================== diff --git a/test/SwarmRegistryUniversal.t.sol b/test/SwarmRegistryUniversal.t.sol index f1eb9612..860fd536 100644 --- a/test/SwarmRegistryUniversal.t.sol +++ b/test/SwarmRegistryUniversal.t.sol @@ -275,6 +275,24 @@ contract SwarmRegistryUniversalTest is Test { // registerSwarm — reverts // ============================== + function test_RevertIf_registerSwarm_zeroUuid() public { + uint256 providerId = _registerProvider(providerOwner, "url1"); + + vm.prank(fleetOwner); + vm.expectRevert(SwarmRegistryUniversal.InvalidUuid.selector); + swarmRegistry.registerSwarm(bytes16(0), providerId, new bytes(32), 8, SwarmRegistryUniversal.TagType.GENERIC); + } + + function test_RevertIf_registerSwarm_providerDoesNotExist() public { + uint256 fleetId = _registerFleet(fleetOwner, "f1"); + uint256 nonExistentProvider = 12345; + + vm.prank(fleetOwner); + // ERC721.ownerOf reverts for non-existent tokens before our ProviderDoesNotExist check + vm.expectRevert(); + swarmRegistry.registerSwarm(_getFleetUuid(fleetId), nonExistentProvider, new bytes(32), 8, SwarmRegistryUniversal.TagType.GENERIC); + } + function test_RevertIf_registerSwarm_notFleetOwner() public { uint256 fleetId = _registerFleet(fleetOwner, "my-fleet"); From 4099ddf3cd1ae3e9c4f583a7c9f750c8686eb1a8 Mon Sep 17 00:00:00 2001 From: Alex Sedighi Date: Tue, 3 Mar 2026 11:25:48 +1300 Subject: [PATCH 03/15] ci: restrict coverage check to src/swarms contracts only --- .github/workflows/checks.yml | 24 ++++++++++++++++++------ 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/.github/workflows/checks.yml b/.github/workflows/checks.yml index 500f15ff..cff85beb 100644 --- a/.github/workflows/checks.yml +++ b/.github/workflows/checks.yml @@ -53,7 +53,7 @@ jobs: run: yarn - name: Run coverage - run: forge coverage --ir-minimum --report lcov --report-file coverage.lcov + run: forge coverage --match-path "test/{Swarm*,ServiceProvider,FleetIdentity}*.t.sol" --ir-minimum --report lcov --report-file coverage.lcov - name: Upload coverage report uses: actions/upload-artifact@v4 @@ -64,17 +64,29 @@ jobs: - name: Check line coverage threshold run: | - # Extract line coverage from lcov report - LINES_FOUND=$(grep -E "^LF:" coverage.lcov | awk -F: '{sum += $2} END {print sum}') - LINES_HIT=$(grep -E "^LH:" coverage.lcov | awk -F: '{sum += $2} END {print sum}') + # Extract line coverage from lcov report for src/swarms/ contracts only + # Parse lcov format: find swarm file sections and sum their LF/LH values + LINES_FOUND=$(awk ' + /^SF:.*src\/swarms\// { in_swarm = 1 } + /^end_of_record/ { in_swarm = 0 } + in_swarm && /^LF:/ { sum += substr($0, 4) } + END { print sum+0 } + ' coverage.lcov) + + LINES_HIT=$(awk ' + /^SF:.*src\/swarms\// { in_swarm = 1 } + /^end_of_record/ { in_swarm = 0 } + in_swarm && /^LH:/ { sum += substr($0, 4) } + END { print sum+0 } + ' coverage.lcov) if [ "$LINES_FOUND" -eq 0 ]; then - echo "Error: No lines found in coverage report" + echo "Error: No lines found in coverage report for src/swarms/" exit 1 fi COVERAGE=$(awk "BEGIN {printf \"%.2f\", ($LINES_HIT / $LINES_FOUND) * 100}") - echo "Line coverage: $COVERAGE% ($LINES_HIT / $LINES_FOUND lines)" + echo "Swarms line coverage: $COVERAGE% ($LINES_HIT / $LINES_FOUND lines)" # Check if coverage is below 95% THRESHOLD=95 From a194c9ecdc26b710c8d7cd9830db051a26e71ff0 Mon Sep 17 00:00:00 2001 From: Alex Sedighi Date: Tue, 3 Mar 2026 12:02:36 +1300 Subject: [PATCH 04/15] refactor: update SwarmRegistry swarm ID design to exclude providerId - Modify computeSwarmId to use (fleetUuid, filterData, fingerprintSize, tagType) instead of (fleetUuid, providerId, filterData) - This ensures swarm identity is based on immutable fleet/filter properties, not mutable provider relationship - Remove updateSwarmFilter() function since changing filter changes swarm identity - Update provider validation to use try/catch pattern making ProviderDoesNotExist error reachable - Add test_checkMembership_tinyFilter_returnsFalse for m==0 edge case - Coverage: SwarmRegistryL1 97.01%, SwarmRegistryUniversal 98.55% (123 tests passing) --- coverage-test.lcov | 3309 ++++++++++++++++++++++++ coverage.lcov | 3337 +++++++++++++++++++++++++ coverage2.lcov | 3337 +++++++++++++++++++++++++ coverage3.lcov | 3337 +++++++++++++++++++++++++ lcov.info | 3301 ++++++++++++++++++++++++ src/swarms/SwarmRegistryL1.sol | 38 +- src/swarms/SwarmRegistryUniversal.sol | 45 +- test/SwarmRegistryL1.t.sol | 167 +- test/SwarmRegistryUniversal.t.sol | 184 +- 9 files changed, 16771 insertions(+), 284 deletions(-) create mode 100644 coverage-test.lcov create mode 100644 coverage.lcov create mode 100644 coverage2.lcov create mode 100644 coverage3.lcov create mode 100644 lcov.info diff --git a/coverage-test.lcov b/coverage-test.lcov new file mode 100644 index 00000000..bb618d7d --- /dev/null +++ b/coverage-test.lcov @@ -0,0 +1,3309 @@ +TN: +SF:script/CheckBridge.s.sol +DA:13,0 +FN:13,CheckBridge.setUp +FNDA:0,CheckBridge.setUp +DA:14,0 +DA:15,0 +DA:18,0 +FN:18,CheckBridge.run +FNDA:0,CheckBridge.run +DA:19,0 +DA:20,0 +DA:22,0 +DA:23,0 +DA:25,0 +BRDA:25,0,0,- +BRDA:25,0,1,- +DA:26,0 +DA:28,0 +DA:30,0 +BRDA:30,1,0,- +BRDA:30,1,1,- +DA:31,0 +DA:32,0 +BRDA:32,2,0,- +BRDA:32,2,1,- +DA:33,0 +DA:35,0 +DA:38,0 +FNF:2 +FNH:0 +LF:17 +LH:0 +BRF:6 +BRH:0 +end_of_record +TN: +SF:script/ContentSignWhitelist.s.sol +DA:13,0 +FN:13,ContentSignWhitelist.setUp +FNDA:0,ContentSignWhitelist.setUp +DA:14,0 +DA:15,0 +DA:18,0 +FN:18,ContentSignWhitelist.run +FNDA:0,ContentSignWhitelist.run +DA:19,0 +DA:21,0 +BRDA:21,0,0,- +BRDA:21,0,1,- +DA:22,0 +DA:24,0 +DA:26,0 +DA:27,0 +DA:28,0 +FNF:2 +FNH:0 +LF:11 +LH:0 +BRF:2 +BRH:0 +end_of_record +TN: +SF:script/DeployClick.s.sol +DA:14,0 +FN:14,DeployClick.setUp +FNDA:0,DeployClick.setUp +DA:15,0 +DA:16,0 +DA:18,0 +DA:19,0 +DA:22,0 +FN:22,DeployClick.run +FNDA:0,DeployClick.run +DA:23,0 +DA:25,0 +DA:26,0 +DA:28,0 +DA:29,0 +DA:32,0 +DA:35,0 +DA:37,0 +DA:39,0 +DA:40,0 +DA:41,0 +FNF:2 +FNH:0 +LF:17 +LH:0 +BRF:0 +BRH:0 +end_of_record +TN: +SF:script/DeployContentSignEnterprise.s.sol +DA:15,0 +FN:15,DeployContentSignEnterprise.setUp +FNDA:0,DeployContentSignEnterprise.setUp +DA:16,0 +DA:17,0 +DA:18,0 +DA:21,0 +FN:21,DeployContentSignEnterprise.run +FNDA:0,DeployContentSignEnterprise.run +DA:22,0 +DA:24,0 +DA:26,0 +DA:28,0 +DA:29,0 +DA:30,0 +DA:31,0 +FNF:2 +FNH:0 +LF:12 +LH:0 +BRF:0 +BRH:0 +end_of_record +TN: +SF:script/DeployL1Bridge.s.sol +DA:22,0 +FN:22,DeployL1Bridge.setUp +FNDA:0,DeployL1Bridge.setUp +DA:23,0 +DA:24,0 +DA:25,0 +DA:26,0 +DA:28,0 +DA:29,0 +DA:30,0 +DA:31,0 +DA:34,0 +FN:34,DeployL1Bridge.run +FNDA:0,DeployL1Bridge.run +DA:35,0 +DA:37,0 +DA:39,0 +DA:40,0 +DA:41,0 +DA:43,0 +DA:45,0 +DA:46,0 +FNF:2 +FNH:0 +LF:18 +LH:0 +BRF:0 +BRH:0 +end_of_record +TN: +SF:script/DeployL1Ens.s.sol +DA:18,0 +FN:18,DeployL1Ens.run +FNDA:0,DeployL1Ens.run +DA:19,0 +DA:20,0 +DA:22,0 +DA:24,0 +BRDA:24,0,0,- +BRDA:24,0,1,- +DA:25,0 +DA:26,0 +BRDA:26,1,0,- +BRDA:26,1,1,- +DA:27,0 +DA:28,0 +DA:29,0 +DA:30,0 +DA:32,0 +DA:35,0 +DA:36,0 +DA:39,0 +DA:40,0 +DA:42,0 +DA:45,0 +DA:47,0 +BRDA:47,2,0,- +DA:48,0 +DA:49,0 +DA:55,0 +DA:56,0 +DA:59,0 +DA:60,0 +DA:62,0 +DA:63,0 +DA:65,0 +DA:66,0 +DA:68,0 +FNF:1 +FNH:0 +LF:30 +LH:0 +BRF:5 +BRH:0 +end_of_record +TN: +SF:script/DeployL1Nodl.s.sol +DA:17,0 +FN:17,DeployL1Nodl.setUp +FNDA:0,DeployL1Nodl.setUp +DA:18,0 +DA:19,0 +DA:21,0 +DA:22,0 +DA:25,0 +FN:25,DeployL1Nodl.run +FNDA:0,DeployL1Nodl.run +DA:26,0 +DA:28,0 +DA:30,0 +DA:32,0 +FNF:2 +FNH:0 +LF:10 +LH:0 +BRF:0 +BRH:0 +end_of_record +TN: +SF:script/DeployL2Bridge.s.sol +DA:19,0 +FN:19,DeployL2Bridge.setUp +FNDA:0,DeployL2Bridge.setUp +DA:20,0 +DA:21,0 +DA:23,0 +DA:24,0 +DA:27,0 +FN:27,DeployL2Bridge.run +FNDA:0,DeployL2Bridge.run +DA:28,0 +DA:30,0 +DA:32,0 +DA:33,0 +DA:34,0 +DA:36,0 +DA:38,0 +DA:39,0 +FNF:2 +FNH:0 +LF:14 +LH:0 +BRF:0 +BRH:0 +end_of_record +TN: +SF:script/DeployL2Nodl.s.sol +DA:17,0 +FN:17,DeployL2Nodl.setUp +FNDA:0,DeployL2Nodl.setUp +DA:18,0 +DA:19,0 +DA:21,0 +DA:22,0 +DA:25,0 +FN:25,DeployL2Nodl.run +FNDA:0,DeployL2Nodl.run +DA:26,0 +DA:28,0 +DA:29,0 +DA:30,0 +DA:32,0 +DA:34,0 +FNF:2 +FNH:0 +LF:12 +LH:0 +BRF:0 +BRH:0 +end_of_record +TN: +SF:script/DeployMigrationNFT.s.sol +DA:18,0 +FN:18,DeployMigrationNFT.setUp +FNDA:0,DeployMigrationNFT.setUp +DA:19,0 +DA:20,0 +DA:22,0 +DA:23,0 +DA:24,0 +DA:25,0 +DA:27,0 +DA:28,0 +DA:32,0 +FN:32,DeployMigrationNFT.run +FNDA:0,DeployMigrationNFT.run +DA:33,0 +DA:35,0 +DA:37,0 +DA:39,0 +FNF:2 +FNH:0 +LF:14 +LH:0 +BRF:0 +BRH:0 +end_of_record +TN: +SF:script/DeployNodlMigration.sol +DA:14,0 +FN:14,DeployNodlMigration.setUp +FNDA:0,DeployNodlMigration.setUp +DA:15,0 +DA:16,0 +DA:18,0 +DA:19,0 +DA:20,0 +DA:23,0 +FN:23,DeployNodlMigration.run +FNDA:0,DeployNodlMigration.run +DA:24,0 +DA:26,0 +DA:27,0 +DA:29,0 +DA:30,0 +DA:32,0 +DA:34,0 +DA:36,0 +DA:37,0 +FNF:2 +FNH:0 +LF:16 +LH:0 +BRF:0 +BRH:0 +end_of_record +TN: +SF:script/DeployRewards.sol +DA:18,0 +FN:18,DeployRewards.setUp +FNDA:0,DeployRewards.setUp +DA:19,0 +DA:20,0 +DA:21,0 +DA:22,0 +DA:23,0 +DA:24,0 +DA:27,0 +FN:27,DeployRewards.run +FNDA:0,DeployRewards.run +DA:28,0 +DA:29,0 +BRDA:29,0,0,- +DA:30,0 +DA:31,0 +DA:32,0 +DA:35,0 +DA:36,0 +DA:39,0 +DA:40,0 +DA:42,0 +DA:44,0 +FNF:2 +FNH:0 +LF:19 +LH:0 +BRF:1 +BRH:0 +end_of_record +TN: +SF:script/RewardsSig.s.sol +DA:15,0 +FN:15,RewardsSig.setUp +FNDA:0,RewardsSig.setUp +DA:16,0 +DA:17,0 +DA:18,0 +DA:19,0 +DA:22,0 +FN:22,RewardsSig.run +FNDA:0,RewardsSig.run +DA:23,0 +DA:25,0 +DA:26,0 +DA:27,0 +DA:29,0 +DA:31,0 +DA:33,0 +DA:34,0 +DA:36,0 +DA:37,0 +DA:39,0 +DA:40,0 +DA:42,0 +DA:43,0 +FNF:2 +FNH:0 +LF:20 +LH:0 +BRF:0 +BRH:0 +end_of_record +TN: +SF:src/Grants.sol +DA:56,0 +FN:56,Grants.constructor +FNDA:0,Grants.constructor +DA:57,0 +BRDA:57,0,0,- +DA:58,0 +DA:61,0 +DA:62,0 +DA:63,0 +DA:75,0 +FN:75,Grants.addVestingSchedule +FNDA:0,Grants.addVestingSchedule +DA:83,0 +DA:85,0 +DA:87,0 +DA:88,0 +BRDA:88,1,0,- +DA:89,0 +DA:90,0 +DA:92,0 +DA:94,0 +DA:96,0 +DA:106,0 +FN:106,Grants.validateVestingSchedule +FNDA:0,Grants.validateVestingSchedule +DA:110,0 +DA:111,0 +DA:112,0 +DA:113,0 +DA:114,0 +DA:123,0 +FN:123,Grants.claim +FNDA:0,Grants.claim +DA:124,0 +DA:125,0 +DA:127,0 +DA:129,0 +DA:130,0 +DA:131,0 +DA:132,0 +DA:133,0 +DA:134,0 +BRDA:134,2,0,- +DA:135,0 +DA:136,0 +DA:137,0 +DA:138,0 +DA:139,0 +DA:140,0 +DA:141,0 +DA:142,0 +BRDA:142,3,0,- +DA:143,0 +DA:144,0 +DA:145,0 +DA:148,0 +DA:152,0 +BRDA:152,4,0,- +BRDA:152,4,1,- +DA:153,0 +DA:154,0 +DA:156,0 +DA:167,0 +FN:167,Grants.renounce +FNDA:0,Grants.renounce +DA:168,0 +DA:170,0 +DA:171,0 +DA:172,0 +DA:173,0 +DA:174,0 +BRDA:174,5,0,- +DA:175,0 +DA:176,0 +DA:181,0 +BRDA:181,6,0,- +BRDA:181,6,1,- +DA:182,0 +DA:184,0 +DA:195,0 +FN:195,Grants.cancelVestingSchedules +FNDA:0,Grants.cancelVestingSchedules +DA:196,0 +DA:197,0 +DA:198,0 +DA:200,0 +DA:201,0 +DA:202,0 +DA:203,0 +DA:204,0 +DA:205,0 +DA:206,0 +BRDA:206,7,0,- +DA:207,0 +DA:208,0 +DA:209,0 +DA:210,0 +DA:211,0 +DA:212,0 +DA:213,0 +DA:214,0 +DA:215,0 +DA:216,0 +DA:217,0 +DA:219,0 +DA:223,0 +BRDA:223,8,0,- +DA:224,0 +DA:227,0 +BRDA:227,9,0,- +DA:228,0 +DA:231,0 +BRDA:231,10,0,- +DA:232,0 +DA:235,0 +DA:243,0 +FN:243,Grants.getGrantsCount +FNDA:0,Grants.getGrantsCount +DA:244,0 +DA:245,0 +DA:246,0 +DA:248,0 +DA:253,0 +FN:253,Grants._mustBeNonZero +FNDA:0,Grants._mustBeNonZero +DA:254,0 +BRDA:254,11,0,- +DA:255,0 +DA:259,0 +FN:259,Grants._mustBeNonZeroAddress +FNDA:0,Grants._mustBeNonZeroAddress +DA:260,0 +BRDA:260,12,0,- +DA:261,0 +DA:265,0 +FN:265,Grants._mustNotBeSelf +FNDA:0,Grants._mustNotBeSelf +DA:266,0 +BRDA:266,13,0,- +DA:267,0 +DA:271,0 +FN:271,Grants._mustBeEqualOrExceedMinAmount +FNDA:0,Grants._mustBeEqualOrExceedMinAmount +DA:272,0 +BRDA:272,14,0,- +DA:273,0 +DA:277,0 +FN:277,Grants._sanitizePageRange +FNDA:0,Grants._sanitizePageRange +DA:278,0 +DA:279,0 +BRDA:279,15,0,- +DA:280,0 +DA:282,0 +BRDA:282,16,0,- +DA:283,0 +DA:285,0 +FNF:12 +FNH:0 +LF:114 +LH:0 +BRF:19 +BRH:0 +end_of_record +TN: +SF:src/L1Nodl.sol +DA:18,0 +FN:18,L1Nodl.constructor +FNDA:0,L1Nodl.constructor +DA:19,0 +BRDA:19,0,0,- +DA:20,0 +DA:22,0 +DA:23,0 +DA:26,0 +FN:26,L1Nodl.mint +FNDA:0,L1Nodl.mint +DA:27,0 +DA:30,0 +FN:30,L1Nodl.clock +FNDA:0,L1Nodl.clock +DA:31,0 +DA:35,0 +FN:35,L1Nodl.CLOCK_MODE +FNDA:0,L1Nodl.CLOCK_MODE +DA:36,0 +DA:39,0 +FN:39,L1Nodl.nonces +FNDA:0,L1Nodl.nonces +DA:40,0 +DA:43,0 +FN:43,L1Nodl._update +FNDA:0,L1Nodl._update +DA:44,0 +FNF:6 +FNH:0 +LF:15 +LH:0 +BRF:1 +BRH:0 +end_of_record +TN: +SF:src/NODL.sol +DA:12,0 +FN:12,NODL.constructor +FNDA:0,NODL.constructor +DA:13,0 +DA:14,0 +DA:17,0 +FN:17,NODL.mint +FNDA:0,NODL.mint +DA:18,0 +DA:20,0 +FNF:2 +FNH:0 +LF:6 +LH:0 +BRF:0 +BRH:0 +end_of_record +TN: +SF:src/Payment.sol +DA:40,0 +FN:40,Payment.constructor +FNDA:0,Payment.constructor +DA:43,0 +DA:44,0 +DA:58,0 +FN:58,Payment.pay +FNDA:0,Payment.pay +DA:59,0 +DA:60,0 +DA:62,0 +BRDA:62,0,0,- +DA:63,0 +DA:66,0 +DA:67,0 +DA:69,0 +DA:70,0 +DA:83,0 +FN:83,Payment.withdraw +FNDA:0,Payment.withdraw +DA:84,0 +FNF:3 +FNH:0 +LF:14 +LH:0 +BRF:1 +BRH:0 +end_of_record +TN: +SF:src/QuotaControl.sol +DA:84,0 +FN:84,QuotaControl.constructor +FNDA:0,QuotaControl.constructor +DA:85,0 +DA:86,0 +DA:88,0 +DA:89,0 +DA:90,0 +DA:97,0 +FN:97,QuotaControl.setQuota +FNDA:0,QuotaControl.setQuota +DA:98,0 +DA:99,0 +DA:109,0 +FN:109,QuotaControl.setPeriod +FNDA:0,QuotaControl.setPeriod +DA:110,0 +DA:111,0 +DA:112,0 +DA:121,0 +FN:121,QuotaControl._checkedResetClaimed +FNDA:0,QuotaControl._checkedResetClaimed +DA:122,0 +BRDA:122,0,0,- +DA:123,0 +DA:126,0 +DA:127,0 +DA:140,0 +FN:140,QuotaControl._checkedUpdateClaimed +FNDA:0,QuotaControl._checkedUpdateClaimed +DA:141,0 +DA:142,0 +BRDA:142,1,0,- +DA:143,0 +DA:145,0 +DA:156,0 +FN:156,QuotaControl._mustBeWithinPeriodRange +FNDA:0,QuotaControl._mustBeWithinPeriodRange +DA:157,0 +BRDA:157,2,0,- +DA:158,0 +DA:160,0 +BRDA:160,3,0,- +DA:161,0 +FNF:6 +FNH:0 +LF:28 +LH:0 +BRF:4 +BRH:0 +end_of_record +TN: +SF:src/Rewards.sol +DA:137,0 +FN:137,Rewards.constructor +FNDA:0,Rewards.constructor +DA:145,0 +DA:147,0 +DA:148,0 +DA:149,0 +DA:157,0 +FN:157,Rewards.mintReward +FNDA:0,Rewards.mintReward +DA:158,0 +DA:159,0 +DA:161,0 +DA:162,0 +DA:165,0 +DA:166,0 +DA:168,0 +DA:176,0 +FN:176,Rewards.mintBatchReward +FNDA:0,Rewards.mintBatchReward +DA:177,0 +DA:178,0 +DA:180,0 +DA:182,0 +DA:184,0 +DA:186,0 +DA:187,0 +DA:189,0 +DA:192,0 +DA:194,0 +DA:196,0 +DA:197,0 +DA:199,0 +DA:201,0 +DA:208,0 +FN:208,Rewards.setBatchSubmitterRewardBasisPoints +FNDA:0,Rewards.setBatchSubmitterRewardBasisPoints +DA:209,0 +DA:210,0 +DA:211,0 +DA:213,0 +DA:220,0 +FN:220,Rewards._mustBeLessThanBasisPointsDivisor +FNDA:0,Rewards._mustBeLessThanBasisPointsDivisor +DA:221,0 +BRDA:221,0,0,- +DA:222,0 +DA:231,0 +FN:231,Rewards._mustBeExpectedSequence +FNDA:0,Rewards._mustBeExpectedSequence +DA:232,0 +BRDA:232,1,0,- +DA:233,0 +DA:241,0 +FN:241,Rewards._mustBeExpectedBatchSequence +FNDA:0,Rewards._mustBeExpectedBatchSequence +DA:242,0 +BRDA:242,2,0,- +DA:243,0 +DA:251,0 +FN:251,Rewards._mustBeValidBatchStructure +FNDA:0,Rewards._mustBeValidBatchStructure +DA:252,0 +BRDA:252,3,0,- +DA:253,0 +DA:263,0 +FN:263,Rewards._mustBeFromAuthorizedOracle +FNDA:0,Rewards._mustBeFromAuthorizedOracle +DA:264,0 +BRDA:264,4,0,- +DA:265,0 +DA:274,0 +FN:274,Rewards._batchSum +FNDA:0,Rewards._batchSum +DA:275,0 +DA:276,0 +DA:277,0 +DA:279,0 +DA:287,0 +FN:287,Rewards.digestReward +FNDA:0,Rewards.digestReward +DA:288,0 +DA:289,0 +DA:297,0 +FN:297,Rewards.digestBatchReward +FNDA:0,Rewards.digestBatchReward +DA:298,0 +DA:299,0 +DA:300,0 +DA:301,0 +DA:308,0 +FN:308,Rewards.latestBatchDetails +FNDA:0,Rewards.latestBatchDetails +DA:309,0 +FNF:13 +FNH:0 +LF:63 +LH:0 +BRF:5 +BRH:0 +end_of_record +TN: +SF:src/bridge/BridgeBase.sol +DA:70,0 +FN:70,BridgeBase.constructor +FNDA:0,BridgeBase.constructor +DA:71,0 +DA:72,0 +DA:73,0 +DA:75,0 +DA:76,0 +DA:79,0 +DA:80,0 +DA:81,0 +DA:90,0 +FN:90,BridgeBase._createVote +FNDA:0,BridgeBase._createVote +DA:91,0 +DA:93,0 +DA:99,0 +FN:99,BridgeBase._recordVote +FNDA:0,BridgeBase._recordVote +DA:100,0 +DA:102,0 +DA:104,0 +DA:108,0 +FN:108,BridgeBase._processVote +FNDA:0,BridgeBase._processVote +DA:109,0 +DA:110,0 +DA:111,0 +DA:116,0 +FN:116,BridgeBase._execute +FNDA:0,BridgeBase._execute +DA:117,0 +DA:118,0 +DA:119,0 +DA:121,0 +DA:164,0 +FN:164,BridgeBase._mustNotHaveExecutedYet +FNDA:0,BridgeBase._mustNotHaveExecutedYet +DA:165,0 +BRDA:165,0,0,- +DA:166,0 +DA:170,0 +FN:170,BridgeBase._mustBePastSafetyDelay +FNDA:0,BridgeBase._mustBePastSafetyDelay +DA:171,0 +BRDA:171,1,0,- +DA:172,0 +DA:176,0 +FN:176,BridgeBase._mustHaveEnoughVotes +FNDA:0,BridgeBase._mustHaveEnoughVotes +DA:177,0 +BRDA:177,2,0,- +DA:178,0 +DA:182,0 +FN:182,BridgeBase._mustHaveEnoughOracles +FNDA:0,BridgeBase._mustHaveEnoughOracles +DA:183,0 +BRDA:183,3,0,- +DA:184,0 +DA:188,0 +FN:188,BridgeBase._mustBeAnOracle +FNDA:0,BridgeBase._mustBeAnOracle +DA:189,0 +BRDA:189,4,0,- +DA:190,0 +DA:194,0 +FN:194,BridgeBase._mustNotExceedMaxOracles +FNDA:0,BridgeBase._mustNotExceedMaxOracles +DA:195,0 +BRDA:195,5,0,- +DA:196,0 +DA:200,0 +FN:200,BridgeBase._mustNotBeZeroMinVotes +FNDA:0,BridgeBase._mustNotBeZeroMinVotes +DA:201,0 +BRDA:201,6,0,- +DA:202,0 +DA:206,0 +FN:206,BridgeBase._mustNotHaveVotedYet +FNDA:0,BridgeBase._mustNotHaveVotedYet +DA:207,0 +BRDA:207,7,0,- +DA:208,0 +FNF:13 +FNH:0 +LF:49 +LH:0 +BRF:8 +BRH:0 +end_of_record +TN: +SF:src/bridge/GrantsMigration.sol +DA:57,0 +FN:57,GrantsMigration.constructor +FNDA:0,GrantsMigration.constructor +DA:60,0 +DA:70,0 +FN:70,GrantsMigration.bridge +FNDA:0,GrantsMigration.bridge +DA:73,0 +DA:74,0 +DA:76,0 +BRDA:76,0,0,- +BRDA:76,0,1,- +DA:77,0 +DA:78,0 +DA:80,0 +DA:88,0 +FN:88,GrantsMigration.grant +FNDA:0,GrantsMigration.grant +DA:89,0 +DA:90,0 +DA:91,0 +DA:92,0 +DA:93,0 +DA:94,0 +DA:103,0 +DA:108,0 +FN:108,GrantsMigration._mustNotBeChangingParameters +FNDA:0,GrantsMigration._mustNotBeChangingParameters +DA:114,0 +DA:116,0 +BRDA:116,1,0,- +DA:117,0 +DA:120,0 +DA:121,0 +BRDA:121,2,0,- +DA:122,0 +DA:125,0 +DA:127,0 +DA:128,0 +DA:129,0 +DA:130,0 +DA:131,0 +DA:132,0 +BRDA:132,3,0,- +DA:133,0 +DA:138,0 +FN:138,GrantsMigration._createProposal +FNDA:0,GrantsMigration._createProposal +DA:145,0 +BRDA:145,4,0,- +DA:146,0 +DA:148,0 +BRDA:148,5,0,- +DA:149,0 +DA:151,0 +DA:152,0 +DA:153,0 +DA:156,0 +DA:158,0 +BRDA:158,6,0,- +DA:159,0 +DA:161,0 +DA:162,0 +DA:165,0 +FN:165,GrantsMigration._proposalExists +FNDA:0,GrantsMigration._proposalExists +DA:166,0 +DA:169,0 +FN:169,GrantsMigration._flagAsExecuted +FNDA:0,GrantsMigration._flagAsExecuted +DA:170,0 +DA:173,0 +FN:173,GrantsMigration._incTotalVotes +FNDA:0,GrantsMigration._incTotalVotes +DA:174,0 +DA:177,0 +FN:177,GrantsMigration._updateLastVote +FNDA:0,GrantsMigration._updateLastVote +DA:178,0 +DA:181,0 +FN:181,GrantsMigration._totalVotes +FNDA:0,GrantsMigration._totalVotes +DA:182,0 +DA:185,0 +FN:185,GrantsMigration._lastVote +FNDA:0,GrantsMigration._lastVote +DA:186,0 +DA:189,0 +FN:189,GrantsMigration._executed +FNDA:0,GrantsMigration._executed +DA:190,0 +FNF:12 +FNH:0 +LF:59 +LH:0 +BRF:8 +BRH:0 +end_of_record +TN: +SF:src/bridge/L1Bridge.sol +DA:80,0 +FN:80,L1Bridge.constructor +FNDA:0,L1Bridge.constructor +DA:81,0 +BRDA:81,0,0,- +DA:82,0 +DA:84,0 +DA:85,0 +DA:86,0 +DA:94,0 +FN:94,L1Bridge.pause +FNDA:0,L1Bridge.pause +DA:95,0 +DA:99,0 +FN:99,L1Bridge.unpause +FNDA:0,L1Bridge.unpause +DA:100,0 +DA:115,0 +FN:115,L1Bridge.quoteL2BaseCost +FNDA:0,L1Bridge.quoteL2BaseCost +DA:120,0 +DA:130,0 +FN:130,L1Bridge.quoteL2BaseCostAtGasPrice +FNDA:0,L1Bridge.quoteL2BaseCostAtGasPrice +DA:135,0 +DA:152,0 +FN:152,L1Bridge.deposit +FNDA:0,L1Bridge.deposit +DA:159,0 +BRDA:159,1,0,- +DA:160,0 +DA:162,0 +BRDA:162,2,0,- +DA:163,0 +DA:166,0 +DA:168,0 +DA:169,0 +DA:171,0 +DA:175,0 +DA:177,0 +DA:183,0 +FN:183,L1Bridge.deposit +FNDA:0,L1Bridge.deposit +DA:189,0 +DA:202,0 +FN:202,L1Bridge.claimFailedDeposit +FNDA:0,L1Bridge.claimFailedDeposit +DA:210,0 +DA:211,0 +BRDA:211,3,0,- +DA:212,0 +DA:214,0 +DA:217,0 +BRDA:217,4,0,- +DA:218,0 +DA:220,0 +DA:221,0 +DA:222,0 +DA:234,0 +FN:234,L1Bridge.finalizeWithdrawal +FNDA:0,L1Bridge.finalizeWithdrawal +DA:241,0 +BRDA:241,5,0,- +DA:242,0 +DA:245,0 +DA:246,0 +DA:249,0 +DA:255,0 +BRDA:255,6,0,- +DA:256,0 +DA:259,0 +DA:261,0 +DA:262,0 +DA:276,0 +FN:276,L1Bridge._parseL2WithdrawalMessage +FNDA:0,L1Bridge._parseL2WithdrawalMessage +DA:282,0 +BRDA:282,7,0,- +DA:283,0 +DA:287,0 +DA:288,0 +BRDA:288,8,0,- +DA:289,0 +DA:291,0 +DA:292,0 +FNF:10 +FNH:0 +LF:56 +LH:0 +BRF:9 +BRH:0 +end_of_record +TN: +SF:src/bridge/L2Bridge.sol +DA:49,0 +FN:49,L2Bridge.onlyL1Bridge +FNDA:0,L2Bridge.onlyL1Bridge +DA:50,0 +BRDA:50,0,0,- +DA:51,0 +DA:65,0 +FN:65,L2Bridge.constructor +FNDA:0,L2Bridge.constructor +DA:66,0 +BRDA:66,1,0,- +DA:67,0 +DA:69,0 +DA:70,0 +DA:78,0 +FN:78,L2Bridge.pause +FNDA:0,L2Bridge.pause +DA:79,0 +DA:83,0 +FN:83,L2Bridge.unpause +FNDA:0,L2Bridge.unpause +DA:84,0 +DA:90,0 +FN:90,L2Bridge.initialize +FNDA:0,L2Bridge.initialize +DA:91,0 +BRDA:91,2,0,- +DA:92,0 +DA:94,0 +BRDA:94,3,0,- +DA:95,0 +DA:97,0 +DA:103,0 +FN:103,L2Bridge.finalizeDeposit +FNDA:0,L2Bridge.finalizeDeposit +DA:109,0 +BRDA:109,4,0,- +DA:110,0 +DA:112,0 +BRDA:112,5,0,- +DA:113,0 +DA:116,0 +DA:118,0 +DA:124,0 +FN:124,L2Bridge.withdraw +FNDA:0,L2Bridge.withdraw +DA:125,0 +BRDA:125,6,0,- +DA:126,0 +DA:128,0 +BRDA:128,7,0,- +DA:129,0 +DA:132,0 +DA:135,0 +DA:136,0 +DA:138,0 +FNF:7 +FNH:0 +LF:34 +LH:0 +BRF:8 +BRH:0 +end_of_record +TN: +SF:src/bridge/MigrationNFT.sol +DA:69,0 +FN:69,MigrationNFT.constructor +FNDA:0,MigrationNFT.constructor +DA:75,0 +BRDA:75,0,0,- +DA:76,0 +DA:78,0 +BRDA:78,1,0,- +DA:79,0 +DA:81,0 +BRDA:81,2,0,- +DA:82,0 +DA:85,0 +DA:86,0 +BRDA:86,3,0,- +DA:87,0 +DA:91,0 +DA:92,0 +DA:93,0 +DA:94,0 +DA:101,0 +FN:101,MigrationNFT.tokenURI +FNDA:0,MigrationNFT.tokenURI +DA:102,0 +DA:104,0 +DA:105,0 +DA:112,0 +FN:112,MigrationNFT.safeMint +FNDA:0,MigrationNFT.safeMint +DA:113,0 +DA:115,0 +DA:117,0 +DA:118,0 +DA:119,0 +DA:121,0 +DA:123,0 +DA:124,0 +BRDA:124,4,0,- +DA:125,0 +DA:128,0 +DA:129,0 +DA:130,0 +DA:131,0 +DA:132,0 +DA:136,0 +FN:136,MigrationNFT._computeLevelUps +FNDA:0,MigrationNFT._computeLevelUps +DA:141,0 +DA:142,0 +DA:147,0 +DA:148,0 +DA:149,0 +BRDA:149,5,0,- +DA:150,0 +DA:151,0 +DA:155,0 +BRDA:155,6,0,- +DA:156,0 +DA:160,0 +FN:160,MigrationNFT._mustNotHaveBeenClaimed +FNDA:0,MigrationNFT._mustNotHaveBeenClaimed +DA:161,0 +BRDA:161,7,0,- +DA:162,0 +DA:166,0 +FN:166,MigrationNFT._mustBeAnExistingProposal +FNDA:0,MigrationNFT._mustBeAnExistingProposal +DA:168,0 +BRDA:168,8,0,- +DA:169,0 +DA:173,0 +FN:173,MigrationNFT._mustBeExecuted +FNDA:0,MigrationNFT._mustBeExecuted +DA:174,0 +BRDA:174,9,0,- +DA:175,0 +DA:179,0 +FN:179,MigrationNFT._mustAlreadyBeHolderOrEnoughHoldersRemaining +FNDA:0,MigrationNFT._mustAlreadyBeHolderOrEnoughHoldersRemaining +DA:180,0 +DA:181,0 +BRDA:181,10,0,- +DA:182,0 +DA:186,0 +FN:186,MigrationNFT._update +FNDA:0,MigrationNFT._update +DA:187,0 +DA:188,0 +BRDA:188,11,0,- +DA:190,0 +DA:193,0 +FNF:9 +FNH:0 +LF:61 +LH:0 +BRF:12 +BRH:0 +end_of_record +TN: +SF:src/bridge/NODLMigration.sol +DA:35,0 +FN:35,NODLMigration.bridge +FNDA:0,NODLMigration.bridge +DA:36,0 +DA:37,0 +DA:39,0 +BRDA:39,0,0,- +BRDA:39,0,1,- +DA:40,0 +DA:41,0 +DA:43,0 +DA:50,0 +FN:50,NODLMigration.withdraw +FNDA:0,NODLMigration.withdraw +DA:51,0 +DA:52,0 +DA:55,0 +FN:55,NODLMigration._mustNotBeChangingParameters +FNDA:0,NODLMigration._mustNotBeChangingParameters +DA:56,0 +BRDA:56,1,0,- +DA:57,0 +DA:61,0 +FN:61,NODLMigration._proposalExists +FNDA:0,NODLMigration._proposalExists +DA:62,0 +DA:65,0 +FN:65,NODLMigration._createVote +FNDA:0,NODLMigration._createVote +DA:66,0 +DA:67,0 +DA:68,0 +DA:71,0 +FN:71,NODLMigration._withdraw +FNDA:0,NODLMigration._withdraw +DA:72,0 +DA:73,0 +DA:76,0 +FN:76,NODLMigration._flagAsExecuted +FNDA:0,NODLMigration._flagAsExecuted +DA:77,0 +DA:80,0 +FN:80,NODLMigration._incTotalVotes +FNDA:0,NODLMigration._incTotalVotes +DA:81,0 +DA:84,0 +FN:84,NODLMigration._updateLastVote +FNDA:0,NODLMigration._updateLastVote +DA:85,0 +DA:88,0 +FN:88,NODLMigration._totalVotes +FNDA:0,NODLMigration._totalVotes +DA:89,0 +DA:92,0 +FN:92,NODLMigration._lastVote +FNDA:0,NODLMigration._lastVote +DA:93,0 +DA:96,0 +FN:96,NODLMigration._executed +FNDA:0,NODLMigration._executed +DA:97,0 +FNF:12 +FNH:0 +LF:34 +LH:0 +BRF:3 +BRH:0 +end_of_record +TN: +SF:src/contentsign/BaseContentSign.sol +DA:17,0 +FN:17,BaseContentSign.safeMint +FNDA:0,BaseContentSign.safeMint +DA:18,0 +DA:20,0 +DA:21,0 +DA:22,0 +DA:25,0 +FN:25,BaseContentSign.tokenURI +FNDA:0,BaseContentSign.tokenURI +DA:26,0 +DA:29,0 +FN:29,BaseContentSign.supportsInterface +FNDA:0,BaseContentSign.supportsInterface +DA:36,0 +DA:39,0 +FN:39,BaseContentSign._mustBeWhitelisted +FNDA:0,BaseContentSign._mustBeWhitelisted +DA:40,0 +BRDA:40,0,0,- +DA:41,0 +FNF:4 +FNH:0 +LF:12 +LH:0 +BRF:1 +BRH:0 +end_of_record +TN: +SF:src/contentsign/ClickBounty.sol +DA:158,0 +FN:158,ClickBounty.constructor +FNDA:0,ClickBounty.constructor +DA:159,0 +DA:160,0 +DA:162,0 +DA:163,0 +DA:164,0 +DA:181,0 +FN:181,ClickBounty.payEntryFee +FNDA:0,ClickBounty.payEntryFee +DA:182,0 +DA:184,0 +BRDA:184,0,0,- +DA:185,0 +DA:188,0 +DA:189,0 +BRDA:189,1,0,- +DA:190,0 +DA:193,0 +DA:194,0 +DA:209,0 +FN:209,ClickBounty.setEntryFee +FNDA:0,ClickBounty.setEntryFee +DA:210,0 +DA:211,0 +DA:212,0 +DA:224,0 +FN:224,ClickBounty.withdraw +FNDA:0,ClickBounty.withdraw +DA:225,0 +DA:226,0 +DA:254,0 +FN:254,ClickBounty.awardBounty +FNDA:0,ClickBounty.awardBounty +DA:255,0 +DA:257,0 +BRDA:257,2,0,- +DA:258,0 +DA:260,0 +DA:262,0 +BRDA:262,3,0,- +DA:263,0 +DA:265,0 +BRDA:265,4,0,- +DA:266,0 +DA:269,0 +DA:272,0 +DA:275,0 +DA:277,0 +DA:280,0 +DA:294,0 +FN:294,ClickBounty.getLeaderboard +FNDA:0,ClickBounty.getLeaderboard +DA:295,0 +DA:296,0 +DA:297,0 +DA:298,0 +DA:317,0 +FN:317,ClickBounty._updateLeaderboard +FNDA:0,ClickBounty._updateLeaderboard +DA:319,0 +BRDA:319,5,0,- +DA:320,0 +DA:321,0 +DA:322,0 +DA:326,0 +DA:327,0 +DA:328,0 +DA:329,0 +DA:330,0 +BRDA:330,6,0,- +DA:331,0 +DA:332,0 +DA:337,0 +BRDA:337,7,0,- +DA:338,0 +DA:339,0 +FNF:7 +FNH:0 +LF:56 +LH:0 +BRF:8 +BRH:0 +end_of_record +TN: +SF:src/contentsign/ClickContentSign.sol +DA:12,0 +FN:12,ClickContentSign.constructor +FNDA:0,ClickContentSign.constructor +DA:13,0 +DA:16,0 +FN:16,ClickContentSign._userIsWhitelisted +FNDA:0,ClickContentSign._userIsWhitelisted +DA:17,0 +FNF:2 +FNH:0 +LF:4 +LH:0 +BRF:0 +BRH:0 +end_of_record +TN: +SF:src/contentsign/EnterpriseContentSign.sol +DA:13,0 +FN:13,EnterpriseContentSign.constructor +FNDA:0,EnterpriseContentSign.constructor +DA:14,0 +DA:17,0 +FN:17,EnterpriseContentSign.supportsInterface +FNDA:0,EnterpriseContentSign.supportsInterface +DA:23,0 +DA:26,0 +FN:26,EnterpriseContentSign._userIsWhitelisted +FNDA:0,EnterpriseContentSign._userIsWhitelisted +DA:27,0 +FNF:3 +FNH:0 +LF:6 +LH:0 +BRF:0 +BRH:0 +end_of_record +TN: +SF:src/contentsign/PaymentMiddleware.sol +DA:21,0 +FN:21,PaymentMiddleware.constructor +FNDA:0,PaymentMiddleware.constructor +DA:24,0 +DA:25,0 +DA:26,0 +DA:27,0 +DA:30,0 +FN:30,PaymentMiddleware.safeMint +FNDA:0,PaymentMiddleware.safeMint +DA:32,0 +BRDA:32,0,0,- +DA:33,0 +DA:37,0 +DA:40,0 +DA:43,0 +FN:43,PaymentMiddleware.withdraw +FNDA:0,PaymentMiddleware.withdraw +DA:44,0 +DA:46,0 +DA:47,0 +DA:50,0 +FN:50,PaymentMiddleware.setFeeAmount +FNDA:0,PaymentMiddleware.setFeeAmount +DA:51,0 +DA:53,0 +DA:56,0 +FN:56,PaymentMiddleware.setTarget +FNDA:0,PaymentMiddleware.setTarget +DA:57,0 +DA:59,0 +DA:62,0 +FN:62,PaymentMiddleware.setWhitelist +FNDA:0,PaymentMiddleware.setWhitelist +DA:63,0 +DA:65,0 +DA:68,0 +FN:68,PaymentMiddleware.setFeeToken +FNDA:0,PaymentMiddleware.setFeeToken +DA:69,0 +DA:71,0 +FNF:7 +FNH:0 +LF:26 +LH:0 +BRF:1 +BRH:0 +end_of_record +TN: +SF:src/nameservice/ClickNameService.sol +DA:62,0 +FN:62,ClickNameService.constructor +FNDA:0,ClickNameService.constructor +DA:63,0 +DA:64,0 +DA:68,0 +FN:68,ClickNameService.resolve +FNDA:0,ClickNameService.resolve +DA:69,0 +DA:70,0 +DA:71,0 +BRDA:71,0,0,- +DA:72,0 +DA:74,0 +DA:80,0 +FN:80,ClickNameService.batchRegister +FNDA:0,ClickNameService.batchRegister +DA:81,0 +BRDA:81,1,0,- +DA:82,0 +DA:85,0 +DA:86,0 +DA:94,0 +FN:94,ClickNameService.setDefaultExpiry +FNDA:0,ClickNameService.setDefaultExpiry +DA:95,0 +DA:99,0 +FN:99,ClickNameService.register +FNDA:0,ClickNameService.register +DA:100,0 +DA:104,0 +FN:104,ClickNameService.registerWithExpiry +FNDA:0,ClickNameService.registerWithExpiry +DA:105,0 +BRDA:105,2,0,- +DA:106,0 +DA:109,0 +DA:110,0 +DA:111,0 +DA:112,0 +DA:115,0 +FN:115,ClickNameService._register +FNDA:0,ClickNameService._register +DA:116,0 +BRDA:116,3,0,- +DA:117,0 +DA:119,0 +BRDA:119,4,0,- +DA:120,0 +DA:123,0 +DA:124,0 +DA:125,0 +BRDA:125,5,0,- +BRDA:125,5,1,- +DA:126,0 +DA:128,0 +BRDA:128,6,0,- +DA:129,0 +DA:131,0 +DA:134,0 +DA:140,0 +FN:140,ClickNameService.supportsInterface +FNDA:0,ClickNameService.supportsInterface +DA:141,0 +DA:142,0 +DA:149,0 +FN:149,ClickNameService.burn +FNDA:0,ClickNameService.burn +DA:150,0 +DA:151,0 +DA:158,0 +FN:158,ClickNameService.removeExpired +FNDA:0,ClickNameService.removeExpired +DA:159,0 +DA:160,0 +BRDA:160,7,0,- +DA:161,0 +DA:163,0 +DA:164,0 +DA:171,0 +FN:171,ClickNameService.extendExpiry +FNDA:0,ClickNameService.extendExpiry +DA:172,0 +BRDA:172,8,0,- +DA:173,0 +DA:175,0 +BRDA:175,9,0,- +DA:176,0 +DA:179,0 +BRDA:179,10,0,- +BRDA:179,10,1,- +DA:180,0 +DA:182,0 +DA:187,0 +FN:187,ClickNameService._isAlphanumeric +FNDA:0,ClickNameService._isAlphanumeric +DA:188,0 +DA:189,0 +DA:190,0 +DA:191,0 +DA:193,0 +DA:197,0 +FN:197,ClickNameService._isAuthorized +FNDA:0,ClickNameService._isAuthorized +DA:198,0 +FNF:13 +FNH:0 +LF:66 +LH:0 +BRF:13 +BRH:0 +end_of_record +TN: +SF:src/nameservice/NameService.sol +DA:75,0 +FN:75,NameService.constructor +FNDA:0,NameService.constructor +DA:78,0 +DA:79,0 +DA:83,0 +FN:83,NameService.resolve +FNDA:0,NameService.resolve +DA:84,0 +DA:85,0 +DA:86,0 +BRDA:86,0,0,- +DA:87,0 +DA:89,0 +DA:95,0 +FN:95,NameService.batchRegister +FNDA:0,NameService.batchRegister +DA:96,0 +BRDA:96,1,0,- +DA:97,0 +DA:100,0 +DA:101,0 +DA:109,0 +FN:109,NameService.setDefaultExpiry +FNDA:0,NameService.setDefaultExpiry +DA:110,0 +DA:114,0 +FN:114,NameService.register +FNDA:0,NameService.register +DA:115,0 +DA:119,0 +FN:119,NameService.registerWithExpiry +FNDA:0,NameService.registerWithExpiry +DA:120,0 +BRDA:120,2,0,- +DA:121,0 +DA:124,0 +DA:125,0 +DA:126,0 +DA:127,0 +DA:130,0 +FN:130,NameService._register +FNDA:0,NameService._register +DA:131,0 +BRDA:131,3,0,- +DA:132,0 +DA:134,0 +BRDA:134,4,0,- +DA:135,0 +DA:138,0 +DA:139,0 +DA:140,0 +BRDA:140,5,0,- +BRDA:140,5,1,- +DA:141,0 +DA:143,0 +BRDA:143,6,0,- +DA:144,0 +DA:146,0 +DA:149,0 +DA:155,0 +FN:155,NameService.supportsInterface +FNDA:0,NameService.supportsInterface +DA:156,0 +DA:157,0 +DA:164,0 +FN:164,NameService.burn +FNDA:0,NameService.burn +DA:165,0 +DA:167,0 +DA:168,0 +DA:169,0 +DA:171,0 +DA:173,0 +DA:174,0 +DA:181,0 +FN:181,NameService.removeExpired +FNDA:0,NameService.removeExpired +DA:182,0 +DA:183,0 +BRDA:183,7,0,- +DA:184,0 +DA:186,0 +DA:187,0 +DA:194,0 +FN:194,NameService.extendExpiry +FNDA:0,NameService.extendExpiry +DA:195,0 +BRDA:195,8,0,- +DA:196,0 +DA:198,0 +BRDA:198,9,0,- +DA:199,0 +DA:202,0 +BRDA:202,10,0,- +BRDA:202,10,1,- +DA:203,0 +DA:205,0 +DA:210,0 +FN:210,NameService._isAlphanumeric +FNDA:0,NameService._isAlphanumeric +DA:211,0 +DA:212,0 +DA:213,0 +DA:214,0 +DA:216,0 +DA:220,0 +FN:220,NameService._isAuthorized +FNDA:0,NameService._isAuthorized +DA:221,0 +DA:228,0 +FN:228,NameService.setTextRecord +FNDA:0,NameService.setTextRecord +DA:229,0 +DA:231,0 +DA:232,0 +DA:233,0 +DA:235,0 +BRDA:235,12,0,- +DA:236,0 +DA:238,0 +BRDA:238,13,0,- +DA:239,0 +DA:241,0 +DA:242,0 +DA:249,0 +FN:249,NameService.getTextRecord +FNDA:0,NameService.getTextRecord +DA:250,0 +DA:251,0 +BRDA:251,14,0,- +DA:252,0 +DA:254,0 +FNF:15 +FNH:0 +LF:87 +LH:0 +BRF:16 +BRH:0 +end_of_record +TN: +SF:src/nameservice/PaymasterTest.sol +DA:23,0 +FN:23,PaymasterTest.register +FNDA:0,PaymasterTest.register +DA:24,0 +BRDA:24,0,0,- +DA:25,0 +DA:27,0 +BRDA:27,1,0,- +DA:28,0 +DA:31,0 +DA:32,0 +DA:33,0 +BRDA:33,2,0,- +BRDA:33,2,1,- +DA:34,0 +DA:36,0 +DA:39,0 +DA:43,0 +FN:43,PaymasterTest._isAlphanumeric +FNDA:0,PaymasterTest._isAlphanumeric +DA:44,0 +DA:45,0 +DA:46,0 +DA:47,0 +DA:49,0 +FNF:2 +FNH:0 +LF:17 +LH:0 +BRF:4 +BRH:0 +end_of_record +TN: +SF:src/nameservice/UniversalResolver.sol +DA:53,0 +FN:53,UniversalResolver.constructor +FNDA:0,UniversalResolver.constructor +DA:56,0 +DA:57,0 +DA:58,0 +DA:59,0 +DA:62,0 +DA:63,0 +DA:66,0 +FN:66,UniversalResolver.setUrl +FNDA:0,UniversalResolver.setUrl +DA:67,0 +DA:81,0 +FN:81,UniversalResolver._parseDnsDomain +FNDA:0,UniversalResolver._parseDnsDomain +DA:86,0 +DA:88,0 +DA:89,0 +DA:93,0 +DA:95,0 +DA:96,0 +DA:100,0 +DA:102,0 +DA:103,0 +DA:105,0 +DA:110,0 +FN:110,UniversalResolver.getStorageKey +FNDA:0,UniversalResolver.getStorageKey +DA:111,0 +DA:112,0 +DA:119,0 +FN:119,UniversalResolver.getTextRecordStorageKey +FNDA:0,UniversalResolver.getTextRecordStorageKey +DA:120,0 +DA:121,0 +DA:122,0 +DA:130,0 +FN:130,UniversalResolver.resolve +FNDA:0,UniversalResolver.resolve +DA:131,0 +DA:133,0 +BRDA:133,2,0,- +DA:134,0 +DA:137,0 +DA:138,0 +DA:140,0 +BRDA:140,3,0,- +BRDA:140,3,1,- +DA:141,0 +DA:142,0 +DA:143,0 +BRDA:143,4,0,- +BRDA:143,4,1,- +DA:144,0 +DA:145,0 +BRDA:145,5,0,- +DA:146,0 +DA:147,0 +BRDA:147,6,0,- +DA:148,0 +DA:152,0 +DA:155,0 +DA:156,0 +DA:158,0 +DA:159,0 +DA:161,0 +DA:167,0 +FN:167,UniversalResolver.resolveWithProof +FNDA:0,UniversalResolver.resolveWithProof +DA:168,0 +DA:169,0 +DA:172,0 +DA:174,0 +DA:176,0 +DA:178,0 +BRDA:178,7,0,- +DA:179,0 +DA:182,0 +BRDA:182,8,0,- +BRDA:182,8,1,- +DA:183,0 +DA:184,0 +BRDA:184,9,0,- +BRDA:184,9,1,- +DA:185,0 +DA:187,0 +DA:194,0 +FN:194,UniversalResolver.supportsInterface +FNDA:0,UniversalResolver.supportsInterface +DA:195,0 +DA:196,0 +FNF:8 +FNH:0 +LF:64 +LH:0 +BRF:12 +BRH:0 +end_of_record +TN: +SF:src/paymasters/BasePaymaster.sol +DA:33,0 +FN:33,BasePaymaster.constructor +FNDA:0,BasePaymaster.constructor +DA:34,0 +DA:35,0 +DA:38,0 +FN:38,BasePaymaster.validateAndPayForPaymasterTransaction +FNDA:0,BasePaymaster.validateAndPayForPaymasterTransaction +DA:43,0 +DA:46,0 +DA:48,0 +BRDA:48,0,0,- +DA:49,0 +DA:52,0 +DA:56,0 +DA:57,0 +DA:58,0 +DA:60,0 +BRDA:60,1,0,- +BRDA:60,1,1,- +DA:61,0 +DA:62,0 +BRDA:62,2,0,- +BRDA:62,2,1,- +DA:63,0 +DA:64,0 +DA:66,0 +DA:68,0 +DA:72,0 +DA:73,0 +BRDA:73,3,0,- +DA:74,0 +DA:77,0 +DA:80,0 +FN:80,BasePaymaster.postTransaction +FNDA:0,BasePaymaster.postTransaction +DA:88,0 +DA:93,0 +FN:93,BasePaymaster.withdraw +FNDA:0,BasePaymaster.withdraw +DA:94,0 +DA:96,0 +DA:97,0 +BRDA:97,4,0,- +DA:99,0 +DA:104,0 +FN:104,BasePaymaster._mustBeBootloader +FNDA:0,BasePaymaster._mustBeBootloader +DA:105,0 +BRDA:105,5,0,- +DA:106,0 +FNF:5 +FNH:0 +LF:33 +LH:0 +BRF:8 +BRH:0 +end_of_record +TN: +SF:src/paymasters/WhitelistPaymaster.sol +DA:22,0 +FN:22,WhitelistPaymaster.constructor +FNDA:0,WhitelistPaymaster.constructor +DA:23,0 +DA:26,0 +FN:26,WhitelistPaymaster.addWhitelistedContracts +FNDA:0,WhitelistPaymaster.addWhitelistedContracts +DA:27,0 +DA:29,0 +DA:30,0 +DA:33,0 +DA:36,0 +FN:36,WhitelistPaymaster.removeWhitelistedContracts +FNDA:0,WhitelistPaymaster.removeWhitelistedContracts +DA:37,0 +DA:39,0 +DA:40,0 +DA:43,0 +DA:46,0 +FN:46,WhitelistPaymaster.addWhitelistedUsers +FNDA:0,WhitelistPaymaster.addWhitelistedUsers +DA:47,0 +DA:49,0 +DA:50,0 +DA:53,0 +DA:56,0 +FN:56,WhitelistPaymaster.removeWhitelistedUsers +FNDA:0,WhitelistPaymaster.removeWhitelistedUsers +DA:57,0 +DA:59,0 +DA:60,0 +DA:63,0 +DA:66,0 +FN:66,WhitelistPaymaster._validateAndPayGeneralFlow +FNDA:0,WhitelistPaymaster._validateAndPayGeneralFlow +DA:67,0 +BRDA:67,0,0,- +DA:68,0 +DA:71,0 +BRDA:71,1,0,- +DA:72,0 +DA:76,0 +FN:76,WhitelistPaymaster._validateAndPayApprovalBasedFlow +FNDA:0,WhitelistPaymaster._validateAndPayApprovalBasedFlow +DA:81,0 +FNF:7 +FNH:0 +LF:29 +LH:0 +BRF:2 +BRH:0 +end_of_record +TN: +SF:src/swarms/FleetIdentity.sol +DA:227,872 +FN:227,FleetIdentity.constructor +FNDA:872,FleetIdentity.constructor +DA:228,872 +DA:229,872 +DA:243,12006 +FN:243,FleetIdentity.registerFleetCountry +FNDA:12006,FleetIdentity.registerFleetCountry +DA:248,12006 +BRDA:248,0,0,- +DA:249,12006 +BRDA:249,1,0,2 +DA:250,12004 +DA:251,12004 +DA:252,12004 +DA:267,14439 +FN:267,FleetIdentity.registerFleetLocal +FNDA:14439,FleetIdentity.registerFleetLocal +DA:272,14439 +BRDA:272,2,0,- +DA:273,14439 +BRDA:273,3,0,1 +DA:274,14438 +BRDA:274,4,0,2 +DA:275,14436 +DA:276,14436 +DA:277,14436 +DA:286,265 +FN:286,FleetIdentity.promote +FNDA:265,FleetIdentity.promote +DA:287,265 +DA:294,38 +FN:294,FleetIdentity.reassignTier +FNDA:38,FleetIdentity.reassignTier +DA:295,38 +DA:296,38 +BRDA:296,5,0,1 +DA:297,37 +BRDA:297,6,0,28 +BRDA:297,6,1,9 +DA:298,28 +DA:300,9 +DA:316,282 +FN:316,FleetIdentity.setOperator +FNDA:282,FleetIdentity.setOperator +DA:318,282 +BRDA:318,7,0,2 +DA:321,280 +BRDA:321,8,0,- +DA:322,0 +DA:325,280 +DA:328,280 +DA:329,280 +DA:332,280 +DA:335,280 +DA:338,280 +BRDA:338,9,0,278 +DA:340,278 +DA:342,278 +DA:345,1 +DA:367,905 +FN:367,FleetIdentity.burn +FNDA:905,FleetIdentity.burn +DA:368,905 +DA:370,905 +DA:371,905 +DA:372,905 +DA:373,905 +DA:374,905 +DA:376,905 +BRDA:376,10,0,27 +BRDA:376,10,1,574 +DA:378,27 +BRDA:378,11,0,2 +DA:380,25 +DA:381,25 +DA:382,25 +DA:384,25 +DA:387,878 +BRDA:387,12,0,259 +DA:388,259 +DA:391,619 +DA:392,619 +DA:395,619 +DA:397,619 +DA:398,619 +DA:400,45 +BRDA:400,13,0,45 +BRDA:400,13,1,574 +DA:402,45 +DA:403,45 +DA:404,45 +DA:408,574 +DA:412,619 +DA:414,619 +DA:427,24 +FN:427,FleetIdentity.claimUuid +FNDA:24,FleetIdentity.claimUuid +DA:428,24 +BRDA:428,14,0,1 +DA:429,23 +BRDA:429,15,0,2 +DA:432,21 +DA:433,21 +DA:434,21 +DA:436,21 +DA:439,21 +DA:440,21 +DA:442,21 +DA:444,21 +DA:454,29620 +FN:454,FleetIdentity.tierBond +FNDA:29620,FleetIdentity.tierBond +DA:455,29620 +DA:456,29620 +DA:462,9 +FN:462,FleetIdentity.localInclusionHint +FNDA:9,FleetIdentity.localInclusionHint +DA:467,9 +BRDA:467,16,0,- +DA:468,9 +BRDA:468,17,0,- +DA:469,9 +DA:470,7 +DA:478,8 +FN:478,FleetIdentity.countryInclusionHint +FNDA:8,FleetIdentity.countryInclusionHint +DA:479,8 +BRDA:479,18,0,- +DA:482,8 +DA:485,7 +DA:486,7 +DA:487,7 +DA:488,5 +DA:489,5 +DA:490,5 +BRDA:490,19,0,- +DA:492,7 +DA:496,4 +FN:496,FleetIdentity.highestActiveTier +FNDA:4,FleetIdentity.highestActiveTier +DA:497,4 +DA:498,4 +DA:499,1 +DA:503,35 +FN:503,FleetIdentity.tierMemberCount +FNDA:35,FleetIdentity.tierMemberCount +DA:504,35 +DA:508,2236 +FN:508,FleetIdentity.getTierMembers +FNDA:2236,FleetIdentity.getTierMembers +DA:509,2236 +DA:513,2 +FN:513,FleetIdentity.getTierUuids +FNDA:2,FleetIdentity.getTierUuids +DA:514,2 +DA:515,2 +DA:516,2 +DA:517,2 +DA:522,14479 +FN:522,FleetIdentity.tokenUuid +FNDA:14479,FleetIdentity.tokenUuid +DA:523,14479 +DA:527,57639 +FN:527,FleetIdentity.tokenRegion +FNDA:57639,FleetIdentity.tokenRegion +DA:528,57639 +DA:532,25976 +FN:532,FleetIdentity.computeTokenId +FNDA:25976,FleetIdentity.computeTokenId +DA:533,25976 +DA:537,800 +FN:537,FleetIdentity.bonds +FNDA:800,FleetIdentity.bonds +DA:538,800 +DA:539,798 +DA:540,798 +DA:541,797 +DA:545,16 +FN:545,FleetIdentity.isOwnedOnly +FNDA:16,FleetIdentity.isOwnedOnly +DA:546,16 +DA:554,4262 +FN:554,FleetIdentity.operatorOf +FNDA:4262,FleetIdentity.operatorOf +DA:555,4262 +DA:556,4262 +BRDA:556,23,0,4230 +DA:557,4230 +DA:582,1343 +FN:582,FleetIdentity.buildHighestBondedUuidBundle +FNDA:1343,FleetIdentity.buildHighestBondedUuidBundle +DA:587,1343 +BRDA:587,24,0,- +DA:588,1343 +BRDA:588,25,0,1 +DA:590,1342 +DA:591,1342 +DA:593,1342 +DA:610,8 +FN:610,FleetIdentity.buildCountryOnlyBundle +FNDA:8,FleetIdentity.buildCountryOnlyBundle +DA:615,8 +BRDA:615,26,0,2 +DA:617,6 +DA:619,6 +DA:621,6 +DA:633,1370 +FN:633,FleetIdentity._buildHighestBondedUuidBundle +FNDA:1370,FleetIdentity._buildHighestBondedUuidBundle +DA:638,1370 +DA:640,1370 +DA:643,1370 +DA:644,3017 +DA:647,3017 +DA:650,3017 +DA:655,0 +DA:662,6034 +FN:662,FleetIdentity._appendTierUuids +FNDA:6034,FleetIdentity._appendTierUuids +DA:668,6034 +DA:669,6034 +DA:670,6034 +DA:671,6034 +DA:673,6034 +DA:674,13262 +DA:675,13262 +DA:677,0 +DA:685,5 +FN:685,FleetIdentity.getActiveCountries +FNDA:5,FleetIdentity.getActiveCountries +DA:686,5 +DA:691,5 +FN:691,FleetIdentity.getActiveAdminAreas +FNDA:5,FleetIdentity.getActiveAdminAreas +DA:693,5 +DA:694,5 +DA:695,5 +DA:696,5 +DA:700,5 +DA:701,5 +DA:702,5 +DA:703,5 +DA:704,5 +DA:705,5 +DA:706,6 +DA:709,0 +DA:714,0 +FN:714,FleetIdentity.getCountryAdminAreas +FNDA:0,FleetIdentity.getCountryAdminAreas +DA:715,0 +DA:720,15838 +FN:720,FleetIdentity.makeAdminRegion +FNDA:15838,FleetIdentity.makeAdminRegion +DA:721,15838 +DA:731,4484 +FN:731,FleetIdentity._countryFromRegion +FNDA:4484,FleetIdentity._countryFromRegion +DA:732,4484 +DA:736,5 +FN:736,FleetIdentity._adminFromRegion +FNDA:5,FleetIdentity._adminFromRegion +DA:737,5 +DA:742,54428 +FN:742,FleetIdentity._isCountryRegion +FNDA:54428,FleetIdentity._isCountryRegion +DA:743,54428 +DA:749,26249 +FN:749,FleetIdentity._pullBond +FNDA:26249,FleetIdentity._pullBond +DA:750,26249 +BRDA:750,27,0,26248 +DA:751,26248 +DA:756,929 +FN:756,FleetIdentity._refundBond +FNDA:929,FleetIdentity._refundBond +DA:757,929 +BRDA:757,28,0,928 +DA:758,928 +DA:765,25 +FN:765,FleetIdentity._clearUuidOwnership +FNDA:25,FleetIdentity._clearUuidOwnership +DA:766,25 +DA:767,25 +DA:768,25 +DA:769,25 +DA:770,25 +DA:775,0 +FN:775,FleetIdentity._decrementUuidCount +FNDA:0,FleetIdentity._decrementUuidCount +DA:776,0 +DA:777,0 +BRDA:777,29,0,- +BRDA:777,29,1,- +DA:778,0 +DA:780,0 +DA:788,619 +FN:788,FleetIdentity._cleanupFleetFromTier +FNDA:619,FleetIdentity._cleanupFleetFromTier +DA:789,619 +DA:790,619 +DA:791,619 +DA:792,619 +DA:793,619 +DA:800,23703 +FN:800,FleetIdentity._mintFleetToken +FNDA:23703,FleetIdentity._mintFleetToken +DA:801,23703 +DA:802,23703 +DA:803,23703 +DA:804,23703 +DA:805,23703 +DA:810,2216 +FN:810,FleetIdentity._mintFleetTokenTo +FNDA:2216,FleetIdentity._mintFleetTokenTo +DA:811,2216 +DA:812,2216 +DA:813,2216 +DA:814,2216 +DA:815,2216 +DA:824,26439 +FN:824,FleetIdentity._register +FNDA:26439,FleetIdentity._register +DA:825,26439 +DA:826,26439 +DA:827,26439 +DA:828,12004 +DA:830,26439 +BRDA:830,30,0,10 +BRDA:830,30,1,2 +DA:832,10 +DA:833,10 +BRDA:833,31,0,- +DA:834,10 +DA:836,10 +DA:837,10 +DA:838,10 +DA:840,10 +DA:843,10 +DA:845,10 +DA:846,26429 +BRDA:846,32,0,23703 +BRDA:846,32,1,2 +DA:848,23703 +DA:849,23703 +DA:850,23703 +DA:851,23703 +DA:854,23703 +DA:857,23703 +DA:859,23703 +DA:862,2726 +DA:863,2726 +BRDA:863,33,0,518 +DA:864,2208 +BRDA:864,34,0,2 +DA:865,2206 +DA:867,2206 +DA:868,2206 +DA:870,2206 +DA:873,2206 +DA:875,2206 +DA:880,293 +FN:880,FleetIdentity._promote +FNDA:293,FleetIdentity._promote +DA:881,293 +DA:882,293 +DA:883,293 +BRDA:883,35,0,260 +DA:885,33 +DA:886,33 +DA:887,33 +BRDA:887,36,0,- +DA:888,33 +BRDA:888,37,0,1 +DA:889,32 +BRDA:889,38,0,1 +DA:891,31 +DA:892,31 +DA:893,31 +DA:894,31 +DA:897,31 +DA:898,31 +DA:899,31 +DA:900,31 +DA:903,31 +DA:905,31 +DA:909,9 +FN:909,FleetIdentity._demote +FNDA:9,FleetIdentity._demote +DA:910,9 +DA:911,9 +DA:912,9 +BRDA:912,39,0,1 +DA:914,8 +DA:915,8 +DA:916,8 +BRDA:916,40,0,- +DA:917,8 +BRDA:917,41,0,1 +DA:919,7 +DA:920,7 +DA:921,7 +DA:922,7 +DA:925,7 +DA:926,7 +DA:927,7 +DA:928,7 +DA:929,7 +DA:932,7 +DA:934,7 +DA:938,26440 +FN:938,FleetIdentity._validateExplicitTier +FNDA:26440,FleetIdentity._validateExplicitTier +DA:939,26440 +BRDA:939,42,0,- +DA:940,26440 +BRDA:940,43,0,1 +DA:946,1370 +FN:946,FleetIdentity._findMaxTierIndex +FNDA:1370,FleetIdentity._findMaxTierIndex +DA:951,1370 +DA:952,1370 +DA:954,1370 +DA:955,1370 +BRDA:955,44,0,378 +DA:956,1370 +DA:976,22 +FN:976,FleetIdentity._findCheapestInclusionTier +FNDA:22,FleetIdentity._findCheapestInclusionTier +DA:981,22 +DA:982,22 +DA:983,22 +DA:985,22 +DA:990,22 +DA:991,26 +DA:992,26 +DA:994,26 +BRDA:994,45,0,11 +DA:995,11 +DA:1000,15 +DA:1001,15 +DA:1002,15 +DA:1003,15 +DA:1007,11 +BRDA:1007,46,0,8 +DA:1008,8 +DA:1011,3 +DA:1016,25957 +FN:1016,FleetIdentity._addToTier +FNDA:25957,FleetIdentity._addToTier +DA:1017,25957 +DA:1018,25957 +DA:1021,25957 +BRDA:1021,47,0,11167 +DA:1022,11167 +DA:1027,657 +FN:1027,FleetIdentity._removeFromTier +FNDA:657,FleetIdentity._removeFromTier +DA:1028,657 +DA:1029,657 +DA:1030,657 +DA:1032,657 +BRDA:1032,48,0,3 +DA:1033,3 +DA:1034,3 +DA:1035,3 +DA:1037,657 +DA:1041,626 +FN:1041,FleetIdentity._trimTierCount +FNDA:626,FleetIdentity._trimTierCount +DA:1042,626 +DA:1043,1280 +DA:1044,654 +DA:1046,626 +DA:1052,25919 +FN:1052,FleetIdentity._addToRegionIndex +FNDA:25919,FleetIdentity._addToRegionIndex +DA:1053,25919 +BRDA:1053,49,0,11745 +BRDA:1053,49,1,3505 +DA:1055,11745 +DA:1056,11745 +BRDA:1056,50,0,4077 +DA:1057,4077 +DA:1058,4077 +DA:1062,14174 +BRDA:1062,51,0,4447 +DA:1063,4447 +DA:1065,4447 +BRDA:1065,52,0,3505 +DA:1066,3505 +DA:1067,3505 +DA:1069,4447 +DA:1070,4447 +DA:1076,619 +FN:1076,FleetIdentity._removeFromRegionIndex +FNDA:619,FleetIdentity._removeFromRegionIndex +DA:1077,619 +DA:1079,616 +BRDA:1079,54,0,579 +BRDA:1079,54,1,5 +DA:1080,579 +DA:1081,579 +DA:1082,579 +BRDA:1082,55,0,579 +DA:1084,579 +DA:1086,576 +DA:1087,576 +DA:1088,576 +BRDA:1088,57,0,416 +DA:1089,416 +DA:1090,416 +DA:1091,416 +DA:1093,576 +DA:1094,576 +DA:1098,37 +DA:1099,37 +BRDA:1099,58,0,37 +DA:1100,37 +DA:1101,37 +DA:1102,37 +DA:1103,37 +DA:1104,37 +BRDA:1104,59,0,1 +DA:1105,1 +DA:1106,1 +DA:1107,1 +DA:1109,37 +DA:1110,37 +DA:1113,37 +BRDA:1113,60,0,36 +DA:1114,36 +DA:1115,36 +BRDA:1115,61,0,36 +DA:1116,36 +DA:1117,36 +DA:1118,36 +BRDA:1118,62,0,5 +DA:1119,5 +DA:1120,5 +DA:1121,5 +DA:1123,36 +DA:1124,36 +DA:1135,26646 +FN:1135,FleetIdentity._update +FNDA:26646,FleetIdentity._update +DA:1136,26646 +DA:1140,26646 +DA:1141,26646 +BRDA:1141,63,0,3 +DA:1142,3 +DA:1145,26646 +DA:1148,0 +FN:1148,FleetIdentity._increaseBalance +FNDA:0,FleetIdentity._increaseBalance +DA:1149,0 +DA:1152,3 +FN:1152,FleetIdentity.supportsInterface +FNDA:3,FleetIdentity.supportsInterface +DA:1153,3 +FNF:53 +FNH:50 +LF:387 +LH:374 +BRF:67 +BRH:53 +end_of_record +TN: +SF:src/swarms/ServiceProvider.sol +DA:27,1935 +FN:27,ServiceProvider.registerProvider +FNDA:1935,ServiceProvider.registerProvider +DA:28,1935 +BRDA:28,0,0,1 +DA:29,1 +DA:32,1934 +DA:34,1934 +DA:36,1934 +DA:38,1934 +DA:43,276 +FN:43,ServiceProvider.burn +FNDA:276,ServiceProvider.burn +DA:44,276 +BRDA:44,1,0,258 +DA:45,258 +DA:48,18 +DA:50,18 +DA:52,18 +FNF:2 +FNH:2 +LF:13 +LH:13 +BRF:2 +BRH:2 +end_of_record +TN: +SF:src/swarms/SwarmRegistryL1.sol +DA:83,313 +FN:83,SwarmRegistryL1.computeSwarmId +FNDA:313,SwarmRegistryL1.computeSwarmId +DA:88,313 +DA:91,63 +FN:91,SwarmRegistryL1.constructor +FNDA:63,SwarmRegistryL1.constructor +DA:92,63 +BRDA:92,0,0,3 +DA:93,3 +DA:95,60 +DA:96,60 +DA:106,573 +FN:106,SwarmRegistryL1.registerSwarm +FNDA:573,SwarmRegistryL1.registerSwarm +DA:113,573 +BRDA:113,1,0,1 +DA:114,1 +DA:116,572 +BRDA:116,2,0,258 +DA:117,258 +DA:119,314 +BRDA:119,3,0,2 +DA:120,2 +DA:124,312 +BRDA:124,4,0,1 +DA:125,1 +DA:127,311 +BRDA:127,5,0,311 +DA:128,1 +BRDA:128,5,1,1 +DA:129,1 +DA:132,310 +DA:134,310 +BRDA:134,6,0,1 +DA:135,1 +DA:138,309 +DA:139,309 +DA:140,309 +DA:141,309 +DA:142,309 +DA:143,309 +DA:145,309 +DA:146,309 +DA:148,309 +DA:150,309 +DA:155,7 +FN:155,SwarmRegistryL1.acceptSwarm +FNDA:7,SwarmRegistryL1.acceptSwarm +DA:156,7 +DA:157,7 +BRDA:157,7,0,1 +DA:159,6 +DA:160,6 +BRDA:160,8,0,2 +DA:162,4 +BRDA:162,9,0,1 +DA:163,1 +DA:165,3 +DA:166,3 +DA:171,4 +FN:171,SwarmRegistryL1.rejectSwarm +FNDA:4,SwarmRegistryL1.rejectSwarm +DA:172,4 +DA:173,4 +BRDA:173,10,0,- +DA:175,4 +DA:176,4 +BRDA:176,11,0,1 +DA:178,3 +BRDA:178,12,0,1 +DA:179,1 +DA:181,2 +DA:182,2 +DA:188,5 +FN:188,SwarmRegistryL1.updateSwarmProvider +FNDA:5,SwarmRegistryL1.updateSwarmProvider +DA:189,5 +DA:190,5 +BRDA:190,13,0,1 +DA:191,1 +DA:193,4 +BRDA:193,14,0,1 +DA:194,1 +DA:196,3 +BRDA:196,15,0,3 +DA:197,1 +BRDA:197,15,1,1 +DA:198,1 +DA:201,2 +DA:203,2 +DA:205,0 +DA:207,0 +DA:212,7 +FN:212,SwarmRegistryL1.deleteSwarm +FNDA:7,SwarmRegistryL1.deleteSwarm +DA:213,7 +DA:214,7 +BRDA:214,16,0,1 +DA:215,1 +DA:217,6 +BRDA:217,17,0,1 +DA:218,1 +DA:221,5 +DA:223,5 +DA:225,5 +DA:227,5 +DA:234,26 +FN:234,SwarmRegistryL1.isSwarmValid +FNDA:26,SwarmRegistryL1.isSwarmValid +DA:235,26 +DA:236,26 +BRDA:236,18,0,1 +DA:239,25 +DA:241,25 +BRDA:241,19,0,25 +DA:242,18 +DA:243,7 +BRDA:243,19,1,7 +DA:244,7 +DA:250,6 +FN:250,SwarmRegistryL1.purgeOrphanedSwarm +FNDA:6,SwarmRegistryL1.purgeOrphanedSwarm +DA:251,6 +DA:252,6 +BRDA:252,20,0,1 +DA:254,5 +DA:255,5 +BRDA:255,21,0,1 +DA:257,4 +DA:259,4 +DA:261,4 +DA:263,4 +DA:270,7 +FN:270,SwarmRegistryL1.checkMembership +FNDA:7,SwarmRegistryL1.checkMembership +DA:271,7 +DA:272,7 +BRDA:272,22,0,1 +DA:273,1 +DA:277,6 +DA:278,6 +BRDA:278,23,0,1 +DA:280,5 +DA:281,5 +DA:283,0 +DA:287,5 +BRDA:287,24,0,5 +DA:289,5 +DA:294,5 +DA:295,5 +DA:297,0 +DA:299,4 +DA:300,4 +DA:301,4 +DA:303,4 +DA:304,4 +DA:306,4 +DA:307,4 +DA:308,4 +DA:310,4 +DA:316,9 +FN:316,SwarmRegistryL1._removeFromUuidSwarms +FNDA:9,SwarmRegistryL1._removeFromUuidSwarms +DA:317,9 +DA:318,9 +DA:319,9 +DA:321,9 +DA:322,9 +DA:323,9 +DA:324,9 +DA:333,12 +FN:333,SwarmRegistryL1._readFingerprint +FNDA:12,SwarmRegistryL1._readFingerprint +DA:334,12 +DA:335,12 +DA:336,12 +DA:339,12 +DA:342,12 +DA:343,12 +DA:344,24 +DA:346,24 +DA:350,12 +DA:351,12 +DA:352,12 +DA:354,12 +FNF:12 +FNH:12 +LF:134 +LH:130 +BRF:28 +BRH:27 +end_of_record +TN: +SF:src/swarms/SwarmRegistryUniversal.sol +DA:89,577 +FN:89,SwarmRegistryUniversal.computeSwarmId +FNDA:577,SwarmRegistryUniversal.computeSwarmId +DA:94,577 +DA:97,72 +FN:97,SwarmRegistryUniversal.constructor +FNDA:72,SwarmRegistryUniversal.constructor +DA:98,72 +BRDA:98,0,0,3 +DA:99,3 +DA:101,69 +DA:102,69 +DA:112,837 +FN:112,SwarmRegistryUniversal.registerSwarm +FNDA:837,SwarmRegistryUniversal.registerSwarm +DA:119,837 +BRDA:119,1,0,1 +DA:120,1 +DA:122,836 +BRDA:122,2,0,258 +DA:123,258 +DA:125,578 +BRDA:125,3,0,1 +DA:126,1 +DA:128,577 +BRDA:128,4,0,1 +DA:129,1 +DA:133,576 +BRDA:133,5,0,1 +DA:134,1 +DA:136,575 +BRDA:136,6,0,575 +DA:137,1 +BRDA:137,6,1,1 +DA:138,1 +DA:141,574 +DA:143,574 +BRDA:143,7,0,1 +DA:144,1 +DA:147,573 +DA:148,573 +DA:149,573 +DA:150,573 +DA:151,573 +DA:152,573 +DA:153,573 +DA:155,573 +DA:157,573 +DA:158,573 +DA:160,573 +DA:165,9 +FN:165,SwarmRegistryUniversal.acceptSwarm +FNDA:9,SwarmRegistryUniversal.acceptSwarm +DA:166,9 +DA:167,9 +BRDA:167,8,0,1 +DA:169,8 +DA:170,8 +BRDA:170,9,0,2 +DA:172,6 +BRDA:172,10,0,2 +DA:173,2 +DA:175,4 +DA:176,4 +DA:181,5 +FN:181,SwarmRegistryUniversal.rejectSwarm +FNDA:5,SwarmRegistryUniversal.rejectSwarm +DA:182,5 +DA:183,5 +BRDA:183,11,0,- +DA:185,5 +DA:186,5 +BRDA:186,12,0,1 +DA:188,4 +BRDA:188,13,0,1 +DA:189,1 +DA:191,3 +DA:192,3 +DA:198,5 +FN:198,SwarmRegistryUniversal.updateSwarmProvider +FNDA:5,SwarmRegistryUniversal.updateSwarmProvider +DA:199,5 +DA:200,5 +BRDA:200,14,0,1 +DA:201,1 +DA:203,4 +BRDA:203,15,0,1 +DA:204,1 +DA:206,3 +BRDA:206,16,0,3 +DA:207,1 +BRDA:207,16,1,1 +DA:208,1 +DA:211,2 +DA:214,2 +DA:215,0 +DA:217,0 +DA:222,8 +FN:222,SwarmRegistryUniversal.deleteSwarm +FNDA:8,SwarmRegistryUniversal.deleteSwarm +DA:223,8 +DA:224,8 +BRDA:224,17,0,1 +DA:225,1 +DA:227,7 +BRDA:227,18,0,1 +DA:228,1 +DA:231,6 +DA:233,6 +DA:235,6 +DA:236,6 +DA:238,6 +DA:245,30 +FN:245,SwarmRegistryUniversal.isSwarmValid +FNDA:30,SwarmRegistryUniversal.isSwarmValid +DA:246,30 +DA:247,30 +BRDA:247,19,0,1 +DA:250,29 +DA:252,29 +BRDA:252,20,0,29 +DA:253,21 +DA:254,8 +BRDA:254,20,1,8 +DA:255,8 +DA:261,7 +FN:261,SwarmRegistryUniversal.purgeOrphanedSwarm +FNDA:7,SwarmRegistryUniversal.purgeOrphanedSwarm +DA:262,7 +DA:263,7 +BRDA:263,21,0,1 +DA:265,6 +DA:266,6 +BRDA:266,22,0,1 +DA:268,5 +DA:270,5 +DA:272,5 +DA:273,5 +DA:275,5 +DA:282,7 +FN:282,SwarmRegistryUniversal.checkMembership +FNDA:7,SwarmRegistryUniversal.checkMembership +DA:283,7 +DA:284,7 +BRDA:284,23,0,1 +DA:285,1 +DA:289,6 +DA:290,6 +BRDA:290,24,0,1 +DA:292,5 +DA:293,5 +DA:296,5 +DA:297,5 +DA:300,4 +DA:301,4 +DA:302,4 +DA:304,4 +DA:305,4 +DA:308,4 +DA:309,4 +DA:310,4 +DA:312,4 +DA:318,3 +FN:318,SwarmRegistryUniversal.getFilterData +FNDA:3,SwarmRegistryUniversal.getFilterData +DA:319,3 +BRDA:319,26,0,1 +DA:320,1 +DA:322,2 +DA:328,11 +FN:328,SwarmRegistryUniversal._removeFromUuidSwarms +FNDA:11,SwarmRegistryUniversal._removeFromUuidSwarms +DA:329,11 +DA:330,11 +DA:331,11 +DA:333,11 +DA:334,11 +DA:335,11 +DA:336,11 +DA:345,12 +FN:345,SwarmRegistryUniversal._readFingerprint +FNDA:12,SwarmRegistryUniversal._readFingerprint +DA:346,12 +DA:347,12 +DA:348,12 +DA:351,12 +DA:352,12 +DA:353,24 +DA:355,24 +DA:360,12 +DA:361,12 +DA:362,12 +DA:364,12 +FNF:13 +FNH:13 +LF:138 +LH:136 +BRF:29 +BRH:28 +end_of_record +TN: +SF:test/FleetIdentity.t.sol +DA:12,648 +FN:12,MockERC20.mint +FNDA:648,MockERC20.mint +DA:13,648 +DA:23,1 +FN:23,BadERC20.mint +FNDA:1,BadERC20.mint +DA:24,1 +DA:27,1 +FN:27,BadERC20.setFail +FNDA:1,BadERC20.setFail +DA:28,1 +DA:31,0 +FN:31,BadERC20.transfer +FNDA:0,BadERC20.transfer +DA:33,0 +DA:36,1 +FN:36,BadERC20.transferFrom +FNDA:1,BadERC20.transferFrom +DA:38,0 +FNF:5 +FNH:4 +LF:10 +LH:7 +BRF:0 +BRH:0 +end_of_record +TN: +SF:test/FleetIdentityFairness.t.sol +DA:12,403 +FN:12,MockERC20Fairness.mint +FNDA:403,MockERC20Fairness.mint +DA:13,403 +FNF:1 +FNH:1 +LF:2 +LH:2 +BRF:0 +BRH:0 +end_of_record +TN: +SF:test/Grants.t.sol +DA:9,0 +FN:9,MockToken.constructor +FNDA:0,MockToken.constructor +DA:10,0 +FNF:1 +FNH:0 +LF:2 +LH:0 +BRF:0 +BRH:0 +end_of_record +TN: +SF:test/Payment.t.sol +DA:12,0 +FN:12,MockToken.constructor +FNDA:0,MockToken.constructor +DA:13,0 +FNF:1 +FNH:0 +LF:2 +LH:0 +BRF:0 +BRH:0 +end_of_record +TN: +SF:test/QuotaControl.t.sol +DA:14,0 +FN:14,TestableQuotaControl.exposeCheckedResetClaimed +FNDA:0,TestableQuotaControl.exposeCheckedResetClaimed +DA:15,0 +DA:18,0 +FN:18,TestableQuotaControl.exposeCheckedUpdateClaimed +FNDA:0,TestableQuotaControl.exposeCheckedUpdateClaimed +DA:19,0 +FNF:2 +FNH:0 +LF:4 +LH:0 +BRF:0 +BRH:0 +end_of_record +TN: +SF:test/SwarmRegistryL1.t.sol +DA:13,60 +FN:13,MockBondTokenL1.mint +FNDA:60,MockBondTokenL1.mint +DA:14,60 +FNF:1 +FNH:1 +LF:2 +LH:2 +BRF:0 +BRH:0 +end_of_record +TN: +SF:test/SwarmRegistryUniversal.t.sol +DA:13,69 +FN:13,MockBondTokenUniv.mint +FNDA:69,MockBondTokenUniv.mint +DA:14,69 +FNF:1 +FNH:1 +LF:2 +LH:2 +BRF:0 +BRH:0 +end_of_record +TN: +SF:test/__helpers__/AccessControlUtils.sol +DA:9,0 +FN:9,AccessControlUtils.expectRevert_AccessControlUnauthorizedAccount +FNDA:0,AccessControlUtils.expectRevert_AccessControlUnauthorizedAccount +DA:10,0 +FNF:1 +FNH:0 +LF:2 +LH:0 +BRF:0 +BRH:0 +end_of_record +TN: +SF:test/bridge/L1Bridge.t.sol +DA:31,0 +FN:31,MockMailbox.setL1ToL2Failed +FNDA:0,MockMailbox.setL1ToL2Failed +DA:32,0 +DA:35,0 +FN:35,MockMailbox.setInclusion +FNDA:0,MockMailbox.setInclusion +DA:36,0 +DA:39,0 +FN:39,MockMailbox.setBaseCostReturn +FNDA:0,MockMailbox.setBaseCostReturn +DA:40,0 +DA:43,0 +FN:43,MockMailbox.expectBaseCostParams +FNDA:0,MockMailbox.expectBaseCostParams +DA:44,0 +DA:45,0 +DA:46,0 +DA:50,0 +FN:50,MockMailbox.requestL2Transaction +FNDA:0,MockMailbox.requestL2Transaction +DA:59,0 +DA:60,0 +DA:63,0 +DA:66,0 +FN:66,MockMailbox.proveL1ToL2TransactionStatus +FNDA:0,MockMailbox.proveL1ToL2TransactionStatus +DA:74,0 +BRDA:74,0,0,- +DA:75,0 +DA:77,0 +DA:80,0 +FN:80,MockMailbox.proveL2MessageInclusion +FNDA:0,MockMailbox.proveL2MessageInclusion +DA:86,0 +DA:89,0 +FN:89,MockMailbox.l2TransactionBaseCost +FNDA:0,MockMailbox.l2TransactionBaseCost +DA:95,0 +BRDA:95,1,0,- +BRDA:95,1,1,- +DA:96,0 +BRDA:96,2,0,- +BRDA:96,2,1,- +DA:97,0 +BRDA:97,3,0,- +BRDA:97,3,1,- +DA:98,0 +FNF:8 +FNH:0 +LF:25 +LH:0 +BRF:7 +BRH:0 +end_of_record +TN: +SF:test/bridge/MigrationNFT.t.sol +DA:12,0 +FN:12,MigrationNFTTestUtils.bridgeTokens +FNDA:0,MigrationNFTTestUtils.bridgeTokens +DA:20,0 +DA:21,0 +DA:22,0 +FNF:1 +FNH:0 +LF:4 +LH:0 +BRF:0 +BRH:0 +end_of_record +TN: +SF:test/contentsign/BaseContentSign.t.sol +DA:13,0 +FN:13,MockContentSign.setWhitelisted +FNDA:0,MockContentSign.setWhitelisted +DA:14,0 +DA:17,0 +FN:17,MockContentSign._userIsWhitelisted +FNDA:0,MockContentSign._userIsWhitelisted +DA:18,0 +FNF:2 +FNH:0 +LF:4 +LH:0 +BRF:0 +BRH:0 +end_of_record +TN: +SF:test/contentsign/ClickBounty.t.sol +DA:13,0 +FN:13,MockERC20.constructor +FNDA:0,MockERC20.constructor +DA:14,0 +DA:21,0 +FN:21,MockContentSign._userIsWhitelisted +FNDA:0,MockContentSign._userIsWhitelisted +DA:22,0 +FNF:2 +FNH:0 +LF:4 +LH:0 +BRF:0 +BRH:0 +end_of_record +TN: +SF:test/contentsign/PaymentMiddleware.t.sol +DA:16,0 +FN:16,MockToken.mint +FNDA:0,MockToken.mint +DA:17,0 +DA:24,0 +FN:24,MockWhitelist.mint +FNDA:0,MockWhitelist.mint +DA:25,0 +DA:32,0 +FN:32,MockContentSign._userIsWhitelisted +FNDA:0,MockContentSign._userIsWhitelisted +DA:33,0 +FNF:3 +FNH:0 +LF:6 +LH:0 +BRF:0 +BRH:0 +end_of_record +TN: +SF:test/paymasters/BasePaymaster.t.sol +DA:13,0 +FN:13,MockPaymaster._validateAndPayGeneralFlow +FNDA:0,MockPaymaster._validateAndPayGeneralFlow +DA:15,0 +DA:18,0 +FN:18,MockPaymaster._validateAndPayApprovalBasedFlow +FNDA:0,MockPaymaster._validateAndPayApprovalBasedFlow +DA:23,0 +FNF:2 +FNH:0 +LF:4 +LH:0 +BRF:0 +BRH:0 +end_of_record +TN: +SF:test/paymasters/WhitelistPaymaster.t.sol +DA:14,0 +FN:14,MockWhitelistPaymaster.mock_validateAndPayGeneralFlow +FNDA:0,MockWhitelistPaymaster.mock_validateAndPayGeneralFlow +DA:15,0 +DA:18,0 +FN:18,MockWhitelistPaymaster.mock_validateAndPayApprovalBasedFlow +FNDA:0,MockWhitelistPaymaster.mock_validateAndPayApprovalBasedFlow +DA:26,0 +FNF:2 +FNH:0 +LF:4 +LH:0 +BRF:0 +BRH:0 +end_of_record diff --git a/coverage.lcov b/coverage.lcov new file mode 100644 index 00000000..3348fa9b --- /dev/null +++ b/coverage.lcov @@ -0,0 +1,3337 @@ +TN: +SF:script/CheckBridge.s.sol +DA:13,0 +FN:13,CheckBridge.setUp +FNDA:0,CheckBridge.setUp +DA:14,0 +DA:15,0 +DA:18,0 +FN:18,CheckBridge.run +FNDA:0,CheckBridge.run +DA:19,0 +DA:20,0 +DA:22,0 +DA:23,0 +DA:25,0 +BRDA:25,0,0,- +BRDA:25,0,1,- +DA:26,0 +DA:28,0 +DA:30,0 +BRDA:30,1,0,- +BRDA:30,1,1,- +DA:31,0 +DA:32,0 +BRDA:32,2,0,- +BRDA:32,2,1,- +DA:33,0 +DA:35,0 +DA:38,0 +FNF:2 +FNH:0 +LF:17 +LH:0 +BRF:6 +BRH:0 +end_of_record +TN: +SF:script/ContentSignWhitelist.s.sol +DA:13,0 +FN:13,ContentSignWhitelist.setUp +FNDA:0,ContentSignWhitelist.setUp +DA:14,0 +DA:15,0 +DA:18,0 +FN:18,ContentSignWhitelist.run +FNDA:0,ContentSignWhitelist.run +DA:19,0 +DA:21,0 +BRDA:21,0,0,- +BRDA:21,0,1,- +DA:22,0 +DA:24,0 +DA:26,0 +DA:27,0 +DA:28,0 +FNF:2 +FNH:0 +LF:11 +LH:0 +BRF:2 +BRH:0 +end_of_record +TN: +SF:script/DeployClick.s.sol +DA:14,0 +FN:14,DeployClick.setUp +FNDA:0,DeployClick.setUp +DA:15,0 +DA:16,0 +DA:18,0 +DA:19,0 +DA:22,0 +FN:22,DeployClick.run +FNDA:0,DeployClick.run +DA:23,0 +DA:25,0 +DA:26,0 +DA:28,0 +DA:29,0 +DA:32,0 +DA:35,0 +DA:37,0 +DA:39,0 +DA:40,0 +DA:41,0 +FNF:2 +FNH:0 +LF:17 +LH:0 +BRF:0 +BRH:0 +end_of_record +TN: +SF:script/DeployContentSignEnterprise.s.sol +DA:15,0 +FN:15,DeployContentSignEnterprise.setUp +FNDA:0,DeployContentSignEnterprise.setUp +DA:16,0 +DA:17,0 +DA:18,0 +DA:21,0 +FN:21,DeployContentSignEnterprise.run +FNDA:0,DeployContentSignEnterprise.run +DA:22,0 +DA:24,0 +DA:26,0 +DA:28,0 +DA:29,0 +DA:30,0 +DA:31,0 +FNF:2 +FNH:0 +LF:12 +LH:0 +BRF:0 +BRH:0 +end_of_record +TN: +SF:script/DeployL1Bridge.s.sol +DA:22,0 +FN:22,DeployL1Bridge.setUp +FNDA:0,DeployL1Bridge.setUp +DA:23,0 +DA:24,0 +DA:25,0 +DA:26,0 +DA:28,0 +DA:29,0 +DA:30,0 +DA:31,0 +DA:34,0 +FN:34,DeployL1Bridge.run +FNDA:0,DeployL1Bridge.run +DA:35,0 +DA:37,0 +DA:39,0 +DA:40,0 +DA:41,0 +DA:43,0 +DA:45,0 +DA:46,0 +FNF:2 +FNH:0 +LF:18 +LH:0 +BRF:0 +BRH:0 +end_of_record +TN: +SF:script/DeployL1Ens.s.sol +DA:18,0 +FN:18,DeployL1Ens.run +FNDA:0,DeployL1Ens.run +DA:19,0 +DA:20,0 +DA:22,0 +DA:24,0 +BRDA:24,0,0,- +BRDA:24,0,1,- +DA:25,0 +DA:26,0 +BRDA:26,1,0,- +BRDA:26,1,1,- +DA:27,0 +DA:28,0 +DA:29,0 +DA:30,0 +DA:32,0 +DA:35,0 +DA:36,0 +DA:39,0 +DA:40,0 +DA:42,0 +DA:45,0 +DA:47,0 +BRDA:47,2,0,- +DA:48,0 +DA:49,0 +DA:55,0 +DA:56,0 +DA:59,0 +DA:60,0 +DA:62,0 +DA:63,0 +DA:65,0 +DA:66,0 +DA:68,0 +FNF:1 +FNH:0 +LF:30 +LH:0 +BRF:5 +BRH:0 +end_of_record +TN: +SF:script/DeployL1Nodl.s.sol +DA:17,0 +FN:17,DeployL1Nodl.setUp +FNDA:0,DeployL1Nodl.setUp +DA:18,0 +DA:19,0 +DA:21,0 +DA:22,0 +DA:25,0 +FN:25,DeployL1Nodl.run +FNDA:0,DeployL1Nodl.run +DA:26,0 +DA:28,0 +DA:30,0 +DA:32,0 +FNF:2 +FNH:0 +LF:10 +LH:0 +BRF:0 +BRH:0 +end_of_record +TN: +SF:script/DeployL2Bridge.s.sol +DA:19,0 +FN:19,DeployL2Bridge.setUp +FNDA:0,DeployL2Bridge.setUp +DA:20,0 +DA:21,0 +DA:23,0 +DA:24,0 +DA:27,0 +FN:27,DeployL2Bridge.run +FNDA:0,DeployL2Bridge.run +DA:28,0 +DA:30,0 +DA:32,0 +DA:33,0 +DA:34,0 +DA:36,0 +DA:38,0 +DA:39,0 +FNF:2 +FNH:0 +LF:14 +LH:0 +BRF:0 +BRH:0 +end_of_record +TN: +SF:script/DeployL2Nodl.s.sol +DA:17,0 +FN:17,DeployL2Nodl.setUp +FNDA:0,DeployL2Nodl.setUp +DA:18,0 +DA:19,0 +DA:21,0 +DA:22,0 +DA:25,0 +FN:25,DeployL2Nodl.run +FNDA:0,DeployL2Nodl.run +DA:26,0 +DA:28,0 +DA:29,0 +DA:30,0 +DA:32,0 +DA:34,0 +FNF:2 +FNH:0 +LF:12 +LH:0 +BRF:0 +BRH:0 +end_of_record +TN: +SF:script/DeployMigrationNFT.s.sol +DA:18,0 +FN:18,DeployMigrationNFT.setUp +FNDA:0,DeployMigrationNFT.setUp +DA:19,0 +DA:20,0 +DA:22,0 +DA:23,0 +DA:24,0 +DA:25,0 +DA:27,0 +DA:28,0 +DA:32,0 +FN:32,DeployMigrationNFT.run +FNDA:0,DeployMigrationNFT.run +DA:33,0 +DA:35,0 +DA:37,0 +DA:39,0 +FNF:2 +FNH:0 +LF:14 +LH:0 +BRF:0 +BRH:0 +end_of_record +TN: +SF:script/DeployNodlMigration.sol +DA:14,0 +FN:14,DeployNodlMigration.setUp +FNDA:0,DeployNodlMigration.setUp +DA:15,0 +DA:16,0 +DA:18,0 +DA:19,0 +DA:20,0 +DA:23,0 +FN:23,DeployNodlMigration.run +FNDA:0,DeployNodlMigration.run +DA:24,0 +DA:26,0 +DA:27,0 +DA:29,0 +DA:30,0 +DA:32,0 +DA:34,0 +DA:36,0 +DA:37,0 +FNF:2 +FNH:0 +LF:16 +LH:0 +BRF:0 +BRH:0 +end_of_record +TN: +SF:script/DeployRewards.sol +DA:18,0 +FN:18,DeployRewards.setUp +FNDA:0,DeployRewards.setUp +DA:19,0 +DA:20,0 +DA:21,0 +DA:22,0 +DA:23,0 +DA:24,0 +DA:27,0 +FN:27,DeployRewards.run +FNDA:0,DeployRewards.run +DA:28,0 +DA:29,0 +BRDA:29,0,0,- +DA:30,0 +DA:31,0 +DA:32,0 +DA:35,0 +DA:36,0 +DA:39,0 +DA:40,0 +DA:42,0 +DA:44,0 +FNF:2 +FNH:0 +LF:19 +LH:0 +BRF:1 +BRH:0 +end_of_record +TN: +SF:script/RewardsSig.s.sol +DA:15,0 +FN:15,RewardsSig.setUp +FNDA:0,RewardsSig.setUp +DA:16,0 +DA:17,0 +DA:18,0 +DA:19,0 +DA:22,0 +FN:22,RewardsSig.run +FNDA:0,RewardsSig.run +DA:23,0 +DA:25,0 +DA:26,0 +DA:27,0 +DA:29,0 +DA:31,0 +DA:33,0 +DA:34,0 +DA:36,0 +DA:37,0 +DA:39,0 +DA:40,0 +DA:42,0 +DA:43,0 +FNF:2 +FNH:0 +LF:20 +LH:0 +BRF:0 +BRH:0 +end_of_record +TN: +SF:src/Grants.sol +DA:56,0 +FN:56,Grants.constructor +FNDA:0,Grants.constructor +DA:57,0 +BRDA:57,0,0,- +DA:58,0 +DA:61,0 +DA:62,0 +DA:63,0 +DA:75,0 +FN:75,Grants.addVestingSchedule +FNDA:0,Grants.addVestingSchedule +DA:83,0 +DA:85,0 +DA:87,0 +DA:88,0 +BRDA:88,1,0,- +DA:89,0 +DA:90,0 +DA:92,0 +DA:94,0 +DA:96,0 +DA:106,0 +FN:106,Grants.validateVestingSchedule +FNDA:0,Grants.validateVestingSchedule +DA:110,0 +DA:111,0 +DA:112,0 +DA:113,0 +DA:114,0 +DA:123,0 +FN:123,Grants.claim +FNDA:0,Grants.claim +DA:124,0 +DA:125,0 +DA:127,0 +DA:129,0 +DA:130,0 +DA:131,0 +DA:132,0 +DA:133,0 +DA:134,0 +BRDA:134,2,0,- +DA:135,0 +DA:136,0 +DA:137,0 +DA:138,0 +DA:139,0 +DA:140,0 +DA:141,0 +DA:142,0 +BRDA:142,3,0,- +DA:143,0 +DA:144,0 +DA:145,0 +DA:148,0 +DA:152,0 +BRDA:152,4,0,- +BRDA:152,4,1,- +DA:153,0 +DA:154,0 +DA:156,0 +DA:167,0 +FN:167,Grants.renounce +FNDA:0,Grants.renounce +DA:168,0 +DA:170,0 +DA:171,0 +DA:172,0 +DA:173,0 +DA:174,0 +BRDA:174,5,0,- +DA:175,0 +DA:176,0 +DA:181,0 +BRDA:181,6,0,- +BRDA:181,6,1,- +DA:182,0 +DA:184,0 +DA:195,0 +FN:195,Grants.cancelVestingSchedules +FNDA:0,Grants.cancelVestingSchedules +DA:196,0 +DA:197,0 +DA:198,0 +DA:200,0 +DA:201,0 +DA:202,0 +DA:203,0 +DA:204,0 +DA:205,0 +DA:206,0 +BRDA:206,7,0,- +DA:207,0 +DA:208,0 +DA:209,0 +DA:210,0 +DA:211,0 +DA:212,0 +DA:213,0 +DA:214,0 +DA:215,0 +DA:216,0 +DA:217,0 +DA:219,0 +DA:223,0 +BRDA:223,8,0,- +DA:224,0 +DA:227,0 +BRDA:227,9,0,- +DA:228,0 +DA:231,0 +BRDA:231,10,0,- +DA:232,0 +DA:235,0 +DA:243,0 +FN:243,Grants.getGrantsCount +FNDA:0,Grants.getGrantsCount +DA:244,0 +DA:245,0 +DA:246,0 +DA:248,0 +DA:253,0 +FN:253,Grants._mustBeNonZero +FNDA:0,Grants._mustBeNonZero +DA:254,0 +BRDA:254,11,0,- +DA:255,0 +DA:259,0 +FN:259,Grants._mustBeNonZeroAddress +FNDA:0,Grants._mustBeNonZeroAddress +DA:260,0 +BRDA:260,12,0,- +DA:261,0 +DA:265,0 +FN:265,Grants._mustNotBeSelf +FNDA:0,Grants._mustNotBeSelf +DA:266,0 +BRDA:266,13,0,- +DA:267,0 +DA:271,0 +FN:271,Grants._mustBeEqualOrExceedMinAmount +FNDA:0,Grants._mustBeEqualOrExceedMinAmount +DA:272,0 +BRDA:272,14,0,- +DA:273,0 +DA:277,0 +FN:277,Grants._sanitizePageRange +FNDA:0,Grants._sanitizePageRange +DA:278,0 +DA:279,0 +BRDA:279,15,0,- +DA:280,0 +DA:282,0 +BRDA:282,16,0,- +DA:283,0 +DA:285,0 +FNF:12 +FNH:0 +LF:114 +LH:0 +BRF:19 +BRH:0 +end_of_record +TN: +SF:src/L1Nodl.sol +DA:18,0 +FN:18,L1Nodl.constructor +FNDA:0,L1Nodl.constructor +DA:19,0 +BRDA:19,0,0,- +DA:20,0 +DA:22,0 +DA:23,0 +DA:26,0 +FN:26,L1Nodl.mint +FNDA:0,L1Nodl.mint +DA:27,0 +DA:30,0 +FN:30,L1Nodl.clock +FNDA:0,L1Nodl.clock +DA:31,0 +DA:35,0 +FN:35,L1Nodl.CLOCK_MODE +FNDA:0,L1Nodl.CLOCK_MODE +DA:36,0 +DA:39,0 +FN:39,L1Nodl.nonces +FNDA:0,L1Nodl.nonces +DA:40,0 +DA:43,0 +FN:43,L1Nodl._update +FNDA:0,L1Nodl._update +DA:44,0 +FNF:6 +FNH:0 +LF:15 +LH:0 +BRF:1 +BRH:0 +end_of_record +TN: +SF:src/NODL.sol +DA:12,0 +FN:12,NODL.constructor +FNDA:0,NODL.constructor +DA:13,0 +DA:14,0 +DA:17,0 +FN:17,NODL.mint +FNDA:0,NODL.mint +DA:18,0 +DA:20,0 +FNF:2 +FNH:0 +LF:6 +LH:0 +BRF:0 +BRH:0 +end_of_record +TN: +SF:src/Payment.sol +DA:40,0 +FN:40,Payment.constructor +FNDA:0,Payment.constructor +DA:43,0 +DA:44,0 +DA:58,0 +FN:58,Payment.pay +FNDA:0,Payment.pay +DA:59,0 +DA:60,0 +DA:62,0 +BRDA:62,0,0,- +DA:63,0 +DA:66,0 +DA:67,0 +DA:69,0 +DA:70,0 +DA:83,0 +FN:83,Payment.withdraw +FNDA:0,Payment.withdraw +DA:84,0 +FNF:3 +FNH:0 +LF:14 +LH:0 +BRF:1 +BRH:0 +end_of_record +TN: +SF:src/QuotaControl.sol +DA:84,0 +FN:84,QuotaControl.constructor +FNDA:0,QuotaControl.constructor +DA:85,0 +DA:86,0 +DA:88,0 +DA:89,0 +DA:90,0 +DA:97,0 +FN:97,QuotaControl.setQuota +FNDA:0,QuotaControl.setQuota +DA:98,0 +DA:99,0 +DA:109,0 +FN:109,QuotaControl.setPeriod +FNDA:0,QuotaControl.setPeriod +DA:110,0 +DA:111,0 +DA:112,0 +DA:121,0 +FN:121,QuotaControl._checkedResetClaimed +FNDA:0,QuotaControl._checkedResetClaimed +DA:122,0 +BRDA:122,0,0,- +DA:123,0 +DA:126,0 +DA:127,0 +DA:140,0 +FN:140,QuotaControl._checkedUpdateClaimed +FNDA:0,QuotaControl._checkedUpdateClaimed +DA:141,0 +DA:142,0 +BRDA:142,1,0,- +DA:143,0 +DA:145,0 +DA:156,0 +FN:156,QuotaControl._mustBeWithinPeriodRange +FNDA:0,QuotaControl._mustBeWithinPeriodRange +DA:157,0 +BRDA:157,2,0,- +DA:158,0 +DA:160,0 +BRDA:160,3,0,- +DA:161,0 +FNF:6 +FNH:0 +LF:28 +LH:0 +BRF:4 +BRH:0 +end_of_record +TN: +SF:src/Rewards.sol +DA:137,0 +FN:137,Rewards.constructor +FNDA:0,Rewards.constructor +DA:145,0 +DA:147,0 +DA:148,0 +DA:149,0 +DA:157,0 +FN:157,Rewards.mintReward +FNDA:0,Rewards.mintReward +DA:158,0 +DA:159,0 +DA:161,0 +DA:162,0 +DA:165,0 +DA:166,0 +DA:168,0 +DA:176,0 +FN:176,Rewards.mintBatchReward +FNDA:0,Rewards.mintBatchReward +DA:177,0 +DA:178,0 +DA:180,0 +DA:182,0 +DA:184,0 +DA:186,0 +DA:187,0 +DA:189,0 +DA:192,0 +DA:194,0 +DA:196,0 +DA:197,0 +DA:199,0 +DA:201,0 +DA:208,0 +FN:208,Rewards.setBatchSubmitterRewardBasisPoints +FNDA:0,Rewards.setBatchSubmitterRewardBasisPoints +DA:209,0 +DA:210,0 +DA:211,0 +DA:213,0 +DA:220,0 +FN:220,Rewards._mustBeLessThanBasisPointsDivisor +FNDA:0,Rewards._mustBeLessThanBasisPointsDivisor +DA:221,0 +BRDA:221,0,0,- +DA:222,0 +DA:231,0 +FN:231,Rewards._mustBeExpectedSequence +FNDA:0,Rewards._mustBeExpectedSequence +DA:232,0 +BRDA:232,1,0,- +DA:233,0 +DA:241,0 +FN:241,Rewards._mustBeExpectedBatchSequence +FNDA:0,Rewards._mustBeExpectedBatchSequence +DA:242,0 +BRDA:242,2,0,- +DA:243,0 +DA:251,0 +FN:251,Rewards._mustBeValidBatchStructure +FNDA:0,Rewards._mustBeValidBatchStructure +DA:252,0 +BRDA:252,3,0,- +DA:253,0 +DA:263,0 +FN:263,Rewards._mustBeFromAuthorizedOracle +FNDA:0,Rewards._mustBeFromAuthorizedOracle +DA:264,0 +BRDA:264,4,0,- +DA:265,0 +DA:274,0 +FN:274,Rewards._batchSum +FNDA:0,Rewards._batchSum +DA:275,0 +DA:276,0 +DA:277,0 +DA:279,0 +DA:287,0 +FN:287,Rewards.digestReward +FNDA:0,Rewards.digestReward +DA:288,0 +DA:289,0 +DA:297,0 +FN:297,Rewards.digestBatchReward +FNDA:0,Rewards.digestBatchReward +DA:298,0 +DA:299,0 +DA:300,0 +DA:301,0 +DA:308,0 +FN:308,Rewards.latestBatchDetails +FNDA:0,Rewards.latestBatchDetails +DA:309,0 +FNF:13 +FNH:0 +LF:63 +LH:0 +BRF:5 +BRH:0 +end_of_record +TN: +SF:src/bridge/BridgeBase.sol +DA:70,0 +FN:70,BridgeBase.constructor +FNDA:0,BridgeBase.constructor +DA:71,0 +DA:72,0 +DA:73,0 +DA:75,0 +DA:76,0 +DA:79,0 +DA:80,0 +DA:81,0 +DA:90,0 +FN:90,BridgeBase._createVote +FNDA:0,BridgeBase._createVote +DA:91,0 +DA:93,0 +DA:99,0 +FN:99,BridgeBase._recordVote +FNDA:0,BridgeBase._recordVote +DA:100,0 +DA:102,0 +DA:104,0 +DA:108,0 +FN:108,BridgeBase._processVote +FNDA:0,BridgeBase._processVote +DA:109,0 +DA:110,0 +DA:111,0 +DA:116,0 +FN:116,BridgeBase._execute +FNDA:0,BridgeBase._execute +DA:117,0 +DA:118,0 +DA:119,0 +DA:121,0 +DA:164,0 +FN:164,BridgeBase._mustNotHaveExecutedYet +FNDA:0,BridgeBase._mustNotHaveExecutedYet +DA:165,0 +BRDA:165,0,0,- +DA:166,0 +DA:170,0 +FN:170,BridgeBase._mustBePastSafetyDelay +FNDA:0,BridgeBase._mustBePastSafetyDelay +DA:171,0 +BRDA:171,1,0,- +DA:172,0 +DA:176,0 +FN:176,BridgeBase._mustHaveEnoughVotes +FNDA:0,BridgeBase._mustHaveEnoughVotes +DA:177,0 +BRDA:177,2,0,- +DA:178,0 +DA:182,0 +FN:182,BridgeBase._mustHaveEnoughOracles +FNDA:0,BridgeBase._mustHaveEnoughOracles +DA:183,0 +BRDA:183,3,0,- +DA:184,0 +DA:188,0 +FN:188,BridgeBase._mustBeAnOracle +FNDA:0,BridgeBase._mustBeAnOracle +DA:189,0 +BRDA:189,4,0,- +DA:190,0 +DA:194,0 +FN:194,BridgeBase._mustNotExceedMaxOracles +FNDA:0,BridgeBase._mustNotExceedMaxOracles +DA:195,0 +BRDA:195,5,0,- +DA:196,0 +DA:200,0 +FN:200,BridgeBase._mustNotBeZeroMinVotes +FNDA:0,BridgeBase._mustNotBeZeroMinVotes +DA:201,0 +BRDA:201,6,0,- +DA:202,0 +DA:206,0 +FN:206,BridgeBase._mustNotHaveVotedYet +FNDA:0,BridgeBase._mustNotHaveVotedYet +DA:207,0 +BRDA:207,7,0,- +DA:208,0 +FNF:13 +FNH:0 +LF:49 +LH:0 +BRF:8 +BRH:0 +end_of_record +TN: +SF:src/bridge/GrantsMigration.sol +DA:57,0 +FN:57,GrantsMigration.constructor +FNDA:0,GrantsMigration.constructor +DA:60,0 +DA:70,0 +FN:70,GrantsMigration.bridge +FNDA:0,GrantsMigration.bridge +DA:73,0 +DA:74,0 +DA:76,0 +BRDA:76,0,0,- +BRDA:76,0,1,- +DA:77,0 +DA:78,0 +DA:80,0 +DA:88,0 +FN:88,GrantsMigration.grant +FNDA:0,GrantsMigration.grant +DA:89,0 +DA:90,0 +DA:91,0 +DA:92,0 +DA:93,0 +DA:94,0 +DA:103,0 +DA:108,0 +FN:108,GrantsMigration._mustNotBeChangingParameters +FNDA:0,GrantsMigration._mustNotBeChangingParameters +DA:114,0 +DA:116,0 +BRDA:116,1,0,- +DA:117,0 +DA:120,0 +DA:121,0 +BRDA:121,2,0,- +DA:122,0 +DA:125,0 +DA:127,0 +DA:128,0 +DA:129,0 +DA:130,0 +DA:131,0 +DA:132,0 +BRDA:132,3,0,- +DA:133,0 +DA:138,0 +FN:138,GrantsMigration._createProposal +FNDA:0,GrantsMigration._createProposal +DA:145,0 +BRDA:145,4,0,- +DA:146,0 +DA:148,0 +BRDA:148,5,0,- +DA:149,0 +DA:151,0 +DA:152,0 +DA:153,0 +DA:156,0 +DA:158,0 +BRDA:158,6,0,- +DA:159,0 +DA:161,0 +DA:162,0 +DA:165,0 +FN:165,GrantsMigration._proposalExists +FNDA:0,GrantsMigration._proposalExists +DA:166,0 +DA:169,0 +FN:169,GrantsMigration._flagAsExecuted +FNDA:0,GrantsMigration._flagAsExecuted +DA:170,0 +DA:173,0 +FN:173,GrantsMigration._incTotalVotes +FNDA:0,GrantsMigration._incTotalVotes +DA:174,0 +DA:177,0 +FN:177,GrantsMigration._updateLastVote +FNDA:0,GrantsMigration._updateLastVote +DA:178,0 +DA:181,0 +FN:181,GrantsMigration._totalVotes +FNDA:0,GrantsMigration._totalVotes +DA:182,0 +DA:185,0 +FN:185,GrantsMigration._lastVote +FNDA:0,GrantsMigration._lastVote +DA:186,0 +DA:189,0 +FN:189,GrantsMigration._executed +FNDA:0,GrantsMigration._executed +DA:190,0 +FNF:12 +FNH:0 +LF:59 +LH:0 +BRF:8 +BRH:0 +end_of_record +TN: +SF:src/bridge/L1Bridge.sol +DA:80,0 +FN:80,L1Bridge.constructor +FNDA:0,L1Bridge.constructor +DA:81,0 +BRDA:81,0,0,- +DA:82,0 +DA:84,0 +DA:85,0 +DA:86,0 +DA:94,0 +FN:94,L1Bridge.pause +FNDA:0,L1Bridge.pause +DA:95,0 +DA:99,0 +FN:99,L1Bridge.unpause +FNDA:0,L1Bridge.unpause +DA:100,0 +DA:115,0 +FN:115,L1Bridge.quoteL2BaseCost +FNDA:0,L1Bridge.quoteL2BaseCost +DA:120,0 +DA:130,0 +FN:130,L1Bridge.quoteL2BaseCostAtGasPrice +FNDA:0,L1Bridge.quoteL2BaseCostAtGasPrice +DA:135,0 +DA:152,0 +FN:152,L1Bridge.deposit +FNDA:0,L1Bridge.deposit +DA:159,0 +BRDA:159,1,0,- +DA:160,0 +DA:162,0 +BRDA:162,2,0,- +DA:163,0 +DA:166,0 +DA:168,0 +DA:169,0 +DA:171,0 +DA:175,0 +DA:177,0 +DA:183,0 +FN:183,L1Bridge.deposit +FNDA:0,L1Bridge.deposit +DA:189,0 +DA:202,0 +FN:202,L1Bridge.claimFailedDeposit +FNDA:0,L1Bridge.claimFailedDeposit +DA:210,0 +DA:211,0 +BRDA:211,3,0,- +DA:212,0 +DA:214,0 +DA:217,0 +BRDA:217,4,0,- +DA:218,0 +DA:220,0 +DA:221,0 +DA:222,0 +DA:234,0 +FN:234,L1Bridge.finalizeWithdrawal +FNDA:0,L1Bridge.finalizeWithdrawal +DA:241,0 +BRDA:241,5,0,- +DA:242,0 +DA:245,0 +DA:246,0 +DA:249,0 +DA:255,0 +BRDA:255,6,0,- +DA:256,0 +DA:259,0 +DA:261,0 +DA:262,0 +DA:276,0 +FN:276,L1Bridge._parseL2WithdrawalMessage +FNDA:0,L1Bridge._parseL2WithdrawalMessage +DA:282,0 +BRDA:282,7,0,- +DA:283,0 +DA:287,0 +DA:288,0 +BRDA:288,8,0,- +DA:289,0 +DA:291,0 +DA:292,0 +FNF:10 +FNH:0 +LF:56 +LH:0 +BRF:9 +BRH:0 +end_of_record +TN: +SF:src/bridge/L2Bridge.sol +DA:49,0 +FN:49,L2Bridge.onlyL1Bridge +FNDA:0,L2Bridge.onlyL1Bridge +DA:50,0 +BRDA:50,0,0,- +DA:51,0 +DA:65,0 +FN:65,L2Bridge.constructor +FNDA:0,L2Bridge.constructor +DA:66,0 +BRDA:66,1,0,- +DA:67,0 +DA:69,0 +DA:70,0 +DA:78,0 +FN:78,L2Bridge.pause +FNDA:0,L2Bridge.pause +DA:79,0 +DA:83,0 +FN:83,L2Bridge.unpause +FNDA:0,L2Bridge.unpause +DA:84,0 +DA:90,0 +FN:90,L2Bridge.initialize +FNDA:0,L2Bridge.initialize +DA:91,0 +BRDA:91,2,0,- +DA:92,0 +DA:94,0 +BRDA:94,3,0,- +DA:95,0 +DA:97,0 +DA:103,0 +FN:103,L2Bridge.finalizeDeposit +FNDA:0,L2Bridge.finalizeDeposit +DA:109,0 +BRDA:109,4,0,- +DA:110,0 +DA:112,0 +BRDA:112,5,0,- +DA:113,0 +DA:116,0 +DA:118,0 +DA:124,0 +FN:124,L2Bridge.withdraw +FNDA:0,L2Bridge.withdraw +DA:125,0 +BRDA:125,6,0,- +DA:126,0 +DA:128,0 +BRDA:128,7,0,- +DA:129,0 +DA:132,0 +DA:135,0 +DA:136,0 +DA:138,0 +FNF:7 +FNH:0 +LF:34 +LH:0 +BRF:8 +BRH:0 +end_of_record +TN: +SF:src/bridge/MigrationNFT.sol +DA:69,0 +FN:69,MigrationNFT.constructor +FNDA:0,MigrationNFT.constructor +DA:75,0 +BRDA:75,0,0,- +DA:76,0 +DA:78,0 +BRDA:78,1,0,- +DA:79,0 +DA:81,0 +BRDA:81,2,0,- +DA:82,0 +DA:85,0 +DA:86,0 +BRDA:86,3,0,- +DA:87,0 +DA:91,0 +DA:92,0 +DA:93,0 +DA:94,0 +DA:101,0 +FN:101,MigrationNFT.tokenURI +FNDA:0,MigrationNFT.tokenURI +DA:102,0 +DA:104,0 +DA:105,0 +DA:112,0 +FN:112,MigrationNFT.safeMint +FNDA:0,MigrationNFT.safeMint +DA:113,0 +DA:115,0 +DA:117,0 +DA:118,0 +DA:119,0 +DA:121,0 +DA:123,0 +DA:124,0 +BRDA:124,4,0,- +DA:125,0 +DA:128,0 +DA:129,0 +DA:130,0 +DA:131,0 +DA:132,0 +DA:136,0 +FN:136,MigrationNFT._computeLevelUps +FNDA:0,MigrationNFT._computeLevelUps +DA:141,0 +DA:142,0 +DA:147,0 +DA:148,0 +DA:149,0 +BRDA:149,5,0,- +DA:150,0 +DA:151,0 +DA:155,0 +BRDA:155,6,0,- +DA:156,0 +DA:160,0 +FN:160,MigrationNFT._mustNotHaveBeenClaimed +FNDA:0,MigrationNFT._mustNotHaveBeenClaimed +DA:161,0 +BRDA:161,7,0,- +DA:162,0 +DA:166,0 +FN:166,MigrationNFT._mustBeAnExistingProposal +FNDA:0,MigrationNFT._mustBeAnExistingProposal +DA:168,0 +BRDA:168,8,0,- +DA:169,0 +DA:173,0 +FN:173,MigrationNFT._mustBeExecuted +FNDA:0,MigrationNFT._mustBeExecuted +DA:174,0 +BRDA:174,9,0,- +DA:175,0 +DA:179,0 +FN:179,MigrationNFT._mustAlreadyBeHolderOrEnoughHoldersRemaining +FNDA:0,MigrationNFT._mustAlreadyBeHolderOrEnoughHoldersRemaining +DA:180,0 +DA:181,0 +BRDA:181,10,0,- +DA:182,0 +DA:186,0 +FN:186,MigrationNFT._update +FNDA:0,MigrationNFT._update +DA:187,0 +DA:188,0 +BRDA:188,11,0,- +DA:190,0 +DA:193,0 +FNF:9 +FNH:0 +LF:61 +LH:0 +BRF:12 +BRH:0 +end_of_record +TN: +SF:src/bridge/NODLMigration.sol +DA:35,0 +FN:35,NODLMigration.bridge +FNDA:0,NODLMigration.bridge +DA:36,0 +DA:37,0 +DA:39,0 +BRDA:39,0,0,- +BRDA:39,0,1,- +DA:40,0 +DA:41,0 +DA:43,0 +DA:50,0 +FN:50,NODLMigration.withdraw +FNDA:0,NODLMigration.withdraw +DA:51,0 +DA:52,0 +DA:55,0 +FN:55,NODLMigration._mustNotBeChangingParameters +FNDA:0,NODLMigration._mustNotBeChangingParameters +DA:56,0 +BRDA:56,1,0,- +DA:57,0 +DA:61,0 +FN:61,NODLMigration._proposalExists +FNDA:0,NODLMigration._proposalExists +DA:62,0 +DA:65,0 +FN:65,NODLMigration._createVote +FNDA:0,NODLMigration._createVote +DA:66,0 +DA:67,0 +DA:68,0 +DA:71,0 +FN:71,NODLMigration._withdraw +FNDA:0,NODLMigration._withdraw +DA:72,0 +DA:73,0 +DA:76,0 +FN:76,NODLMigration._flagAsExecuted +FNDA:0,NODLMigration._flagAsExecuted +DA:77,0 +DA:80,0 +FN:80,NODLMigration._incTotalVotes +FNDA:0,NODLMigration._incTotalVotes +DA:81,0 +DA:84,0 +FN:84,NODLMigration._updateLastVote +FNDA:0,NODLMigration._updateLastVote +DA:85,0 +DA:88,0 +FN:88,NODLMigration._totalVotes +FNDA:0,NODLMigration._totalVotes +DA:89,0 +DA:92,0 +FN:92,NODLMigration._lastVote +FNDA:0,NODLMigration._lastVote +DA:93,0 +DA:96,0 +FN:96,NODLMigration._executed +FNDA:0,NODLMigration._executed +DA:97,0 +FNF:12 +FNH:0 +LF:34 +LH:0 +BRF:3 +BRH:0 +end_of_record +TN: +SF:src/contentsign/BaseContentSign.sol +DA:17,0 +FN:17,BaseContentSign.safeMint +FNDA:0,BaseContentSign.safeMint +DA:18,0 +DA:20,0 +DA:21,0 +DA:22,0 +DA:25,0 +FN:25,BaseContentSign.tokenURI +FNDA:0,BaseContentSign.tokenURI +DA:26,0 +DA:29,0 +FN:29,BaseContentSign.supportsInterface +FNDA:0,BaseContentSign.supportsInterface +DA:36,0 +DA:39,0 +FN:39,BaseContentSign._mustBeWhitelisted +FNDA:0,BaseContentSign._mustBeWhitelisted +DA:40,0 +BRDA:40,0,0,- +DA:41,0 +FNF:4 +FNH:0 +LF:12 +LH:0 +BRF:1 +BRH:0 +end_of_record +TN: +SF:src/contentsign/ClickBounty.sol +DA:158,0 +FN:158,ClickBounty.constructor +FNDA:0,ClickBounty.constructor +DA:159,0 +DA:160,0 +DA:162,0 +DA:163,0 +DA:164,0 +DA:181,0 +FN:181,ClickBounty.payEntryFee +FNDA:0,ClickBounty.payEntryFee +DA:182,0 +DA:184,0 +BRDA:184,0,0,- +DA:185,0 +DA:188,0 +DA:189,0 +BRDA:189,1,0,- +DA:190,0 +DA:193,0 +DA:194,0 +DA:209,0 +FN:209,ClickBounty.setEntryFee +FNDA:0,ClickBounty.setEntryFee +DA:210,0 +DA:211,0 +DA:212,0 +DA:224,0 +FN:224,ClickBounty.withdraw +FNDA:0,ClickBounty.withdraw +DA:225,0 +DA:226,0 +DA:254,0 +FN:254,ClickBounty.awardBounty +FNDA:0,ClickBounty.awardBounty +DA:255,0 +DA:257,0 +BRDA:257,2,0,- +DA:258,0 +DA:260,0 +DA:262,0 +BRDA:262,3,0,- +DA:263,0 +DA:265,0 +BRDA:265,4,0,- +DA:266,0 +DA:269,0 +DA:272,0 +DA:275,0 +DA:277,0 +DA:280,0 +DA:294,0 +FN:294,ClickBounty.getLeaderboard +FNDA:0,ClickBounty.getLeaderboard +DA:295,0 +DA:296,0 +DA:297,0 +DA:298,0 +DA:317,0 +FN:317,ClickBounty._updateLeaderboard +FNDA:0,ClickBounty._updateLeaderboard +DA:319,0 +BRDA:319,5,0,- +DA:320,0 +DA:321,0 +DA:322,0 +DA:326,0 +DA:327,0 +DA:328,0 +DA:329,0 +DA:330,0 +BRDA:330,6,0,- +DA:331,0 +DA:332,0 +DA:337,0 +BRDA:337,7,0,- +DA:338,0 +DA:339,0 +FNF:7 +FNH:0 +LF:56 +LH:0 +BRF:8 +BRH:0 +end_of_record +TN: +SF:src/contentsign/ClickContentSign.sol +DA:12,0 +FN:12,ClickContentSign.constructor +FNDA:0,ClickContentSign.constructor +DA:13,0 +DA:16,0 +FN:16,ClickContentSign._userIsWhitelisted +FNDA:0,ClickContentSign._userIsWhitelisted +DA:17,0 +FNF:2 +FNH:0 +LF:4 +LH:0 +BRF:0 +BRH:0 +end_of_record +TN: +SF:src/contentsign/EnterpriseContentSign.sol +DA:13,0 +FN:13,EnterpriseContentSign.constructor +FNDA:0,EnterpriseContentSign.constructor +DA:14,0 +DA:17,0 +FN:17,EnterpriseContentSign.supportsInterface +FNDA:0,EnterpriseContentSign.supportsInterface +DA:23,0 +DA:26,0 +FN:26,EnterpriseContentSign._userIsWhitelisted +FNDA:0,EnterpriseContentSign._userIsWhitelisted +DA:27,0 +FNF:3 +FNH:0 +LF:6 +LH:0 +BRF:0 +BRH:0 +end_of_record +TN: +SF:src/contentsign/PaymentMiddleware.sol +DA:21,0 +FN:21,PaymentMiddleware.constructor +FNDA:0,PaymentMiddleware.constructor +DA:24,0 +DA:25,0 +DA:26,0 +DA:27,0 +DA:30,0 +FN:30,PaymentMiddleware.safeMint +FNDA:0,PaymentMiddleware.safeMint +DA:32,0 +BRDA:32,0,0,- +DA:33,0 +DA:37,0 +DA:40,0 +DA:43,0 +FN:43,PaymentMiddleware.withdraw +FNDA:0,PaymentMiddleware.withdraw +DA:44,0 +DA:46,0 +DA:47,0 +DA:50,0 +FN:50,PaymentMiddleware.setFeeAmount +FNDA:0,PaymentMiddleware.setFeeAmount +DA:51,0 +DA:53,0 +DA:56,0 +FN:56,PaymentMiddleware.setTarget +FNDA:0,PaymentMiddleware.setTarget +DA:57,0 +DA:59,0 +DA:62,0 +FN:62,PaymentMiddleware.setWhitelist +FNDA:0,PaymentMiddleware.setWhitelist +DA:63,0 +DA:65,0 +DA:68,0 +FN:68,PaymentMiddleware.setFeeToken +FNDA:0,PaymentMiddleware.setFeeToken +DA:69,0 +DA:71,0 +FNF:7 +FNH:0 +LF:26 +LH:0 +BRF:1 +BRH:0 +end_of_record +TN: +SF:src/nameservice/ClickNameService.sol +DA:62,0 +FN:62,ClickNameService.constructor +FNDA:0,ClickNameService.constructor +DA:63,0 +DA:64,0 +DA:68,0 +FN:68,ClickNameService.resolve +FNDA:0,ClickNameService.resolve +DA:69,0 +DA:70,0 +DA:71,0 +BRDA:71,0,0,- +DA:72,0 +DA:74,0 +DA:80,0 +FN:80,ClickNameService.batchRegister +FNDA:0,ClickNameService.batchRegister +DA:81,0 +BRDA:81,1,0,- +DA:82,0 +DA:85,0 +DA:86,0 +DA:94,0 +FN:94,ClickNameService.setDefaultExpiry +FNDA:0,ClickNameService.setDefaultExpiry +DA:95,0 +DA:99,0 +FN:99,ClickNameService.register +FNDA:0,ClickNameService.register +DA:100,0 +DA:104,0 +FN:104,ClickNameService.registerWithExpiry +FNDA:0,ClickNameService.registerWithExpiry +DA:105,0 +BRDA:105,2,0,- +DA:106,0 +DA:109,0 +DA:110,0 +DA:111,0 +DA:112,0 +DA:115,0 +FN:115,ClickNameService._register +FNDA:0,ClickNameService._register +DA:116,0 +BRDA:116,3,0,- +DA:117,0 +DA:119,0 +BRDA:119,4,0,- +DA:120,0 +DA:123,0 +DA:124,0 +DA:125,0 +BRDA:125,5,0,- +BRDA:125,5,1,- +DA:126,0 +DA:128,0 +BRDA:128,6,0,- +DA:129,0 +DA:131,0 +DA:134,0 +DA:140,0 +FN:140,ClickNameService.supportsInterface +FNDA:0,ClickNameService.supportsInterface +DA:141,0 +DA:142,0 +DA:149,0 +FN:149,ClickNameService.burn +FNDA:0,ClickNameService.burn +DA:150,0 +DA:151,0 +DA:158,0 +FN:158,ClickNameService.removeExpired +FNDA:0,ClickNameService.removeExpired +DA:159,0 +DA:160,0 +BRDA:160,7,0,- +DA:161,0 +DA:163,0 +DA:164,0 +DA:171,0 +FN:171,ClickNameService.extendExpiry +FNDA:0,ClickNameService.extendExpiry +DA:172,0 +BRDA:172,8,0,- +DA:173,0 +DA:175,0 +BRDA:175,9,0,- +DA:176,0 +DA:179,0 +BRDA:179,10,0,- +BRDA:179,10,1,- +DA:180,0 +DA:182,0 +DA:187,0 +FN:187,ClickNameService._isAlphanumeric +FNDA:0,ClickNameService._isAlphanumeric +DA:188,0 +DA:189,0 +DA:190,0 +DA:191,0 +DA:193,0 +DA:197,0 +FN:197,ClickNameService._isAuthorized +FNDA:0,ClickNameService._isAuthorized +DA:198,0 +FNF:13 +FNH:0 +LF:66 +LH:0 +BRF:13 +BRH:0 +end_of_record +TN: +SF:src/nameservice/NameService.sol +DA:75,0 +FN:75,NameService.constructor +FNDA:0,NameService.constructor +DA:78,0 +DA:79,0 +DA:83,0 +FN:83,NameService.resolve +FNDA:0,NameService.resolve +DA:84,0 +DA:85,0 +DA:86,0 +BRDA:86,0,0,- +DA:87,0 +DA:89,0 +DA:95,0 +FN:95,NameService.batchRegister +FNDA:0,NameService.batchRegister +DA:96,0 +BRDA:96,1,0,- +DA:97,0 +DA:100,0 +DA:101,0 +DA:109,0 +FN:109,NameService.setDefaultExpiry +FNDA:0,NameService.setDefaultExpiry +DA:110,0 +DA:114,0 +FN:114,NameService.register +FNDA:0,NameService.register +DA:115,0 +DA:119,0 +FN:119,NameService.registerWithExpiry +FNDA:0,NameService.registerWithExpiry +DA:120,0 +BRDA:120,2,0,- +DA:121,0 +DA:124,0 +DA:125,0 +DA:126,0 +DA:127,0 +DA:130,0 +FN:130,NameService._register +FNDA:0,NameService._register +DA:131,0 +BRDA:131,3,0,- +DA:132,0 +DA:134,0 +BRDA:134,4,0,- +DA:135,0 +DA:138,0 +DA:139,0 +DA:140,0 +BRDA:140,5,0,- +BRDA:140,5,1,- +DA:141,0 +DA:143,0 +BRDA:143,6,0,- +DA:144,0 +DA:146,0 +DA:149,0 +DA:155,0 +FN:155,NameService.supportsInterface +FNDA:0,NameService.supportsInterface +DA:156,0 +DA:157,0 +DA:164,0 +FN:164,NameService.burn +FNDA:0,NameService.burn +DA:165,0 +DA:167,0 +DA:168,0 +DA:169,0 +DA:171,0 +DA:173,0 +DA:174,0 +DA:181,0 +FN:181,NameService.removeExpired +FNDA:0,NameService.removeExpired +DA:182,0 +DA:183,0 +BRDA:183,7,0,- +DA:184,0 +DA:186,0 +DA:187,0 +DA:194,0 +FN:194,NameService.extendExpiry +FNDA:0,NameService.extendExpiry +DA:195,0 +BRDA:195,8,0,- +DA:196,0 +DA:198,0 +BRDA:198,9,0,- +DA:199,0 +DA:202,0 +BRDA:202,10,0,- +BRDA:202,10,1,- +DA:203,0 +DA:205,0 +DA:210,0 +FN:210,NameService._isAlphanumeric +FNDA:0,NameService._isAlphanumeric +DA:211,0 +DA:212,0 +DA:213,0 +DA:214,0 +DA:216,0 +DA:220,0 +FN:220,NameService._isAuthorized +FNDA:0,NameService._isAuthorized +DA:221,0 +DA:228,0 +FN:228,NameService.setTextRecord +FNDA:0,NameService.setTextRecord +DA:229,0 +DA:231,0 +DA:232,0 +DA:233,0 +DA:235,0 +BRDA:235,12,0,- +DA:236,0 +DA:238,0 +BRDA:238,13,0,- +DA:239,0 +DA:241,0 +DA:242,0 +DA:249,0 +FN:249,NameService.getTextRecord +FNDA:0,NameService.getTextRecord +DA:250,0 +DA:251,0 +BRDA:251,14,0,- +DA:252,0 +DA:254,0 +FNF:15 +FNH:0 +LF:87 +LH:0 +BRF:16 +BRH:0 +end_of_record +TN: +SF:src/nameservice/PaymasterTest.sol +DA:23,0 +FN:23,PaymasterTest.register +FNDA:0,PaymasterTest.register +DA:24,0 +BRDA:24,0,0,- +DA:25,0 +DA:27,0 +BRDA:27,1,0,- +DA:28,0 +DA:31,0 +DA:32,0 +DA:33,0 +BRDA:33,2,0,- +BRDA:33,2,1,- +DA:34,0 +DA:36,0 +DA:39,0 +DA:43,0 +FN:43,PaymasterTest._isAlphanumeric +FNDA:0,PaymasterTest._isAlphanumeric +DA:44,0 +DA:45,0 +DA:46,0 +DA:47,0 +DA:49,0 +FNF:2 +FNH:0 +LF:17 +LH:0 +BRF:4 +BRH:0 +end_of_record +TN: +SF:src/nameservice/UniversalResolver.sol +DA:53,0 +FN:53,UniversalResolver.constructor +FNDA:0,UniversalResolver.constructor +DA:56,0 +DA:57,0 +DA:58,0 +DA:59,0 +DA:62,0 +DA:63,0 +DA:66,0 +FN:66,UniversalResolver.setUrl +FNDA:0,UniversalResolver.setUrl +DA:67,0 +DA:81,0 +FN:81,UniversalResolver._parseDnsDomain +FNDA:0,UniversalResolver._parseDnsDomain +DA:86,0 +DA:88,0 +DA:89,0 +DA:93,0 +DA:95,0 +DA:96,0 +DA:100,0 +DA:102,0 +DA:103,0 +DA:105,0 +DA:110,0 +FN:110,UniversalResolver.getStorageKey +FNDA:0,UniversalResolver.getStorageKey +DA:111,0 +DA:112,0 +DA:119,0 +FN:119,UniversalResolver.getTextRecordStorageKey +FNDA:0,UniversalResolver.getTextRecordStorageKey +DA:120,0 +DA:121,0 +DA:122,0 +DA:130,0 +FN:130,UniversalResolver.resolve +FNDA:0,UniversalResolver.resolve +DA:131,0 +DA:133,0 +BRDA:133,2,0,- +DA:134,0 +DA:137,0 +DA:138,0 +DA:140,0 +BRDA:140,3,0,- +BRDA:140,3,1,- +DA:141,0 +DA:142,0 +DA:143,0 +BRDA:143,4,0,- +BRDA:143,4,1,- +DA:144,0 +DA:145,0 +BRDA:145,5,0,- +DA:146,0 +DA:147,0 +BRDA:147,6,0,- +DA:148,0 +DA:152,0 +DA:155,0 +DA:156,0 +DA:158,0 +DA:159,0 +DA:161,0 +DA:167,0 +FN:167,UniversalResolver.resolveWithProof +FNDA:0,UniversalResolver.resolveWithProof +DA:168,0 +DA:169,0 +DA:172,0 +DA:174,0 +DA:176,0 +DA:178,0 +BRDA:178,7,0,- +DA:179,0 +DA:182,0 +BRDA:182,8,0,- +BRDA:182,8,1,- +DA:183,0 +DA:184,0 +BRDA:184,9,0,- +BRDA:184,9,1,- +DA:185,0 +DA:187,0 +DA:194,0 +FN:194,UniversalResolver.supportsInterface +FNDA:0,UniversalResolver.supportsInterface +DA:195,0 +DA:196,0 +FNF:8 +FNH:0 +LF:64 +LH:0 +BRF:12 +BRH:0 +end_of_record +TN: +SF:src/paymasters/BasePaymaster.sol +DA:33,0 +FN:33,BasePaymaster.constructor +FNDA:0,BasePaymaster.constructor +DA:34,0 +DA:35,0 +DA:38,0 +FN:38,BasePaymaster.validateAndPayForPaymasterTransaction +FNDA:0,BasePaymaster.validateAndPayForPaymasterTransaction +DA:43,0 +DA:46,0 +DA:48,0 +BRDA:48,0,0,- +DA:49,0 +DA:52,0 +DA:56,0 +DA:57,0 +DA:58,0 +DA:60,0 +BRDA:60,1,0,- +BRDA:60,1,1,- +DA:61,0 +DA:62,0 +BRDA:62,2,0,- +BRDA:62,2,1,- +DA:63,0 +DA:64,0 +DA:66,0 +DA:68,0 +DA:72,0 +DA:73,0 +BRDA:73,3,0,- +DA:74,0 +DA:77,0 +DA:80,0 +FN:80,BasePaymaster.postTransaction +FNDA:0,BasePaymaster.postTransaction +DA:88,0 +DA:93,0 +FN:93,BasePaymaster.withdraw +FNDA:0,BasePaymaster.withdraw +DA:94,0 +DA:96,0 +DA:97,0 +BRDA:97,4,0,- +DA:99,0 +DA:104,0 +FN:104,BasePaymaster._mustBeBootloader +FNDA:0,BasePaymaster._mustBeBootloader +DA:105,0 +BRDA:105,5,0,- +DA:106,0 +FNF:5 +FNH:0 +LF:33 +LH:0 +BRF:8 +BRH:0 +end_of_record +TN: +SF:src/paymasters/WhitelistPaymaster.sol +DA:22,0 +FN:22,WhitelistPaymaster.constructor +FNDA:0,WhitelistPaymaster.constructor +DA:23,0 +DA:26,0 +FN:26,WhitelistPaymaster.addWhitelistedContracts +FNDA:0,WhitelistPaymaster.addWhitelistedContracts +DA:27,0 +DA:29,0 +DA:30,0 +DA:33,0 +DA:36,0 +FN:36,WhitelistPaymaster.removeWhitelistedContracts +FNDA:0,WhitelistPaymaster.removeWhitelistedContracts +DA:37,0 +DA:39,0 +DA:40,0 +DA:43,0 +DA:46,0 +FN:46,WhitelistPaymaster.addWhitelistedUsers +FNDA:0,WhitelistPaymaster.addWhitelistedUsers +DA:47,0 +DA:49,0 +DA:50,0 +DA:53,0 +DA:56,0 +FN:56,WhitelistPaymaster.removeWhitelistedUsers +FNDA:0,WhitelistPaymaster.removeWhitelistedUsers +DA:57,0 +DA:59,0 +DA:60,0 +DA:63,0 +DA:66,0 +FN:66,WhitelistPaymaster._validateAndPayGeneralFlow +FNDA:0,WhitelistPaymaster._validateAndPayGeneralFlow +DA:67,0 +BRDA:67,0,0,- +DA:68,0 +DA:71,0 +BRDA:71,1,0,- +DA:72,0 +DA:76,0 +FN:76,WhitelistPaymaster._validateAndPayApprovalBasedFlow +FNDA:0,WhitelistPaymaster._validateAndPayApprovalBasedFlow +DA:81,0 +FNF:7 +FNH:0 +LF:29 +LH:0 +BRF:2 +BRH:0 +end_of_record +TN: +SF:src/swarms/FleetIdentity.sol +DA:227,140 +FN:227,FleetIdentity.constructor +FNDA:140,FleetIdentity.constructor +DA:228,140 +DA:229,140 +DA:243,0 +FN:243,FleetIdentity.registerFleetCountry +FNDA:0,FleetIdentity.registerFleetCountry +DA:248,0 +BRDA:248,0,0,- +DA:249,0 +BRDA:249,1,0,- +DA:250,0 +DA:251,0 +DA:252,0 +DA:267,1399 +FN:267,FleetIdentity.registerFleetLocal +FNDA:1399,FleetIdentity.registerFleetLocal +DA:272,1399 +BRDA:272,2,0,- +DA:273,1399 +BRDA:273,3,0,- +DA:274,1399 +BRDA:274,4,0,- +DA:275,1399 +DA:276,1399 +DA:277,1399 +DA:286,0 +FN:286,FleetIdentity.promote +FNDA:0,FleetIdentity.promote +DA:287,0 +DA:294,0 +FN:294,FleetIdentity.reassignTier +FNDA:0,FleetIdentity.reassignTier +DA:295,0 +DA:296,0 +BRDA:296,5,0,- +DA:297,0 +BRDA:297,6,0,- +BRDA:297,6,1,- +DA:298,0 +DA:300,0 +DA:316,0 +FN:316,FleetIdentity.setOperator +FNDA:0,FleetIdentity.setOperator +DA:318,0 +BRDA:318,7,0,- +DA:321,0 +BRDA:321,8,0,- +DA:322,0 +DA:325,0 +DA:328,0 +DA:329,0 +DA:332,0 +DA:335,0 +DA:338,0 +BRDA:338,9,0,- +DA:340,0 +DA:342,0 +DA:345,0 +DA:367,20 +FN:367,FleetIdentity.burn +FNDA:20,FleetIdentity.burn +DA:368,20 +DA:370,20 +DA:371,20 +DA:372,20 +DA:373,20 +DA:374,20 +DA:376,20 +BRDA:376,10,0,10 +BRDA:376,10,1,- +DA:378,10 +BRDA:378,11,0,- +DA:380,10 +DA:381,10 +DA:382,10 +DA:384,10 +DA:387,10 +BRDA:387,12,0,- +DA:388,0 +DA:391,10 +DA:392,10 +DA:395,10 +DA:397,10 +DA:398,10 +DA:400,10 +BRDA:400,13,0,10 +BRDA:400,13,1,- +DA:402,10 +DA:403,10 +DA:404,10 +DA:408,0 +DA:412,10 +DA:414,10 +DA:427,0 +FN:427,FleetIdentity.claimUuid +FNDA:0,FleetIdentity.claimUuid +DA:428,0 +BRDA:428,14,0,- +DA:429,0 +BRDA:429,15,0,- +DA:432,0 +DA:433,0 +DA:434,0 +DA:436,0 +DA:439,0 +DA:440,0 +DA:442,0 +DA:444,0 +DA:454,1409 +FN:454,FleetIdentity.tierBond +FNDA:1409,FleetIdentity.tierBond +DA:455,1409 +DA:456,1409 +DA:462,0 +FN:462,FleetIdentity.localInclusionHint +FNDA:0,FleetIdentity.localInclusionHint +DA:467,0 +BRDA:467,16,0,- +DA:468,0 +BRDA:468,17,0,- +DA:469,0 +DA:470,0 +DA:478,0 +FN:478,FleetIdentity.countryInclusionHint +FNDA:0,FleetIdentity.countryInclusionHint +DA:479,0 +BRDA:479,18,0,- +DA:482,0 +DA:485,0 +DA:486,0 +DA:487,0 +DA:488,0 +DA:489,0 +DA:490,0 +BRDA:490,19,0,- +DA:492,0 +DA:496,0 +FN:496,FleetIdentity.highestActiveTier +FNDA:0,FleetIdentity.highestActiveTier +DA:497,0 +DA:498,0 +DA:499,0 +DA:503,0 +FN:503,FleetIdentity.tierMemberCount +FNDA:0,FleetIdentity.tierMemberCount +DA:504,0 +DA:508,0 +FN:508,FleetIdentity.getTierMembers +FNDA:0,FleetIdentity.getTierMembers +DA:509,0 +DA:513,0 +FN:513,FleetIdentity.getTierUuids +FNDA:0,FleetIdentity.getTierUuids +DA:514,0 +DA:515,0 +DA:516,0 +DA:517,0 +DA:522,20 +FN:522,FleetIdentity.tokenUuid +FNDA:20,FleetIdentity.tokenUuid +DA:523,20 +DA:527,1449 +FN:527,FleetIdentity.tokenRegion +FNDA:1449,FleetIdentity.tokenRegion +DA:528,1449 +DA:532,1399 +FN:532,FleetIdentity.computeTokenId +FNDA:1399,FleetIdentity.computeTokenId +DA:533,1399 +DA:537,0 +FN:537,FleetIdentity.bonds +FNDA:0,FleetIdentity.bonds +DA:538,0 +DA:539,0 +DA:540,0 +DA:541,0 +DA:545,0 +FN:545,FleetIdentity.isOwnedOnly +FNDA:0,FleetIdentity.isOwnedOnly +DA:546,0 +DA:554,20 +FN:554,FleetIdentity.operatorOf +FNDA:20,FleetIdentity.operatorOf +DA:555,20 +DA:556,20 +BRDA:556,23,0,20 +DA:557,20 +DA:582,0 +FN:582,FleetIdentity.buildHighestBondedUuidBundle +FNDA:0,FleetIdentity.buildHighestBondedUuidBundle +DA:587,0 +BRDA:587,24,0,- +DA:588,0 +BRDA:588,25,0,- +DA:590,0 +DA:591,0 +DA:593,0 +DA:610,0 +FN:610,FleetIdentity.buildCountryOnlyBundle +FNDA:0,FleetIdentity.buildCountryOnlyBundle +DA:615,0 +BRDA:615,26,0,- +DA:617,0 +DA:619,0 +DA:621,0 +DA:633,0 +FN:633,FleetIdentity._buildHighestBondedUuidBundle +FNDA:0,FleetIdentity._buildHighestBondedUuidBundle +DA:638,0 +DA:640,0 +DA:643,0 +DA:644,0 +DA:647,0 +DA:650,0 +DA:655,0 +DA:662,0 +FN:662,FleetIdentity._appendTierUuids +FNDA:0,FleetIdentity._appendTierUuids +DA:668,0 +DA:669,0 +DA:670,0 +DA:671,0 +DA:673,0 +DA:674,0 +DA:675,0 +DA:677,0 +DA:685,0 +FN:685,FleetIdentity.getActiveCountries +FNDA:0,FleetIdentity.getActiveCountries +DA:686,0 +DA:691,0 +FN:691,FleetIdentity.getActiveAdminAreas +FNDA:0,FleetIdentity.getActiveAdminAreas +DA:693,0 +DA:694,0 +DA:695,0 +DA:696,0 +DA:700,0 +DA:701,0 +DA:702,0 +DA:703,0 +DA:704,0 +DA:705,0 +DA:706,0 +DA:709,0 +DA:714,0 +FN:714,FleetIdentity.getCountryAdminAreas +FNDA:0,FleetIdentity.getCountryAdminAreas +DA:715,0 +DA:720,1399 +FN:720,FleetIdentity.makeAdminRegion +FNDA:1399,FleetIdentity.makeAdminRegion +DA:721,1399 +DA:731,1403 +FN:731,FleetIdentity._countryFromRegion +FNDA:1403,FleetIdentity._countryFromRegion +DA:732,1403 +DA:736,0 +FN:736,FleetIdentity._adminFromRegion +FNDA:0,FleetIdentity._adminFromRegion +DA:737,0 +DA:742,2818 +FN:742,FleetIdentity._isCountryRegion +FNDA:2818,FleetIdentity._isCountryRegion +DA:743,2818 +DA:749,1399 +FN:749,FleetIdentity._pullBond +FNDA:1399,FleetIdentity._pullBond +DA:750,1399 +BRDA:750,27,0,1399 +DA:751,1399 +DA:756,20 +FN:756,FleetIdentity._refundBond +FNDA:20,FleetIdentity._refundBond +DA:757,20 +BRDA:757,28,0,20 +DA:758,20 +DA:765,10 +FN:765,FleetIdentity._clearUuidOwnership +FNDA:10,FleetIdentity._clearUuidOwnership +DA:766,10 +DA:767,10 +DA:768,10 +DA:769,10 +DA:770,10 +DA:775,0 +FN:775,FleetIdentity._decrementUuidCount +FNDA:0,FleetIdentity._decrementUuidCount +DA:776,0 +DA:777,0 +BRDA:777,29,0,- +BRDA:777,29,1,- +DA:778,0 +DA:780,0 +DA:788,10 +FN:788,FleetIdentity._cleanupFleetFromTier +FNDA:10,FleetIdentity._cleanupFleetFromTier +DA:789,10 +DA:790,10 +DA:791,10 +DA:792,10 +DA:793,10 +DA:800,1399 +FN:800,FleetIdentity._mintFleetToken +FNDA:1399,FleetIdentity._mintFleetToken +DA:801,1399 +DA:802,1399 +DA:803,1399 +DA:804,1399 +DA:805,1399 +DA:810,0 +FN:810,FleetIdentity._mintFleetTokenTo +FNDA:0,FleetIdentity._mintFleetTokenTo +DA:811,0 +DA:812,0 +DA:813,0 +DA:814,0 +DA:815,0 +DA:824,1399 +FN:824,FleetIdentity._register +FNDA:1399,FleetIdentity._register +DA:825,1399 +DA:826,1399 +DA:827,1399 +DA:828,0 +DA:830,1399 +BRDA:830,30,0,- +BRDA:830,30,1,- +DA:832,0 +DA:833,0 +BRDA:833,31,0,- +DA:834,0 +DA:836,0 +DA:837,0 +DA:838,0 +DA:840,0 +DA:843,0 +DA:845,0 +DA:846,1399 +BRDA:846,32,0,1399 +BRDA:846,32,1,- +DA:848,1399 +DA:849,1399 +DA:850,1399 +DA:851,1399 +DA:854,1399 +DA:857,1399 +DA:859,1399 +DA:862,0 +DA:863,0 +BRDA:863,33,0,- +DA:864,0 +BRDA:864,34,0,- +DA:865,0 +DA:867,0 +DA:868,0 +DA:870,0 +DA:873,0 +DA:875,0 +DA:880,0 +FN:880,FleetIdentity._promote +FNDA:0,FleetIdentity._promote +DA:881,0 +DA:882,0 +DA:883,0 +BRDA:883,35,0,- +DA:885,0 +DA:886,0 +DA:887,0 +BRDA:887,36,0,- +DA:888,0 +BRDA:888,37,0,- +DA:889,0 +BRDA:889,38,0,- +DA:891,0 +DA:892,0 +DA:893,0 +DA:894,0 +DA:897,0 +DA:898,0 +DA:899,0 +DA:900,0 +DA:903,0 +DA:905,0 +DA:909,0 +FN:909,FleetIdentity._demote +FNDA:0,FleetIdentity._demote +DA:910,0 +DA:911,0 +DA:912,0 +BRDA:912,39,0,- +DA:914,0 +DA:915,0 +DA:916,0 +BRDA:916,40,0,- +DA:917,0 +BRDA:917,41,0,- +DA:919,0 +DA:920,0 +DA:921,0 +DA:922,0 +DA:925,0 +DA:926,0 +DA:927,0 +DA:928,0 +DA:929,0 +DA:932,0 +DA:934,0 +DA:938,1399 +FN:938,FleetIdentity._validateExplicitTier +FNDA:1399,FleetIdentity._validateExplicitTier +DA:939,1399 +BRDA:939,42,0,- +DA:940,1399 +BRDA:940,43,0,- +DA:946,0 +FN:946,FleetIdentity._findMaxTierIndex +FNDA:0,FleetIdentity._findMaxTierIndex +DA:951,0 +DA:952,0 +DA:954,0 +DA:955,0 +BRDA:955,44,0,- +DA:956,0 +DA:976,0 +FN:976,FleetIdentity._findCheapestInclusionTier +FNDA:0,FleetIdentity._findCheapestInclusionTier +DA:981,0 +DA:982,0 +DA:983,0 +DA:985,0 +DA:990,0 +DA:991,0 +DA:992,0 +DA:994,0 +BRDA:994,45,0,- +DA:995,0 +DA:1000,0 +DA:1001,0 +DA:1002,0 +DA:1003,0 +DA:1007,0 +BRDA:1007,46,0,- +DA:1008,0 +DA:1011,0 +DA:1016,1399 +FN:1016,FleetIdentity._addToTier +FNDA:1399,FleetIdentity._addToTier +DA:1017,1399 +DA:1018,1399 +DA:1021,1399 +BRDA:1021,47,0,1393 +DA:1022,1393 +DA:1027,10 +FN:1027,FleetIdentity._removeFromTier +FNDA:10,FleetIdentity._removeFromTier +DA:1028,10 +DA:1029,10 +DA:1030,10 +DA:1032,10 +BRDA:1032,48,0,- +DA:1033,0 +DA:1034,0 +DA:1035,0 +DA:1037,10 +DA:1041,10 +FN:1041,FleetIdentity._trimTierCount +FNDA:10,FleetIdentity._trimTierCount +DA:1042,10 +DA:1043,20 +DA:1044,10 +DA:1046,10 +DA:1052,1399 +FN:1052,FleetIdentity._addToRegionIndex +FNDA:1399,FleetIdentity._addToRegionIndex +DA:1053,1399 +BRDA:1053,49,0,- +BRDA:1053,49,1,1393 +DA:1055,0 +DA:1056,0 +BRDA:1056,50,0,- +DA:1057,0 +DA:1058,0 +DA:1062,1399 +BRDA:1062,51,0,1393 +DA:1063,1393 +DA:1065,1393 +BRDA:1065,52,0,1393 +DA:1066,1393 +DA:1067,1393 +DA:1069,1393 +DA:1070,1393 +DA:1076,10 +FN:1076,FleetIdentity._removeFromRegionIndex +FNDA:10,FleetIdentity._removeFromRegionIndex +DA:1077,10 +DA:1079,10 +BRDA:1079,54,0,- +BRDA:1079,54,1,- +DA:1080,0 +DA:1081,0 +DA:1082,0 +BRDA:1082,55,0,- +DA:1084,0 +DA:1086,0 +DA:1087,0 +DA:1088,0 +BRDA:1088,57,0,- +DA:1089,0 +DA:1090,0 +DA:1091,0 +DA:1093,0 +DA:1094,0 +DA:1098,10 +DA:1099,10 +BRDA:1099,58,0,10 +DA:1100,10 +DA:1101,10 +DA:1102,10 +DA:1103,10 +DA:1104,10 +BRDA:1104,59,0,- +DA:1105,0 +DA:1106,0 +DA:1107,0 +DA:1109,10 +DA:1110,10 +DA:1113,10 +BRDA:1113,60,0,10 +DA:1114,10 +DA:1115,10 +BRDA:1115,61,0,10 +DA:1116,10 +DA:1117,10 +DA:1118,10 +BRDA:1118,62,0,- +DA:1119,0 +DA:1120,0 +DA:1121,0 +DA:1123,10 +DA:1124,10 +DA:1135,1429 +FN:1135,FleetIdentity._update +FNDA:1429,FleetIdentity._update +DA:1136,1429 +DA:1140,1429 +DA:1141,1429 +BRDA:1141,63,0,- +DA:1142,0 +DA:1145,1429 +DA:1148,0 +FN:1148,FleetIdentity._increaseBalance +FNDA:0,FleetIdentity._increaseBalance +DA:1149,0 +DA:1152,0 +FN:1152,FleetIdentity.supportsInterface +FNDA:0,FleetIdentity.supportsInterface +DA:1153,0 +FNF:53 +FNH:24 +LF:387 +LH:144 +BRF:67 +BRH:13 +end_of_record +TN: +SF:src/swarms/ServiceProvider.sol +DA:27,1415 +FN:27,ServiceProvider.registerProvider +FNDA:1415,ServiceProvider.registerProvider +DA:28,1415 +BRDA:28,0,0,- +DA:29,0 +DA:32,1415 +DA:34,1415 +DA:36,1415 +DA:38,1415 +DA:43,15 +FN:43,ServiceProvider.burn +FNDA:15,ServiceProvider.burn +DA:44,15 +BRDA:44,1,0,- +DA:45,0 +DA:48,15 +DA:50,15 +DA:52,15 +FNF:2 +FNH:2 +LF:13 +LH:11 +BRF:2 +BRH:0 +end_of_record +TN: +SF:src/swarms/SwarmRegistryL1.sol +DA:83,318 +FN:83,SwarmRegistryL1.computeSwarmId +FNDA:318,SwarmRegistryL1.computeSwarmId +DA:88,318 +DA:91,69 +FN:91,SwarmRegistryL1.constructor +FNDA:69,SwarmRegistryL1.constructor +DA:92,69 +BRDA:92,0,0,3 +DA:93,3 +DA:95,66 +DA:96,66 +DA:106,578 +FN:106,SwarmRegistryL1.registerSwarm +FNDA:578,SwarmRegistryL1.registerSwarm +DA:113,578 +BRDA:113,1,0,1 +DA:114,1 +DA:116,577 +BRDA:116,2,0,258 +DA:117,258 +DA:119,319 +BRDA:119,3,0,2 +DA:120,2 +DA:124,317 +BRDA:124,4,0,1 +DA:125,1 +DA:127,316 +BRDA:127,5,0,- +DA:128,0 +DA:131,315 +DA:133,315 +BRDA:133,6,0,1 +DA:134,1 +DA:137,314 +DA:138,314 +DA:139,314 +DA:140,314 +DA:141,314 +DA:142,314 +DA:144,314 +DA:145,314 +DA:147,314 +DA:149,314 +DA:154,8 +FN:154,SwarmRegistryL1.acceptSwarm +FNDA:8,SwarmRegistryL1.acceptSwarm +DA:155,8 +DA:156,8 +BRDA:156,7,0,1 +DA:158,7 +DA:159,7 +BRDA:159,8,0,2 +DA:161,5 +BRDA:161,9,0,1 +DA:162,1 +DA:164,4 +DA:165,4 +DA:170,4 +FN:170,SwarmRegistryL1.rejectSwarm +FNDA:4,SwarmRegistryL1.rejectSwarm +DA:171,4 +DA:172,4 +BRDA:172,10,0,- +DA:174,4 +DA:175,4 +BRDA:175,11,0,1 +DA:177,3 +BRDA:177,12,0,1 +DA:178,1 +DA:180,2 +DA:181,2 +DA:187,7 +FN:187,SwarmRegistryL1.updateSwarmFilter +FNDA:7,SwarmRegistryL1.updateSwarmFilter +DA:188,7 +DA:189,7 +BRDA:189,13,0,1 +DA:190,1 +DA:192,6 +BRDA:192,14,0,1 +DA:193,1 +DA:195,5 +BRDA:195,15,0,2 +DA:196,2 +DA:199,3 +DA:201,3 +DA:203,3 +DA:209,4 +FN:209,SwarmRegistryL1.updateSwarmProvider +FNDA:4,SwarmRegistryL1.updateSwarmProvider +DA:210,4 +DA:211,4 +BRDA:211,16,0,1 +DA:212,1 +DA:214,3 +BRDA:214,17,0,1 +DA:215,1 +DA:217,2 +BRDA:217,18,0,- +DA:218,0 +DA:221,1 +DA:223,1 +DA:225,1 +DA:227,1 +DA:232,7 +FN:232,SwarmRegistryL1.deleteSwarm +FNDA:7,SwarmRegistryL1.deleteSwarm +DA:233,7 +DA:234,7 +BRDA:234,19,0,1 +DA:235,1 +DA:237,6 +BRDA:237,20,0,1 +DA:238,1 +DA:241,5 +DA:243,5 +DA:245,5 +DA:247,5 +DA:254,27 +FN:254,SwarmRegistryL1.isSwarmValid +FNDA:27,SwarmRegistryL1.isSwarmValid +DA:255,27 +DA:256,27 +BRDA:256,21,0,1 +DA:259,26 +DA:261,26 +BRDA:261,22,0,26 +DA:262,19 +DA:263,7 +BRDA:263,22,1,7 +DA:264,7 +DA:270,6 +FN:270,SwarmRegistryL1.purgeOrphanedSwarm +FNDA:6,SwarmRegistryL1.purgeOrphanedSwarm +DA:271,6 +DA:272,6 +BRDA:272,23,0,1 +DA:274,5 +DA:275,5 +BRDA:275,24,0,1 +DA:277,4 +DA:279,4 +DA:281,4 +DA:283,4 +DA:290,7 +FN:290,SwarmRegistryL1.checkMembership +FNDA:7,SwarmRegistryL1.checkMembership +DA:291,7 +DA:292,7 +BRDA:292,25,0,1 +DA:293,1 +DA:297,6 +DA:298,6 +BRDA:298,26,0,1 +DA:300,5 +DA:301,5 +DA:303,0 +DA:307,5 +BRDA:307,27,0,5 +DA:309,5 +DA:314,5 +DA:315,5 +DA:317,0 +DA:319,4 +DA:320,4 +DA:321,4 +DA:323,4 +DA:324,4 +DA:326,4 +DA:327,4 +DA:328,4 +DA:330,4 +DA:336,9 +FN:336,SwarmRegistryL1._removeFromUuidSwarms +FNDA:9,SwarmRegistryL1._removeFromUuidSwarms +DA:337,9 +DA:338,9 +DA:339,9 +DA:341,9 +DA:342,9 +DA:343,9 +DA:344,9 +DA:353,12 +FN:353,SwarmRegistryL1._readFingerprint +FNDA:12,SwarmRegistryL1._readFingerprint +DA:354,12 +DA:355,12 +DA:356,12 +DA:359,12 +DA:362,12 +DA:363,12 +DA:364,24 +DA:366,24 +DA:370,12 +DA:371,12 +DA:372,12 +DA:374,12 +FNF:13 +FNH:13 +LF:143 +LH:139 +BRF:29 +BRH:26 +end_of_record +TN: +SF:src/swarms/SwarmRegistryUniversal.sol +DA:89,581 +FN:89,SwarmRegistryUniversal.computeSwarmId +FNDA:581,SwarmRegistryUniversal.computeSwarmId +DA:90,581 +DA:93,77 +FN:93,SwarmRegistryUniversal.constructor +FNDA:77,SwarmRegistryUniversal.constructor +DA:94,77 +BRDA:94,0,0,3 +DA:95,3 +DA:97,74 +DA:98,74 +DA:108,841 +FN:108,SwarmRegistryUniversal.registerSwarm +FNDA:841,SwarmRegistryUniversal.registerSwarm +DA:115,841 +BRDA:115,1,0,1 +DA:116,1 +DA:118,840 +BRDA:118,2,0,258 +DA:119,258 +DA:121,582 +BRDA:121,3,0,1 +DA:122,1 +DA:124,581 +BRDA:124,4,0,1 +DA:125,1 +DA:129,580 +BRDA:129,5,0,1 +DA:130,1 +DA:132,579 +BRDA:132,6,0,- +DA:133,0 +DA:136,578 +DA:138,578 +BRDA:138,7,0,1 +DA:139,1 +DA:142,577 +DA:143,577 +DA:144,577 +DA:145,577 +DA:146,577 +DA:147,577 +DA:148,577 +DA:150,577 +DA:152,577 +DA:153,577 +DA:155,577 +DA:160,10 +FN:160,SwarmRegistryUniversal.acceptSwarm +FNDA:10,SwarmRegistryUniversal.acceptSwarm +DA:161,10 +DA:162,10 +BRDA:162,8,0,1 +DA:164,9 +DA:165,9 +BRDA:165,9,0,2 +DA:167,7 +BRDA:167,10,0,2 +DA:168,2 +DA:170,5 +DA:171,5 +DA:176,5 +FN:176,SwarmRegistryUniversal.rejectSwarm +FNDA:5,SwarmRegistryUniversal.rejectSwarm +DA:177,5 +DA:178,5 +BRDA:178,11,0,- +DA:180,5 +DA:181,5 +BRDA:181,12,0,1 +DA:183,4 +BRDA:183,13,0,1 +DA:184,1 +DA:186,3 +DA:187,3 +DA:193,7 +FN:193,SwarmRegistryUniversal.updateSwarmFilter +FNDA:7,SwarmRegistryUniversal.updateSwarmFilter +DA:194,7 +DA:195,7 +BRDA:195,14,0,1 +DA:196,1 +DA:198,6 +BRDA:198,15,0,1 +DA:199,1 +DA:201,5 +BRDA:201,16,0,1 +DA:202,1 +DA:204,4 +BRDA:204,17,0,1 +DA:205,1 +DA:208,3 +DA:209,3 +DA:210,3 +DA:212,3 +DA:218,4 +FN:218,SwarmRegistryUniversal.updateSwarmProvider +FNDA:4,SwarmRegistryUniversal.updateSwarmProvider +DA:219,4 +DA:220,4 +BRDA:220,18,0,1 +DA:221,1 +DA:223,3 +BRDA:223,19,0,1 +DA:224,1 +DA:226,2 +BRDA:226,20,0,- +DA:227,0 +DA:230,1 +DA:233,1 +DA:234,1 +DA:236,1 +DA:241,8 +FN:241,SwarmRegistryUniversal.deleteSwarm +FNDA:8,SwarmRegistryUniversal.deleteSwarm +DA:242,8 +DA:243,8 +BRDA:243,21,0,1 +DA:244,1 +DA:246,7 +BRDA:246,22,0,1 +DA:247,1 +DA:250,6 +DA:252,6 +DA:254,6 +DA:255,6 +DA:257,6 +DA:264,30 +FN:264,SwarmRegistryUniversal.isSwarmValid +FNDA:30,SwarmRegistryUniversal.isSwarmValid +DA:265,30 +DA:266,30 +BRDA:266,23,0,1 +DA:269,29 +DA:271,29 +BRDA:271,24,0,29 +DA:272,21 +DA:273,8 +BRDA:273,24,1,8 +DA:274,8 +DA:280,7 +FN:280,SwarmRegistryUniversal.purgeOrphanedSwarm +FNDA:7,SwarmRegistryUniversal.purgeOrphanedSwarm +DA:281,7 +DA:282,7 +BRDA:282,25,0,1 +DA:284,6 +DA:285,6 +BRDA:285,26,0,1 +DA:287,5 +DA:289,5 +DA:291,5 +DA:292,5 +DA:294,5 +DA:301,6 +FN:301,SwarmRegistryUniversal.checkMembership +FNDA:6,SwarmRegistryUniversal.checkMembership +DA:302,6 +DA:303,6 +BRDA:303,27,0,1 +DA:304,1 +DA:308,5 +DA:309,5 +BRDA:309,28,0,1 +DA:311,4 +DA:312,4 +DA:315,4 +DA:316,4 +DA:319,4 +DA:320,4 +DA:321,4 +DA:323,4 +DA:324,4 +DA:327,4 +DA:328,4 +DA:329,4 +DA:331,4 +DA:337,3 +FN:337,SwarmRegistryUniversal.getFilterData +FNDA:3,SwarmRegistryUniversal.getFilterData +DA:338,3 +BRDA:338,30,0,1 +DA:339,1 +DA:341,2 +DA:347,11 +FN:347,SwarmRegistryUniversal._removeFromUuidSwarms +FNDA:11,SwarmRegistryUniversal._removeFromUuidSwarms +DA:348,11 +DA:349,11 +DA:350,11 +DA:352,11 +DA:353,11 +DA:354,11 +DA:355,11 +DA:364,12 +FN:364,SwarmRegistryUniversal._readFingerprint +FNDA:12,SwarmRegistryUniversal._readFingerprint +DA:365,12 +DA:366,12 +DA:367,12 +DA:370,12 +DA:371,12 +DA:372,24 +DA:374,24 +DA:379,12 +DA:380,12 +DA:381,12 +DA:383,12 +FNF:14 +FNH:14 +LF:150 +LH:148 +BRF:31 +BRH:28 +end_of_record +TN: +SF:test/FleetIdentity.t.sol +DA:12,0 +FN:12,MockERC20.mint +FNDA:0,MockERC20.mint +DA:13,0 +DA:23,0 +FN:23,BadERC20.mint +FNDA:0,BadERC20.mint +DA:24,0 +DA:27,0 +FN:27,BadERC20.setFail +FNDA:0,BadERC20.setFail +DA:28,0 +DA:31,0 +FN:31,BadERC20.transfer +FNDA:0,BadERC20.transfer +DA:33,0 +DA:36,0 +FN:36,BadERC20.transferFrom +FNDA:0,BadERC20.transferFrom +DA:38,0 +FNF:5 +FNH:0 +LF:10 +LH:0 +BRF:0 +BRH:0 +end_of_record +TN: +SF:test/FleetIdentityFairness.t.sol +DA:12,0 +FN:12,MockERC20Fairness.mint +FNDA:0,MockERC20Fairness.mint +DA:13,0 +FNF:1 +FNH:0 +LF:2 +LH:0 +BRF:0 +BRH:0 +end_of_record +TN: +SF:test/Grants.t.sol +DA:9,0 +FN:9,MockToken.constructor +FNDA:0,MockToken.constructor +DA:10,0 +FNF:1 +FNH:0 +LF:2 +LH:0 +BRF:0 +BRH:0 +end_of_record +TN: +SF:test/Payment.t.sol +DA:12,0 +FN:12,MockToken.constructor +FNDA:0,MockToken.constructor +DA:13,0 +FNF:1 +FNH:0 +LF:2 +LH:0 +BRF:0 +BRH:0 +end_of_record +TN: +SF:test/QuotaControl.t.sol +DA:14,0 +FN:14,TestableQuotaControl.exposeCheckedResetClaimed +FNDA:0,TestableQuotaControl.exposeCheckedResetClaimed +DA:15,0 +DA:18,0 +FN:18,TestableQuotaControl.exposeCheckedUpdateClaimed +FNDA:0,TestableQuotaControl.exposeCheckedUpdateClaimed +DA:19,0 +FNF:2 +FNH:0 +LF:4 +LH:0 +BRF:0 +BRH:0 +end_of_record +TN: +SF:test/SwarmRegistryL1.t.sol +DA:13,66 +FN:13,MockBondTokenL1.mint +FNDA:66,MockBondTokenL1.mint +DA:14,66 +FNF:1 +FNH:1 +LF:2 +LH:2 +BRF:0 +BRH:0 +end_of_record +TN: +SF:test/SwarmRegistryUniversal.t.sol +DA:13,74 +FN:13,MockBondTokenUniv.mint +FNDA:74,MockBondTokenUniv.mint +DA:14,74 +FNF:1 +FNH:1 +LF:2 +LH:2 +BRF:0 +BRH:0 +end_of_record +TN: +SF:test/__helpers__/AccessControlUtils.sol +DA:9,0 +FN:9,AccessControlUtils.expectRevert_AccessControlUnauthorizedAccount +FNDA:0,AccessControlUtils.expectRevert_AccessControlUnauthorizedAccount +DA:10,0 +FNF:1 +FNH:0 +LF:2 +LH:0 +BRF:0 +BRH:0 +end_of_record +TN: +SF:test/bridge/L1Bridge.t.sol +DA:31,0 +FN:31,MockMailbox.setL1ToL2Failed +FNDA:0,MockMailbox.setL1ToL2Failed +DA:32,0 +DA:35,0 +FN:35,MockMailbox.setInclusion +FNDA:0,MockMailbox.setInclusion +DA:36,0 +DA:39,0 +FN:39,MockMailbox.setBaseCostReturn +FNDA:0,MockMailbox.setBaseCostReturn +DA:40,0 +DA:43,0 +FN:43,MockMailbox.expectBaseCostParams +FNDA:0,MockMailbox.expectBaseCostParams +DA:44,0 +DA:45,0 +DA:46,0 +DA:50,0 +FN:50,MockMailbox.requestL2Transaction +FNDA:0,MockMailbox.requestL2Transaction +DA:59,0 +DA:60,0 +DA:63,0 +DA:66,0 +FN:66,MockMailbox.proveL1ToL2TransactionStatus +FNDA:0,MockMailbox.proveL1ToL2TransactionStatus +DA:74,0 +BRDA:74,0,0,- +DA:75,0 +DA:77,0 +DA:80,0 +FN:80,MockMailbox.proveL2MessageInclusion +FNDA:0,MockMailbox.proveL2MessageInclusion +DA:86,0 +DA:89,0 +FN:89,MockMailbox.l2TransactionBaseCost +FNDA:0,MockMailbox.l2TransactionBaseCost +DA:95,0 +BRDA:95,1,0,- +BRDA:95,1,1,- +DA:96,0 +BRDA:96,2,0,- +BRDA:96,2,1,- +DA:97,0 +BRDA:97,3,0,- +BRDA:97,3,1,- +DA:98,0 +FNF:8 +FNH:0 +LF:25 +LH:0 +BRF:7 +BRH:0 +end_of_record +TN: +SF:test/bridge/MigrationNFT.t.sol +DA:12,0 +FN:12,MigrationNFTTestUtils.bridgeTokens +FNDA:0,MigrationNFTTestUtils.bridgeTokens +DA:20,0 +DA:21,0 +DA:22,0 +FNF:1 +FNH:0 +LF:4 +LH:0 +BRF:0 +BRH:0 +end_of_record +TN: +SF:test/contentsign/BaseContentSign.t.sol +DA:13,0 +FN:13,MockContentSign.setWhitelisted +FNDA:0,MockContentSign.setWhitelisted +DA:14,0 +DA:17,0 +FN:17,MockContentSign._userIsWhitelisted +FNDA:0,MockContentSign._userIsWhitelisted +DA:18,0 +FNF:2 +FNH:0 +LF:4 +LH:0 +BRF:0 +BRH:0 +end_of_record +TN: +SF:test/contentsign/ClickBounty.t.sol +DA:13,0 +FN:13,MockERC20.constructor +FNDA:0,MockERC20.constructor +DA:14,0 +DA:21,0 +FN:21,MockContentSign._userIsWhitelisted +FNDA:0,MockContentSign._userIsWhitelisted +DA:22,0 +FNF:2 +FNH:0 +LF:4 +LH:0 +BRF:0 +BRH:0 +end_of_record +TN: +SF:test/contentsign/PaymentMiddleware.t.sol +DA:16,0 +FN:16,MockToken.mint +FNDA:0,MockToken.mint +DA:17,0 +DA:24,0 +FN:24,MockWhitelist.mint +FNDA:0,MockWhitelist.mint +DA:25,0 +DA:32,0 +FN:32,MockContentSign._userIsWhitelisted +FNDA:0,MockContentSign._userIsWhitelisted +DA:33,0 +FNF:3 +FNH:0 +LF:6 +LH:0 +BRF:0 +BRH:0 +end_of_record +TN: +SF:test/paymasters/BasePaymaster.t.sol +DA:13,0 +FN:13,MockPaymaster._validateAndPayGeneralFlow +FNDA:0,MockPaymaster._validateAndPayGeneralFlow +DA:15,0 +DA:18,0 +FN:18,MockPaymaster._validateAndPayApprovalBasedFlow +FNDA:0,MockPaymaster._validateAndPayApprovalBasedFlow +DA:23,0 +FNF:2 +FNH:0 +LF:4 +LH:0 +BRF:0 +BRH:0 +end_of_record +TN: +SF:test/paymasters/WhitelistPaymaster.t.sol +DA:14,0 +FN:14,MockWhitelistPaymaster.mock_validateAndPayGeneralFlow +FNDA:0,MockWhitelistPaymaster.mock_validateAndPayGeneralFlow +DA:15,0 +DA:18,0 +FN:18,MockWhitelistPaymaster.mock_validateAndPayApprovalBasedFlow +FNDA:0,MockWhitelistPaymaster.mock_validateAndPayApprovalBasedFlow +DA:26,0 +FNF:2 +FNH:0 +LF:4 +LH:0 +BRF:0 +BRH:0 +end_of_record diff --git a/coverage2.lcov b/coverage2.lcov new file mode 100644 index 00000000..35bc1e0f --- /dev/null +++ b/coverage2.lcov @@ -0,0 +1,3337 @@ +TN: +SF:script/CheckBridge.s.sol +DA:13,0 +FN:13,CheckBridge.setUp +FNDA:0,CheckBridge.setUp +DA:14,0 +DA:15,0 +DA:18,0 +FN:18,CheckBridge.run +FNDA:0,CheckBridge.run +DA:19,0 +DA:20,0 +DA:22,0 +DA:23,0 +DA:25,0 +BRDA:25,0,0,- +BRDA:25,0,1,- +DA:26,0 +DA:28,0 +DA:30,0 +BRDA:30,1,0,- +BRDA:30,1,1,- +DA:31,0 +DA:32,0 +BRDA:32,2,0,- +BRDA:32,2,1,- +DA:33,0 +DA:35,0 +DA:38,0 +FNF:2 +FNH:0 +LF:17 +LH:0 +BRF:6 +BRH:0 +end_of_record +TN: +SF:script/ContentSignWhitelist.s.sol +DA:13,0 +FN:13,ContentSignWhitelist.setUp +FNDA:0,ContentSignWhitelist.setUp +DA:14,0 +DA:15,0 +DA:18,0 +FN:18,ContentSignWhitelist.run +FNDA:0,ContentSignWhitelist.run +DA:19,0 +DA:21,0 +BRDA:21,0,0,- +BRDA:21,0,1,- +DA:22,0 +DA:24,0 +DA:26,0 +DA:27,0 +DA:28,0 +FNF:2 +FNH:0 +LF:11 +LH:0 +BRF:2 +BRH:0 +end_of_record +TN: +SF:script/DeployClick.s.sol +DA:14,0 +FN:14,DeployClick.setUp +FNDA:0,DeployClick.setUp +DA:15,0 +DA:16,0 +DA:18,0 +DA:19,0 +DA:22,0 +FN:22,DeployClick.run +FNDA:0,DeployClick.run +DA:23,0 +DA:25,0 +DA:26,0 +DA:28,0 +DA:29,0 +DA:32,0 +DA:35,0 +DA:37,0 +DA:39,0 +DA:40,0 +DA:41,0 +FNF:2 +FNH:0 +LF:17 +LH:0 +BRF:0 +BRH:0 +end_of_record +TN: +SF:script/DeployContentSignEnterprise.s.sol +DA:15,0 +FN:15,DeployContentSignEnterprise.setUp +FNDA:0,DeployContentSignEnterprise.setUp +DA:16,0 +DA:17,0 +DA:18,0 +DA:21,0 +FN:21,DeployContentSignEnterprise.run +FNDA:0,DeployContentSignEnterprise.run +DA:22,0 +DA:24,0 +DA:26,0 +DA:28,0 +DA:29,0 +DA:30,0 +DA:31,0 +FNF:2 +FNH:0 +LF:12 +LH:0 +BRF:0 +BRH:0 +end_of_record +TN: +SF:script/DeployL1Bridge.s.sol +DA:22,0 +FN:22,DeployL1Bridge.setUp +FNDA:0,DeployL1Bridge.setUp +DA:23,0 +DA:24,0 +DA:25,0 +DA:26,0 +DA:28,0 +DA:29,0 +DA:30,0 +DA:31,0 +DA:34,0 +FN:34,DeployL1Bridge.run +FNDA:0,DeployL1Bridge.run +DA:35,0 +DA:37,0 +DA:39,0 +DA:40,0 +DA:41,0 +DA:43,0 +DA:45,0 +DA:46,0 +FNF:2 +FNH:0 +LF:18 +LH:0 +BRF:0 +BRH:0 +end_of_record +TN: +SF:script/DeployL1Ens.s.sol +DA:18,0 +FN:18,DeployL1Ens.run +FNDA:0,DeployL1Ens.run +DA:19,0 +DA:20,0 +DA:22,0 +DA:24,0 +BRDA:24,0,0,- +BRDA:24,0,1,- +DA:25,0 +DA:26,0 +BRDA:26,1,0,- +BRDA:26,1,1,- +DA:27,0 +DA:28,0 +DA:29,0 +DA:30,0 +DA:32,0 +DA:35,0 +DA:36,0 +DA:39,0 +DA:40,0 +DA:42,0 +DA:45,0 +DA:47,0 +BRDA:47,2,0,- +DA:48,0 +DA:49,0 +DA:55,0 +DA:56,0 +DA:59,0 +DA:60,0 +DA:62,0 +DA:63,0 +DA:65,0 +DA:66,0 +DA:68,0 +FNF:1 +FNH:0 +LF:30 +LH:0 +BRF:5 +BRH:0 +end_of_record +TN: +SF:script/DeployL1Nodl.s.sol +DA:17,0 +FN:17,DeployL1Nodl.setUp +FNDA:0,DeployL1Nodl.setUp +DA:18,0 +DA:19,0 +DA:21,0 +DA:22,0 +DA:25,0 +FN:25,DeployL1Nodl.run +FNDA:0,DeployL1Nodl.run +DA:26,0 +DA:28,0 +DA:30,0 +DA:32,0 +FNF:2 +FNH:0 +LF:10 +LH:0 +BRF:0 +BRH:0 +end_of_record +TN: +SF:script/DeployL2Bridge.s.sol +DA:19,0 +FN:19,DeployL2Bridge.setUp +FNDA:0,DeployL2Bridge.setUp +DA:20,0 +DA:21,0 +DA:23,0 +DA:24,0 +DA:27,0 +FN:27,DeployL2Bridge.run +FNDA:0,DeployL2Bridge.run +DA:28,0 +DA:30,0 +DA:32,0 +DA:33,0 +DA:34,0 +DA:36,0 +DA:38,0 +DA:39,0 +FNF:2 +FNH:0 +LF:14 +LH:0 +BRF:0 +BRH:0 +end_of_record +TN: +SF:script/DeployL2Nodl.s.sol +DA:17,0 +FN:17,DeployL2Nodl.setUp +FNDA:0,DeployL2Nodl.setUp +DA:18,0 +DA:19,0 +DA:21,0 +DA:22,0 +DA:25,0 +FN:25,DeployL2Nodl.run +FNDA:0,DeployL2Nodl.run +DA:26,0 +DA:28,0 +DA:29,0 +DA:30,0 +DA:32,0 +DA:34,0 +FNF:2 +FNH:0 +LF:12 +LH:0 +BRF:0 +BRH:0 +end_of_record +TN: +SF:script/DeployMigrationNFT.s.sol +DA:18,0 +FN:18,DeployMigrationNFT.setUp +FNDA:0,DeployMigrationNFT.setUp +DA:19,0 +DA:20,0 +DA:22,0 +DA:23,0 +DA:24,0 +DA:25,0 +DA:27,0 +DA:28,0 +DA:32,0 +FN:32,DeployMigrationNFT.run +FNDA:0,DeployMigrationNFT.run +DA:33,0 +DA:35,0 +DA:37,0 +DA:39,0 +FNF:2 +FNH:0 +LF:14 +LH:0 +BRF:0 +BRH:0 +end_of_record +TN: +SF:script/DeployNodlMigration.sol +DA:14,0 +FN:14,DeployNodlMigration.setUp +FNDA:0,DeployNodlMigration.setUp +DA:15,0 +DA:16,0 +DA:18,0 +DA:19,0 +DA:20,0 +DA:23,0 +FN:23,DeployNodlMigration.run +FNDA:0,DeployNodlMigration.run +DA:24,0 +DA:26,0 +DA:27,0 +DA:29,0 +DA:30,0 +DA:32,0 +DA:34,0 +DA:36,0 +DA:37,0 +FNF:2 +FNH:0 +LF:16 +LH:0 +BRF:0 +BRH:0 +end_of_record +TN: +SF:script/DeployRewards.sol +DA:18,0 +FN:18,DeployRewards.setUp +FNDA:0,DeployRewards.setUp +DA:19,0 +DA:20,0 +DA:21,0 +DA:22,0 +DA:23,0 +DA:24,0 +DA:27,0 +FN:27,DeployRewards.run +FNDA:0,DeployRewards.run +DA:28,0 +DA:29,0 +BRDA:29,0,0,- +DA:30,0 +DA:31,0 +DA:32,0 +DA:35,0 +DA:36,0 +DA:39,0 +DA:40,0 +DA:42,0 +DA:44,0 +FNF:2 +FNH:0 +LF:19 +LH:0 +BRF:1 +BRH:0 +end_of_record +TN: +SF:script/RewardsSig.s.sol +DA:15,0 +FN:15,RewardsSig.setUp +FNDA:0,RewardsSig.setUp +DA:16,0 +DA:17,0 +DA:18,0 +DA:19,0 +DA:22,0 +FN:22,RewardsSig.run +FNDA:0,RewardsSig.run +DA:23,0 +DA:25,0 +DA:26,0 +DA:27,0 +DA:29,0 +DA:31,0 +DA:33,0 +DA:34,0 +DA:36,0 +DA:37,0 +DA:39,0 +DA:40,0 +DA:42,0 +DA:43,0 +FNF:2 +FNH:0 +LF:20 +LH:0 +BRF:0 +BRH:0 +end_of_record +TN: +SF:src/Grants.sol +DA:56,0 +FN:56,Grants.constructor +FNDA:0,Grants.constructor +DA:57,0 +BRDA:57,0,0,- +DA:58,0 +DA:61,0 +DA:62,0 +DA:63,0 +DA:75,0 +FN:75,Grants.addVestingSchedule +FNDA:0,Grants.addVestingSchedule +DA:83,0 +DA:85,0 +DA:87,0 +DA:88,0 +BRDA:88,1,0,- +DA:89,0 +DA:90,0 +DA:92,0 +DA:94,0 +DA:96,0 +DA:106,0 +FN:106,Grants.validateVestingSchedule +FNDA:0,Grants.validateVestingSchedule +DA:110,0 +DA:111,0 +DA:112,0 +DA:113,0 +DA:114,0 +DA:123,0 +FN:123,Grants.claim +FNDA:0,Grants.claim +DA:124,0 +DA:125,0 +DA:127,0 +DA:129,0 +DA:130,0 +DA:131,0 +DA:132,0 +DA:133,0 +DA:134,0 +BRDA:134,2,0,- +DA:135,0 +DA:136,0 +DA:137,0 +DA:138,0 +DA:139,0 +DA:140,0 +DA:141,0 +DA:142,0 +BRDA:142,3,0,- +DA:143,0 +DA:144,0 +DA:145,0 +DA:148,0 +DA:152,0 +BRDA:152,4,0,- +BRDA:152,4,1,- +DA:153,0 +DA:154,0 +DA:156,0 +DA:167,0 +FN:167,Grants.renounce +FNDA:0,Grants.renounce +DA:168,0 +DA:170,0 +DA:171,0 +DA:172,0 +DA:173,0 +DA:174,0 +BRDA:174,5,0,- +DA:175,0 +DA:176,0 +DA:181,0 +BRDA:181,6,0,- +BRDA:181,6,1,- +DA:182,0 +DA:184,0 +DA:195,0 +FN:195,Grants.cancelVestingSchedules +FNDA:0,Grants.cancelVestingSchedules +DA:196,0 +DA:197,0 +DA:198,0 +DA:200,0 +DA:201,0 +DA:202,0 +DA:203,0 +DA:204,0 +DA:205,0 +DA:206,0 +BRDA:206,7,0,- +DA:207,0 +DA:208,0 +DA:209,0 +DA:210,0 +DA:211,0 +DA:212,0 +DA:213,0 +DA:214,0 +DA:215,0 +DA:216,0 +DA:217,0 +DA:219,0 +DA:223,0 +BRDA:223,8,0,- +DA:224,0 +DA:227,0 +BRDA:227,9,0,- +DA:228,0 +DA:231,0 +BRDA:231,10,0,- +DA:232,0 +DA:235,0 +DA:243,0 +FN:243,Grants.getGrantsCount +FNDA:0,Grants.getGrantsCount +DA:244,0 +DA:245,0 +DA:246,0 +DA:248,0 +DA:253,0 +FN:253,Grants._mustBeNonZero +FNDA:0,Grants._mustBeNonZero +DA:254,0 +BRDA:254,11,0,- +DA:255,0 +DA:259,0 +FN:259,Grants._mustBeNonZeroAddress +FNDA:0,Grants._mustBeNonZeroAddress +DA:260,0 +BRDA:260,12,0,- +DA:261,0 +DA:265,0 +FN:265,Grants._mustNotBeSelf +FNDA:0,Grants._mustNotBeSelf +DA:266,0 +BRDA:266,13,0,- +DA:267,0 +DA:271,0 +FN:271,Grants._mustBeEqualOrExceedMinAmount +FNDA:0,Grants._mustBeEqualOrExceedMinAmount +DA:272,0 +BRDA:272,14,0,- +DA:273,0 +DA:277,0 +FN:277,Grants._sanitizePageRange +FNDA:0,Grants._sanitizePageRange +DA:278,0 +DA:279,0 +BRDA:279,15,0,- +DA:280,0 +DA:282,0 +BRDA:282,16,0,- +DA:283,0 +DA:285,0 +FNF:12 +FNH:0 +LF:114 +LH:0 +BRF:19 +BRH:0 +end_of_record +TN: +SF:src/L1Nodl.sol +DA:18,0 +FN:18,L1Nodl.constructor +FNDA:0,L1Nodl.constructor +DA:19,0 +BRDA:19,0,0,- +DA:20,0 +DA:22,0 +DA:23,0 +DA:26,0 +FN:26,L1Nodl.mint +FNDA:0,L1Nodl.mint +DA:27,0 +DA:30,0 +FN:30,L1Nodl.clock +FNDA:0,L1Nodl.clock +DA:31,0 +DA:35,0 +FN:35,L1Nodl.CLOCK_MODE +FNDA:0,L1Nodl.CLOCK_MODE +DA:36,0 +DA:39,0 +FN:39,L1Nodl.nonces +FNDA:0,L1Nodl.nonces +DA:40,0 +DA:43,0 +FN:43,L1Nodl._update +FNDA:0,L1Nodl._update +DA:44,0 +FNF:6 +FNH:0 +LF:15 +LH:0 +BRF:1 +BRH:0 +end_of_record +TN: +SF:src/NODL.sol +DA:12,0 +FN:12,NODL.constructor +FNDA:0,NODL.constructor +DA:13,0 +DA:14,0 +DA:17,0 +FN:17,NODL.mint +FNDA:0,NODL.mint +DA:18,0 +DA:20,0 +FNF:2 +FNH:0 +LF:6 +LH:0 +BRF:0 +BRH:0 +end_of_record +TN: +SF:src/Payment.sol +DA:40,0 +FN:40,Payment.constructor +FNDA:0,Payment.constructor +DA:43,0 +DA:44,0 +DA:58,0 +FN:58,Payment.pay +FNDA:0,Payment.pay +DA:59,0 +DA:60,0 +DA:62,0 +BRDA:62,0,0,- +DA:63,0 +DA:66,0 +DA:67,0 +DA:69,0 +DA:70,0 +DA:83,0 +FN:83,Payment.withdraw +FNDA:0,Payment.withdraw +DA:84,0 +FNF:3 +FNH:0 +LF:14 +LH:0 +BRF:1 +BRH:0 +end_of_record +TN: +SF:src/QuotaControl.sol +DA:84,0 +FN:84,QuotaControl.constructor +FNDA:0,QuotaControl.constructor +DA:85,0 +DA:86,0 +DA:88,0 +DA:89,0 +DA:90,0 +DA:97,0 +FN:97,QuotaControl.setQuota +FNDA:0,QuotaControl.setQuota +DA:98,0 +DA:99,0 +DA:109,0 +FN:109,QuotaControl.setPeriod +FNDA:0,QuotaControl.setPeriod +DA:110,0 +DA:111,0 +DA:112,0 +DA:121,0 +FN:121,QuotaControl._checkedResetClaimed +FNDA:0,QuotaControl._checkedResetClaimed +DA:122,0 +BRDA:122,0,0,- +DA:123,0 +DA:126,0 +DA:127,0 +DA:140,0 +FN:140,QuotaControl._checkedUpdateClaimed +FNDA:0,QuotaControl._checkedUpdateClaimed +DA:141,0 +DA:142,0 +BRDA:142,1,0,- +DA:143,0 +DA:145,0 +DA:156,0 +FN:156,QuotaControl._mustBeWithinPeriodRange +FNDA:0,QuotaControl._mustBeWithinPeriodRange +DA:157,0 +BRDA:157,2,0,- +DA:158,0 +DA:160,0 +BRDA:160,3,0,- +DA:161,0 +FNF:6 +FNH:0 +LF:28 +LH:0 +BRF:4 +BRH:0 +end_of_record +TN: +SF:src/Rewards.sol +DA:137,0 +FN:137,Rewards.constructor +FNDA:0,Rewards.constructor +DA:145,0 +DA:147,0 +DA:148,0 +DA:149,0 +DA:157,0 +FN:157,Rewards.mintReward +FNDA:0,Rewards.mintReward +DA:158,0 +DA:159,0 +DA:161,0 +DA:162,0 +DA:165,0 +DA:166,0 +DA:168,0 +DA:176,0 +FN:176,Rewards.mintBatchReward +FNDA:0,Rewards.mintBatchReward +DA:177,0 +DA:178,0 +DA:180,0 +DA:182,0 +DA:184,0 +DA:186,0 +DA:187,0 +DA:189,0 +DA:192,0 +DA:194,0 +DA:196,0 +DA:197,0 +DA:199,0 +DA:201,0 +DA:208,0 +FN:208,Rewards.setBatchSubmitterRewardBasisPoints +FNDA:0,Rewards.setBatchSubmitterRewardBasisPoints +DA:209,0 +DA:210,0 +DA:211,0 +DA:213,0 +DA:220,0 +FN:220,Rewards._mustBeLessThanBasisPointsDivisor +FNDA:0,Rewards._mustBeLessThanBasisPointsDivisor +DA:221,0 +BRDA:221,0,0,- +DA:222,0 +DA:231,0 +FN:231,Rewards._mustBeExpectedSequence +FNDA:0,Rewards._mustBeExpectedSequence +DA:232,0 +BRDA:232,1,0,- +DA:233,0 +DA:241,0 +FN:241,Rewards._mustBeExpectedBatchSequence +FNDA:0,Rewards._mustBeExpectedBatchSequence +DA:242,0 +BRDA:242,2,0,- +DA:243,0 +DA:251,0 +FN:251,Rewards._mustBeValidBatchStructure +FNDA:0,Rewards._mustBeValidBatchStructure +DA:252,0 +BRDA:252,3,0,- +DA:253,0 +DA:263,0 +FN:263,Rewards._mustBeFromAuthorizedOracle +FNDA:0,Rewards._mustBeFromAuthorizedOracle +DA:264,0 +BRDA:264,4,0,- +DA:265,0 +DA:274,0 +FN:274,Rewards._batchSum +FNDA:0,Rewards._batchSum +DA:275,0 +DA:276,0 +DA:277,0 +DA:279,0 +DA:287,0 +FN:287,Rewards.digestReward +FNDA:0,Rewards.digestReward +DA:288,0 +DA:289,0 +DA:297,0 +FN:297,Rewards.digestBatchReward +FNDA:0,Rewards.digestBatchReward +DA:298,0 +DA:299,0 +DA:300,0 +DA:301,0 +DA:308,0 +FN:308,Rewards.latestBatchDetails +FNDA:0,Rewards.latestBatchDetails +DA:309,0 +FNF:13 +FNH:0 +LF:63 +LH:0 +BRF:5 +BRH:0 +end_of_record +TN: +SF:src/bridge/BridgeBase.sol +DA:70,0 +FN:70,BridgeBase.constructor +FNDA:0,BridgeBase.constructor +DA:71,0 +DA:72,0 +DA:73,0 +DA:75,0 +DA:76,0 +DA:79,0 +DA:80,0 +DA:81,0 +DA:90,0 +FN:90,BridgeBase._createVote +FNDA:0,BridgeBase._createVote +DA:91,0 +DA:93,0 +DA:99,0 +FN:99,BridgeBase._recordVote +FNDA:0,BridgeBase._recordVote +DA:100,0 +DA:102,0 +DA:104,0 +DA:108,0 +FN:108,BridgeBase._processVote +FNDA:0,BridgeBase._processVote +DA:109,0 +DA:110,0 +DA:111,0 +DA:116,0 +FN:116,BridgeBase._execute +FNDA:0,BridgeBase._execute +DA:117,0 +DA:118,0 +DA:119,0 +DA:121,0 +DA:164,0 +FN:164,BridgeBase._mustNotHaveExecutedYet +FNDA:0,BridgeBase._mustNotHaveExecutedYet +DA:165,0 +BRDA:165,0,0,- +DA:166,0 +DA:170,0 +FN:170,BridgeBase._mustBePastSafetyDelay +FNDA:0,BridgeBase._mustBePastSafetyDelay +DA:171,0 +BRDA:171,1,0,- +DA:172,0 +DA:176,0 +FN:176,BridgeBase._mustHaveEnoughVotes +FNDA:0,BridgeBase._mustHaveEnoughVotes +DA:177,0 +BRDA:177,2,0,- +DA:178,0 +DA:182,0 +FN:182,BridgeBase._mustHaveEnoughOracles +FNDA:0,BridgeBase._mustHaveEnoughOracles +DA:183,0 +BRDA:183,3,0,- +DA:184,0 +DA:188,0 +FN:188,BridgeBase._mustBeAnOracle +FNDA:0,BridgeBase._mustBeAnOracle +DA:189,0 +BRDA:189,4,0,- +DA:190,0 +DA:194,0 +FN:194,BridgeBase._mustNotExceedMaxOracles +FNDA:0,BridgeBase._mustNotExceedMaxOracles +DA:195,0 +BRDA:195,5,0,- +DA:196,0 +DA:200,0 +FN:200,BridgeBase._mustNotBeZeroMinVotes +FNDA:0,BridgeBase._mustNotBeZeroMinVotes +DA:201,0 +BRDA:201,6,0,- +DA:202,0 +DA:206,0 +FN:206,BridgeBase._mustNotHaveVotedYet +FNDA:0,BridgeBase._mustNotHaveVotedYet +DA:207,0 +BRDA:207,7,0,- +DA:208,0 +FNF:13 +FNH:0 +LF:49 +LH:0 +BRF:8 +BRH:0 +end_of_record +TN: +SF:src/bridge/GrantsMigration.sol +DA:57,0 +FN:57,GrantsMigration.constructor +FNDA:0,GrantsMigration.constructor +DA:60,0 +DA:70,0 +FN:70,GrantsMigration.bridge +FNDA:0,GrantsMigration.bridge +DA:73,0 +DA:74,0 +DA:76,0 +BRDA:76,0,0,- +BRDA:76,0,1,- +DA:77,0 +DA:78,0 +DA:80,0 +DA:88,0 +FN:88,GrantsMigration.grant +FNDA:0,GrantsMigration.grant +DA:89,0 +DA:90,0 +DA:91,0 +DA:92,0 +DA:93,0 +DA:94,0 +DA:103,0 +DA:108,0 +FN:108,GrantsMigration._mustNotBeChangingParameters +FNDA:0,GrantsMigration._mustNotBeChangingParameters +DA:114,0 +DA:116,0 +BRDA:116,1,0,- +DA:117,0 +DA:120,0 +DA:121,0 +BRDA:121,2,0,- +DA:122,0 +DA:125,0 +DA:127,0 +DA:128,0 +DA:129,0 +DA:130,0 +DA:131,0 +DA:132,0 +BRDA:132,3,0,- +DA:133,0 +DA:138,0 +FN:138,GrantsMigration._createProposal +FNDA:0,GrantsMigration._createProposal +DA:145,0 +BRDA:145,4,0,- +DA:146,0 +DA:148,0 +BRDA:148,5,0,- +DA:149,0 +DA:151,0 +DA:152,0 +DA:153,0 +DA:156,0 +DA:158,0 +BRDA:158,6,0,- +DA:159,0 +DA:161,0 +DA:162,0 +DA:165,0 +FN:165,GrantsMigration._proposalExists +FNDA:0,GrantsMigration._proposalExists +DA:166,0 +DA:169,0 +FN:169,GrantsMigration._flagAsExecuted +FNDA:0,GrantsMigration._flagAsExecuted +DA:170,0 +DA:173,0 +FN:173,GrantsMigration._incTotalVotes +FNDA:0,GrantsMigration._incTotalVotes +DA:174,0 +DA:177,0 +FN:177,GrantsMigration._updateLastVote +FNDA:0,GrantsMigration._updateLastVote +DA:178,0 +DA:181,0 +FN:181,GrantsMigration._totalVotes +FNDA:0,GrantsMigration._totalVotes +DA:182,0 +DA:185,0 +FN:185,GrantsMigration._lastVote +FNDA:0,GrantsMigration._lastVote +DA:186,0 +DA:189,0 +FN:189,GrantsMigration._executed +FNDA:0,GrantsMigration._executed +DA:190,0 +FNF:12 +FNH:0 +LF:59 +LH:0 +BRF:8 +BRH:0 +end_of_record +TN: +SF:src/bridge/L1Bridge.sol +DA:80,0 +FN:80,L1Bridge.constructor +FNDA:0,L1Bridge.constructor +DA:81,0 +BRDA:81,0,0,- +DA:82,0 +DA:84,0 +DA:85,0 +DA:86,0 +DA:94,0 +FN:94,L1Bridge.pause +FNDA:0,L1Bridge.pause +DA:95,0 +DA:99,0 +FN:99,L1Bridge.unpause +FNDA:0,L1Bridge.unpause +DA:100,0 +DA:115,0 +FN:115,L1Bridge.quoteL2BaseCost +FNDA:0,L1Bridge.quoteL2BaseCost +DA:120,0 +DA:130,0 +FN:130,L1Bridge.quoteL2BaseCostAtGasPrice +FNDA:0,L1Bridge.quoteL2BaseCostAtGasPrice +DA:135,0 +DA:152,0 +FN:152,L1Bridge.deposit +FNDA:0,L1Bridge.deposit +DA:159,0 +BRDA:159,1,0,- +DA:160,0 +DA:162,0 +BRDA:162,2,0,- +DA:163,0 +DA:166,0 +DA:168,0 +DA:169,0 +DA:171,0 +DA:175,0 +DA:177,0 +DA:183,0 +FN:183,L1Bridge.deposit +FNDA:0,L1Bridge.deposit +DA:189,0 +DA:202,0 +FN:202,L1Bridge.claimFailedDeposit +FNDA:0,L1Bridge.claimFailedDeposit +DA:210,0 +DA:211,0 +BRDA:211,3,0,- +DA:212,0 +DA:214,0 +DA:217,0 +BRDA:217,4,0,- +DA:218,0 +DA:220,0 +DA:221,0 +DA:222,0 +DA:234,0 +FN:234,L1Bridge.finalizeWithdrawal +FNDA:0,L1Bridge.finalizeWithdrawal +DA:241,0 +BRDA:241,5,0,- +DA:242,0 +DA:245,0 +DA:246,0 +DA:249,0 +DA:255,0 +BRDA:255,6,0,- +DA:256,0 +DA:259,0 +DA:261,0 +DA:262,0 +DA:276,0 +FN:276,L1Bridge._parseL2WithdrawalMessage +FNDA:0,L1Bridge._parseL2WithdrawalMessage +DA:282,0 +BRDA:282,7,0,- +DA:283,0 +DA:287,0 +DA:288,0 +BRDA:288,8,0,- +DA:289,0 +DA:291,0 +DA:292,0 +FNF:10 +FNH:0 +LF:56 +LH:0 +BRF:9 +BRH:0 +end_of_record +TN: +SF:src/bridge/L2Bridge.sol +DA:49,0 +FN:49,L2Bridge.onlyL1Bridge +FNDA:0,L2Bridge.onlyL1Bridge +DA:50,0 +BRDA:50,0,0,- +DA:51,0 +DA:65,0 +FN:65,L2Bridge.constructor +FNDA:0,L2Bridge.constructor +DA:66,0 +BRDA:66,1,0,- +DA:67,0 +DA:69,0 +DA:70,0 +DA:78,0 +FN:78,L2Bridge.pause +FNDA:0,L2Bridge.pause +DA:79,0 +DA:83,0 +FN:83,L2Bridge.unpause +FNDA:0,L2Bridge.unpause +DA:84,0 +DA:90,0 +FN:90,L2Bridge.initialize +FNDA:0,L2Bridge.initialize +DA:91,0 +BRDA:91,2,0,- +DA:92,0 +DA:94,0 +BRDA:94,3,0,- +DA:95,0 +DA:97,0 +DA:103,0 +FN:103,L2Bridge.finalizeDeposit +FNDA:0,L2Bridge.finalizeDeposit +DA:109,0 +BRDA:109,4,0,- +DA:110,0 +DA:112,0 +BRDA:112,5,0,- +DA:113,0 +DA:116,0 +DA:118,0 +DA:124,0 +FN:124,L2Bridge.withdraw +FNDA:0,L2Bridge.withdraw +DA:125,0 +BRDA:125,6,0,- +DA:126,0 +DA:128,0 +BRDA:128,7,0,- +DA:129,0 +DA:132,0 +DA:135,0 +DA:136,0 +DA:138,0 +FNF:7 +FNH:0 +LF:34 +LH:0 +BRF:8 +BRH:0 +end_of_record +TN: +SF:src/bridge/MigrationNFT.sol +DA:69,0 +FN:69,MigrationNFT.constructor +FNDA:0,MigrationNFT.constructor +DA:75,0 +BRDA:75,0,0,- +DA:76,0 +DA:78,0 +BRDA:78,1,0,- +DA:79,0 +DA:81,0 +BRDA:81,2,0,- +DA:82,0 +DA:85,0 +DA:86,0 +BRDA:86,3,0,- +DA:87,0 +DA:91,0 +DA:92,0 +DA:93,0 +DA:94,0 +DA:101,0 +FN:101,MigrationNFT.tokenURI +FNDA:0,MigrationNFT.tokenURI +DA:102,0 +DA:104,0 +DA:105,0 +DA:112,0 +FN:112,MigrationNFT.safeMint +FNDA:0,MigrationNFT.safeMint +DA:113,0 +DA:115,0 +DA:117,0 +DA:118,0 +DA:119,0 +DA:121,0 +DA:123,0 +DA:124,0 +BRDA:124,4,0,- +DA:125,0 +DA:128,0 +DA:129,0 +DA:130,0 +DA:131,0 +DA:132,0 +DA:136,0 +FN:136,MigrationNFT._computeLevelUps +FNDA:0,MigrationNFT._computeLevelUps +DA:141,0 +DA:142,0 +DA:147,0 +DA:148,0 +DA:149,0 +BRDA:149,5,0,- +DA:150,0 +DA:151,0 +DA:155,0 +BRDA:155,6,0,- +DA:156,0 +DA:160,0 +FN:160,MigrationNFT._mustNotHaveBeenClaimed +FNDA:0,MigrationNFT._mustNotHaveBeenClaimed +DA:161,0 +BRDA:161,7,0,- +DA:162,0 +DA:166,0 +FN:166,MigrationNFT._mustBeAnExistingProposal +FNDA:0,MigrationNFT._mustBeAnExistingProposal +DA:168,0 +BRDA:168,8,0,- +DA:169,0 +DA:173,0 +FN:173,MigrationNFT._mustBeExecuted +FNDA:0,MigrationNFT._mustBeExecuted +DA:174,0 +BRDA:174,9,0,- +DA:175,0 +DA:179,0 +FN:179,MigrationNFT._mustAlreadyBeHolderOrEnoughHoldersRemaining +FNDA:0,MigrationNFT._mustAlreadyBeHolderOrEnoughHoldersRemaining +DA:180,0 +DA:181,0 +BRDA:181,10,0,- +DA:182,0 +DA:186,0 +FN:186,MigrationNFT._update +FNDA:0,MigrationNFT._update +DA:187,0 +DA:188,0 +BRDA:188,11,0,- +DA:190,0 +DA:193,0 +FNF:9 +FNH:0 +LF:61 +LH:0 +BRF:12 +BRH:0 +end_of_record +TN: +SF:src/bridge/NODLMigration.sol +DA:35,0 +FN:35,NODLMigration.bridge +FNDA:0,NODLMigration.bridge +DA:36,0 +DA:37,0 +DA:39,0 +BRDA:39,0,0,- +BRDA:39,0,1,- +DA:40,0 +DA:41,0 +DA:43,0 +DA:50,0 +FN:50,NODLMigration.withdraw +FNDA:0,NODLMigration.withdraw +DA:51,0 +DA:52,0 +DA:55,0 +FN:55,NODLMigration._mustNotBeChangingParameters +FNDA:0,NODLMigration._mustNotBeChangingParameters +DA:56,0 +BRDA:56,1,0,- +DA:57,0 +DA:61,0 +FN:61,NODLMigration._proposalExists +FNDA:0,NODLMigration._proposalExists +DA:62,0 +DA:65,0 +FN:65,NODLMigration._createVote +FNDA:0,NODLMigration._createVote +DA:66,0 +DA:67,0 +DA:68,0 +DA:71,0 +FN:71,NODLMigration._withdraw +FNDA:0,NODLMigration._withdraw +DA:72,0 +DA:73,0 +DA:76,0 +FN:76,NODLMigration._flagAsExecuted +FNDA:0,NODLMigration._flagAsExecuted +DA:77,0 +DA:80,0 +FN:80,NODLMigration._incTotalVotes +FNDA:0,NODLMigration._incTotalVotes +DA:81,0 +DA:84,0 +FN:84,NODLMigration._updateLastVote +FNDA:0,NODLMigration._updateLastVote +DA:85,0 +DA:88,0 +FN:88,NODLMigration._totalVotes +FNDA:0,NODLMigration._totalVotes +DA:89,0 +DA:92,0 +FN:92,NODLMigration._lastVote +FNDA:0,NODLMigration._lastVote +DA:93,0 +DA:96,0 +FN:96,NODLMigration._executed +FNDA:0,NODLMigration._executed +DA:97,0 +FNF:12 +FNH:0 +LF:34 +LH:0 +BRF:3 +BRH:0 +end_of_record +TN: +SF:src/contentsign/BaseContentSign.sol +DA:17,0 +FN:17,BaseContentSign.safeMint +FNDA:0,BaseContentSign.safeMint +DA:18,0 +DA:20,0 +DA:21,0 +DA:22,0 +DA:25,0 +FN:25,BaseContentSign.tokenURI +FNDA:0,BaseContentSign.tokenURI +DA:26,0 +DA:29,0 +FN:29,BaseContentSign.supportsInterface +FNDA:0,BaseContentSign.supportsInterface +DA:36,0 +DA:39,0 +FN:39,BaseContentSign._mustBeWhitelisted +FNDA:0,BaseContentSign._mustBeWhitelisted +DA:40,0 +BRDA:40,0,0,- +DA:41,0 +FNF:4 +FNH:0 +LF:12 +LH:0 +BRF:1 +BRH:0 +end_of_record +TN: +SF:src/contentsign/ClickBounty.sol +DA:158,0 +FN:158,ClickBounty.constructor +FNDA:0,ClickBounty.constructor +DA:159,0 +DA:160,0 +DA:162,0 +DA:163,0 +DA:164,0 +DA:181,0 +FN:181,ClickBounty.payEntryFee +FNDA:0,ClickBounty.payEntryFee +DA:182,0 +DA:184,0 +BRDA:184,0,0,- +DA:185,0 +DA:188,0 +DA:189,0 +BRDA:189,1,0,- +DA:190,0 +DA:193,0 +DA:194,0 +DA:209,0 +FN:209,ClickBounty.setEntryFee +FNDA:0,ClickBounty.setEntryFee +DA:210,0 +DA:211,0 +DA:212,0 +DA:224,0 +FN:224,ClickBounty.withdraw +FNDA:0,ClickBounty.withdraw +DA:225,0 +DA:226,0 +DA:254,0 +FN:254,ClickBounty.awardBounty +FNDA:0,ClickBounty.awardBounty +DA:255,0 +DA:257,0 +BRDA:257,2,0,- +DA:258,0 +DA:260,0 +DA:262,0 +BRDA:262,3,0,- +DA:263,0 +DA:265,0 +BRDA:265,4,0,- +DA:266,0 +DA:269,0 +DA:272,0 +DA:275,0 +DA:277,0 +DA:280,0 +DA:294,0 +FN:294,ClickBounty.getLeaderboard +FNDA:0,ClickBounty.getLeaderboard +DA:295,0 +DA:296,0 +DA:297,0 +DA:298,0 +DA:317,0 +FN:317,ClickBounty._updateLeaderboard +FNDA:0,ClickBounty._updateLeaderboard +DA:319,0 +BRDA:319,5,0,- +DA:320,0 +DA:321,0 +DA:322,0 +DA:326,0 +DA:327,0 +DA:328,0 +DA:329,0 +DA:330,0 +BRDA:330,6,0,- +DA:331,0 +DA:332,0 +DA:337,0 +BRDA:337,7,0,- +DA:338,0 +DA:339,0 +FNF:7 +FNH:0 +LF:56 +LH:0 +BRF:8 +BRH:0 +end_of_record +TN: +SF:src/contentsign/ClickContentSign.sol +DA:12,0 +FN:12,ClickContentSign.constructor +FNDA:0,ClickContentSign.constructor +DA:13,0 +DA:16,0 +FN:16,ClickContentSign._userIsWhitelisted +FNDA:0,ClickContentSign._userIsWhitelisted +DA:17,0 +FNF:2 +FNH:0 +LF:4 +LH:0 +BRF:0 +BRH:0 +end_of_record +TN: +SF:src/contentsign/EnterpriseContentSign.sol +DA:13,0 +FN:13,EnterpriseContentSign.constructor +FNDA:0,EnterpriseContentSign.constructor +DA:14,0 +DA:17,0 +FN:17,EnterpriseContentSign.supportsInterface +FNDA:0,EnterpriseContentSign.supportsInterface +DA:23,0 +DA:26,0 +FN:26,EnterpriseContentSign._userIsWhitelisted +FNDA:0,EnterpriseContentSign._userIsWhitelisted +DA:27,0 +FNF:3 +FNH:0 +LF:6 +LH:0 +BRF:0 +BRH:0 +end_of_record +TN: +SF:src/contentsign/PaymentMiddleware.sol +DA:21,0 +FN:21,PaymentMiddleware.constructor +FNDA:0,PaymentMiddleware.constructor +DA:24,0 +DA:25,0 +DA:26,0 +DA:27,0 +DA:30,0 +FN:30,PaymentMiddleware.safeMint +FNDA:0,PaymentMiddleware.safeMint +DA:32,0 +BRDA:32,0,0,- +DA:33,0 +DA:37,0 +DA:40,0 +DA:43,0 +FN:43,PaymentMiddleware.withdraw +FNDA:0,PaymentMiddleware.withdraw +DA:44,0 +DA:46,0 +DA:47,0 +DA:50,0 +FN:50,PaymentMiddleware.setFeeAmount +FNDA:0,PaymentMiddleware.setFeeAmount +DA:51,0 +DA:53,0 +DA:56,0 +FN:56,PaymentMiddleware.setTarget +FNDA:0,PaymentMiddleware.setTarget +DA:57,0 +DA:59,0 +DA:62,0 +FN:62,PaymentMiddleware.setWhitelist +FNDA:0,PaymentMiddleware.setWhitelist +DA:63,0 +DA:65,0 +DA:68,0 +FN:68,PaymentMiddleware.setFeeToken +FNDA:0,PaymentMiddleware.setFeeToken +DA:69,0 +DA:71,0 +FNF:7 +FNH:0 +LF:26 +LH:0 +BRF:1 +BRH:0 +end_of_record +TN: +SF:src/nameservice/ClickNameService.sol +DA:62,0 +FN:62,ClickNameService.constructor +FNDA:0,ClickNameService.constructor +DA:63,0 +DA:64,0 +DA:68,0 +FN:68,ClickNameService.resolve +FNDA:0,ClickNameService.resolve +DA:69,0 +DA:70,0 +DA:71,0 +BRDA:71,0,0,- +DA:72,0 +DA:74,0 +DA:80,0 +FN:80,ClickNameService.batchRegister +FNDA:0,ClickNameService.batchRegister +DA:81,0 +BRDA:81,1,0,- +DA:82,0 +DA:85,0 +DA:86,0 +DA:94,0 +FN:94,ClickNameService.setDefaultExpiry +FNDA:0,ClickNameService.setDefaultExpiry +DA:95,0 +DA:99,0 +FN:99,ClickNameService.register +FNDA:0,ClickNameService.register +DA:100,0 +DA:104,0 +FN:104,ClickNameService.registerWithExpiry +FNDA:0,ClickNameService.registerWithExpiry +DA:105,0 +BRDA:105,2,0,- +DA:106,0 +DA:109,0 +DA:110,0 +DA:111,0 +DA:112,0 +DA:115,0 +FN:115,ClickNameService._register +FNDA:0,ClickNameService._register +DA:116,0 +BRDA:116,3,0,- +DA:117,0 +DA:119,0 +BRDA:119,4,0,- +DA:120,0 +DA:123,0 +DA:124,0 +DA:125,0 +BRDA:125,5,0,- +BRDA:125,5,1,- +DA:126,0 +DA:128,0 +BRDA:128,6,0,- +DA:129,0 +DA:131,0 +DA:134,0 +DA:140,0 +FN:140,ClickNameService.supportsInterface +FNDA:0,ClickNameService.supportsInterface +DA:141,0 +DA:142,0 +DA:149,0 +FN:149,ClickNameService.burn +FNDA:0,ClickNameService.burn +DA:150,0 +DA:151,0 +DA:158,0 +FN:158,ClickNameService.removeExpired +FNDA:0,ClickNameService.removeExpired +DA:159,0 +DA:160,0 +BRDA:160,7,0,- +DA:161,0 +DA:163,0 +DA:164,0 +DA:171,0 +FN:171,ClickNameService.extendExpiry +FNDA:0,ClickNameService.extendExpiry +DA:172,0 +BRDA:172,8,0,- +DA:173,0 +DA:175,0 +BRDA:175,9,0,- +DA:176,0 +DA:179,0 +BRDA:179,10,0,- +BRDA:179,10,1,- +DA:180,0 +DA:182,0 +DA:187,0 +FN:187,ClickNameService._isAlphanumeric +FNDA:0,ClickNameService._isAlphanumeric +DA:188,0 +DA:189,0 +DA:190,0 +DA:191,0 +DA:193,0 +DA:197,0 +FN:197,ClickNameService._isAuthorized +FNDA:0,ClickNameService._isAuthorized +DA:198,0 +FNF:13 +FNH:0 +LF:66 +LH:0 +BRF:13 +BRH:0 +end_of_record +TN: +SF:src/nameservice/NameService.sol +DA:75,0 +FN:75,NameService.constructor +FNDA:0,NameService.constructor +DA:78,0 +DA:79,0 +DA:83,0 +FN:83,NameService.resolve +FNDA:0,NameService.resolve +DA:84,0 +DA:85,0 +DA:86,0 +BRDA:86,0,0,- +DA:87,0 +DA:89,0 +DA:95,0 +FN:95,NameService.batchRegister +FNDA:0,NameService.batchRegister +DA:96,0 +BRDA:96,1,0,- +DA:97,0 +DA:100,0 +DA:101,0 +DA:109,0 +FN:109,NameService.setDefaultExpiry +FNDA:0,NameService.setDefaultExpiry +DA:110,0 +DA:114,0 +FN:114,NameService.register +FNDA:0,NameService.register +DA:115,0 +DA:119,0 +FN:119,NameService.registerWithExpiry +FNDA:0,NameService.registerWithExpiry +DA:120,0 +BRDA:120,2,0,- +DA:121,0 +DA:124,0 +DA:125,0 +DA:126,0 +DA:127,0 +DA:130,0 +FN:130,NameService._register +FNDA:0,NameService._register +DA:131,0 +BRDA:131,3,0,- +DA:132,0 +DA:134,0 +BRDA:134,4,0,- +DA:135,0 +DA:138,0 +DA:139,0 +DA:140,0 +BRDA:140,5,0,- +BRDA:140,5,1,- +DA:141,0 +DA:143,0 +BRDA:143,6,0,- +DA:144,0 +DA:146,0 +DA:149,0 +DA:155,0 +FN:155,NameService.supportsInterface +FNDA:0,NameService.supportsInterface +DA:156,0 +DA:157,0 +DA:164,0 +FN:164,NameService.burn +FNDA:0,NameService.burn +DA:165,0 +DA:167,0 +DA:168,0 +DA:169,0 +DA:171,0 +DA:173,0 +DA:174,0 +DA:181,0 +FN:181,NameService.removeExpired +FNDA:0,NameService.removeExpired +DA:182,0 +DA:183,0 +BRDA:183,7,0,- +DA:184,0 +DA:186,0 +DA:187,0 +DA:194,0 +FN:194,NameService.extendExpiry +FNDA:0,NameService.extendExpiry +DA:195,0 +BRDA:195,8,0,- +DA:196,0 +DA:198,0 +BRDA:198,9,0,- +DA:199,0 +DA:202,0 +BRDA:202,10,0,- +BRDA:202,10,1,- +DA:203,0 +DA:205,0 +DA:210,0 +FN:210,NameService._isAlphanumeric +FNDA:0,NameService._isAlphanumeric +DA:211,0 +DA:212,0 +DA:213,0 +DA:214,0 +DA:216,0 +DA:220,0 +FN:220,NameService._isAuthorized +FNDA:0,NameService._isAuthorized +DA:221,0 +DA:228,0 +FN:228,NameService.setTextRecord +FNDA:0,NameService.setTextRecord +DA:229,0 +DA:231,0 +DA:232,0 +DA:233,0 +DA:235,0 +BRDA:235,12,0,- +DA:236,0 +DA:238,0 +BRDA:238,13,0,- +DA:239,0 +DA:241,0 +DA:242,0 +DA:249,0 +FN:249,NameService.getTextRecord +FNDA:0,NameService.getTextRecord +DA:250,0 +DA:251,0 +BRDA:251,14,0,- +DA:252,0 +DA:254,0 +FNF:15 +FNH:0 +LF:87 +LH:0 +BRF:16 +BRH:0 +end_of_record +TN: +SF:src/nameservice/PaymasterTest.sol +DA:23,0 +FN:23,PaymasterTest.register +FNDA:0,PaymasterTest.register +DA:24,0 +BRDA:24,0,0,- +DA:25,0 +DA:27,0 +BRDA:27,1,0,- +DA:28,0 +DA:31,0 +DA:32,0 +DA:33,0 +BRDA:33,2,0,- +BRDA:33,2,1,- +DA:34,0 +DA:36,0 +DA:39,0 +DA:43,0 +FN:43,PaymasterTest._isAlphanumeric +FNDA:0,PaymasterTest._isAlphanumeric +DA:44,0 +DA:45,0 +DA:46,0 +DA:47,0 +DA:49,0 +FNF:2 +FNH:0 +LF:17 +LH:0 +BRF:4 +BRH:0 +end_of_record +TN: +SF:src/nameservice/UniversalResolver.sol +DA:53,0 +FN:53,UniversalResolver.constructor +FNDA:0,UniversalResolver.constructor +DA:56,0 +DA:57,0 +DA:58,0 +DA:59,0 +DA:62,0 +DA:63,0 +DA:66,0 +FN:66,UniversalResolver.setUrl +FNDA:0,UniversalResolver.setUrl +DA:67,0 +DA:81,0 +FN:81,UniversalResolver._parseDnsDomain +FNDA:0,UniversalResolver._parseDnsDomain +DA:86,0 +DA:88,0 +DA:89,0 +DA:93,0 +DA:95,0 +DA:96,0 +DA:100,0 +DA:102,0 +DA:103,0 +DA:105,0 +DA:110,0 +FN:110,UniversalResolver.getStorageKey +FNDA:0,UniversalResolver.getStorageKey +DA:111,0 +DA:112,0 +DA:119,0 +FN:119,UniversalResolver.getTextRecordStorageKey +FNDA:0,UniversalResolver.getTextRecordStorageKey +DA:120,0 +DA:121,0 +DA:122,0 +DA:130,0 +FN:130,UniversalResolver.resolve +FNDA:0,UniversalResolver.resolve +DA:131,0 +DA:133,0 +BRDA:133,2,0,- +DA:134,0 +DA:137,0 +DA:138,0 +DA:140,0 +BRDA:140,3,0,- +BRDA:140,3,1,- +DA:141,0 +DA:142,0 +DA:143,0 +BRDA:143,4,0,- +BRDA:143,4,1,- +DA:144,0 +DA:145,0 +BRDA:145,5,0,- +DA:146,0 +DA:147,0 +BRDA:147,6,0,- +DA:148,0 +DA:152,0 +DA:155,0 +DA:156,0 +DA:158,0 +DA:159,0 +DA:161,0 +DA:167,0 +FN:167,UniversalResolver.resolveWithProof +FNDA:0,UniversalResolver.resolveWithProof +DA:168,0 +DA:169,0 +DA:172,0 +DA:174,0 +DA:176,0 +DA:178,0 +BRDA:178,7,0,- +DA:179,0 +DA:182,0 +BRDA:182,8,0,- +BRDA:182,8,1,- +DA:183,0 +DA:184,0 +BRDA:184,9,0,- +BRDA:184,9,1,- +DA:185,0 +DA:187,0 +DA:194,0 +FN:194,UniversalResolver.supportsInterface +FNDA:0,UniversalResolver.supportsInterface +DA:195,0 +DA:196,0 +FNF:8 +FNH:0 +LF:64 +LH:0 +BRF:12 +BRH:0 +end_of_record +TN: +SF:src/paymasters/BasePaymaster.sol +DA:33,0 +FN:33,BasePaymaster.constructor +FNDA:0,BasePaymaster.constructor +DA:34,0 +DA:35,0 +DA:38,0 +FN:38,BasePaymaster.validateAndPayForPaymasterTransaction +FNDA:0,BasePaymaster.validateAndPayForPaymasterTransaction +DA:43,0 +DA:46,0 +DA:48,0 +BRDA:48,0,0,- +DA:49,0 +DA:52,0 +DA:56,0 +DA:57,0 +DA:58,0 +DA:60,0 +BRDA:60,1,0,- +BRDA:60,1,1,- +DA:61,0 +DA:62,0 +BRDA:62,2,0,- +BRDA:62,2,1,- +DA:63,0 +DA:64,0 +DA:66,0 +DA:68,0 +DA:72,0 +DA:73,0 +BRDA:73,3,0,- +DA:74,0 +DA:77,0 +DA:80,0 +FN:80,BasePaymaster.postTransaction +FNDA:0,BasePaymaster.postTransaction +DA:88,0 +DA:93,0 +FN:93,BasePaymaster.withdraw +FNDA:0,BasePaymaster.withdraw +DA:94,0 +DA:96,0 +DA:97,0 +BRDA:97,4,0,- +DA:99,0 +DA:104,0 +FN:104,BasePaymaster._mustBeBootloader +FNDA:0,BasePaymaster._mustBeBootloader +DA:105,0 +BRDA:105,5,0,- +DA:106,0 +FNF:5 +FNH:0 +LF:33 +LH:0 +BRF:8 +BRH:0 +end_of_record +TN: +SF:src/paymasters/WhitelistPaymaster.sol +DA:22,0 +FN:22,WhitelistPaymaster.constructor +FNDA:0,WhitelistPaymaster.constructor +DA:23,0 +DA:26,0 +FN:26,WhitelistPaymaster.addWhitelistedContracts +FNDA:0,WhitelistPaymaster.addWhitelistedContracts +DA:27,0 +DA:29,0 +DA:30,0 +DA:33,0 +DA:36,0 +FN:36,WhitelistPaymaster.removeWhitelistedContracts +FNDA:0,WhitelistPaymaster.removeWhitelistedContracts +DA:37,0 +DA:39,0 +DA:40,0 +DA:43,0 +DA:46,0 +FN:46,WhitelistPaymaster.addWhitelistedUsers +FNDA:0,WhitelistPaymaster.addWhitelistedUsers +DA:47,0 +DA:49,0 +DA:50,0 +DA:53,0 +DA:56,0 +FN:56,WhitelistPaymaster.removeWhitelistedUsers +FNDA:0,WhitelistPaymaster.removeWhitelistedUsers +DA:57,0 +DA:59,0 +DA:60,0 +DA:63,0 +DA:66,0 +FN:66,WhitelistPaymaster._validateAndPayGeneralFlow +FNDA:0,WhitelistPaymaster._validateAndPayGeneralFlow +DA:67,0 +BRDA:67,0,0,- +DA:68,0 +DA:71,0 +BRDA:71,1,0,- +DA:72,0 +DA:76,0 +FN:76,WhitelistPaymaster._validateAndPayApprovalBasedFlow +FNDA:0,WhitelistPaymaster._validateAndPayApprovalBasedFlow +DA:81,0 +FNF:7 +FNH:0 +LF:29 +LH:0 +BRF:2 +BRH:0 +end_of_record +TN: +SF:src/swarms/FleetIdentity.sol +DA:227,880 +FN:227,FleetIdentity.constructor +FNDA:880,FleetIdentity.constructor +DA:228,880 +DA:229,880 +DA:243,12142 +FN:243,FleetIdentity.registerFleetCountry +FNDA:12142,FleetIdentity.registerFleetCountry +DA:248,12142 +BRDA:248,0,0,- +DA:249,12142 +BRDA:249,1,0,2 +DA:250,12140 +DA:251,12140 +DA:252,12140 +DA:267,14567 +FN:267,FleetIdentity.registerFleetLocal +FNDA:14567,FleetIdentity.registerFleetLocal +DA:272,14567 +BRDA:272,2,0,- +DA:273,14567 +BRDA:273,3,0,1 +DA:274,14566 +BRDA:274,4,0,2 +DA:275,14564 +DA:276,14564 +DA:277,14564 +DA:286,265 +FN:286,FleetIdentity.promote +FNDA:265,FleetIdentity.promote +DA:287,265 +DA:294,38 +FN:294,FleetIdentity.reassignTier +FNDA:38,FleetIdentity.reassignTier +DA:295,38 +DA:296,38 +BRDA:296,5,0,1 +DA:297,37 +BRDA:297,6,0,28 +BRDA:297,6,1,9 +DA:298,28 +DA:300,9 +DA:316,282 +FN:316,FleetIdentity.setOperator +FNDA:282,FleetIdentity.setOperator +DA:318,282 +BRDA:318,7,0,2 +DA:321,280 +BRDA:321,8,0,- +DA:322,0 +DA:325,280 +DA:328,280 +DA:329,280 +DA:332,280 +DA:335,280 +DA:338,280 +BRDA:338,9,0,278 +DA:340,278 +DA:342,278 +DA:345,1 +DA:367,912 +FN:367,FleetIdentity.burn +FNDA:912,FleetIdentity.burn +DA:368,912 +DA:370,912 +DA:371,912 +DA:372,912 +DA:373,912 +DA:374,912 +DA:376,912 +BRDA:376,10,0,27 +BRDA:376,10,1,583 +DA:378,27 +BRDA:378,11,0,2 +DA:380,25 +DA:381,25 +DA:382,25 +DA:384,25 +DA:387,885 +BRDA:387,12,0,259 +DA:388,259 +DA:391,626 +DA:392,626 +DA:395,626 +DA:397,626 +DA:398,626 +DA:400,43 +BRDA:400,13,0,43 +BRDA:400,13,1,583 +DA:402,43 +DA:403,43 +DA:404,43 +DA:408,583 +DA:412,626 +DA:414,626 +DA:427,24 +FN:427,FleetIdentity.claimUuid +FNDA:24,FleetIdentity.claimUuid +DA:428,24 +BRDA:428,14,0,1 +DA:429,23 +BRDA:429,15,0,2 +DA:432,21 +DA:433,21 +DA:434,21 +DA:436,21 +DA:439,21 +DA:440,21 +DA:442,21 +DA:444,21 +DA:454,29891 +FN:454,FleetIdentity.tierBond +FNDA:29891,FleetIdentity.tierBond +DA:455,29891 +DA:456,29891 +DA:462,9 +FN:462,FleetIdentity.localInclusionHint +FNDA:9,FleetIdentity.localInclusionHint +DA:467,9 +BRDA:467,16,0,- +DA:468,9 +BRDA:468,17,0,- +DA:469,9 +DA:470,7 +DA:478,8 +FN:478,FleetIdentity.countryInclusionHint +FNDA:8,FleetIdentity.countryInclusionHint +DA:479,8 +BRDA:479,18,0,- +DA:482,8 +DA:485,7 +DA:486,7 +DA:487,7 +DA:488,5 +DA:489,5 +DA:490,5 +BRDA:490,19,0,- +DA:492,7 +DA:496,4 +FN:496,FleetIdentity.highestActiveTier +FNDA:4,FleetIdentity.highestActiveTier +DA:497,4 +DA:498,4 +DA:499,1 +DA:503,35 +FN:503,FleetIdentity.tierMemberCount +FNDA:35,FleetIdentity.tierMemberCount +DA:504,35 +DA:508,2256 +FN:508,FleetIdentity.getTierMembers +FNDA:2256,FleetIdentity.getTierMembers +DA:509,2256 +DA:513,2 +FN:513,FleetIdentity.getTierUuids +FNDA:2,FleetIdentity.getTierUuids +DA:514,2 +DA:515,2 +DA:516,2 +DA:517,2 +DA:522,14574 +FN:522,FleetIdentity.tokenUuid +FNDA:14574,FleetIdentity.tokenUuid +DA:523,14574 +DA:527,58421 +FN:527,FleetIdentity.tokenRegion +FNDA:58421,FleetIdentity.tokenRegion +DA:528,58421 +DA:532,26240 +FN:532,FleetIdentity.computeTokenId +FNDA:26240,FleetIdentity.computeTokenId +DA:533,26240 +DA:537,800 +FN:537,FleetIdentity.bonds +FNDA:800,FleetIdentity.bonds +DA:538,800 +DA:539,798 +DA:540,798 +DA:541,797 +DA:545,16 +FN:545,FleetIdentity.isOwnedOnly +FNDA:16,FleetIdentity.isOwnedOnly +DA:546,16 +DA:554,4330 +FN:554,FleetIdentity.operatorOf +FNDA:4330,FleetIdentity.operatorOf +DA:555,4330 +DA:556,4330 +BRDA:556,23,0,4298 +DA:557,4298 +DA:582,1343 +FN:582,FleetIdentity.buildHighestBondedUuidBundle +FNDA:1343,FleetIdentity.buildHighestBondedUuidBundle +DA:587,1343 +BRDA:587,24,0,- +DA:588,1343 +BRDA:588,25,0,1 +DA:590,1342 +DA:591,1342 +DA:593,1342 +DA:610,8 +FN:610,FleetIdentity.buildCountryOnlyBundle +FNDA:8,FleetIdentity.buildCountryOnlyBundle +DA:615,8 +BRDA:615,26,0,2 +DA:617,6 +DA:619,6 +DA:621,6 +DA:633,1370 +FN:633,FleetIdentity._buildHighestBondedUuidBundle +FNDA:1370,FleetIdentity._buildHighestBondedUuidBundle +DA:638,1370 +DA:640,1370 +DA:643,1370 +DA:644,3021 +DA:647,3021 +DA:650,3021 +DA:655,0 +DA:662,6042 +FN:662,FleetIdentity._appendTierUuids +FNDA:6042,FleetIdentity._appendTierUuids +DA:668,6042 +DA:669,6042 +DA:670,6042 +DA:671,6042 +DA:673,6042 +DA:674,13350 +DA:675,13350 +DA:677,0 +DA:685,4 +FN:685,FleetIdentity.getActiveCountries +FNDA:4,FleetIdentity.getActiveCountries +DA:686,4 +DA:691,3 +FN:691,FleetIdentity.getActiveAdminAreas +FNDA:3,FleetIdentity.getActiveAdminAreas +DA:693,3 +DA:694,3 +DA:695,3 +DA:696,2 +DA:700,3 +DA:701,3 +DA:702,3 +DA:703,2 +DA:704,2 +DA:705,2 +DA:706,3 +DA:709,0 +DA:714,0 +FN:714,FleetIdentity.getCountryAdminAreas +FNDA:0,FleetIdentity.getCountryAdminAreas +DA:715,0 +DA:720,15965 +FN:720,FleetIdentity.makeAdminRegion +FNDA:15965,FleetIdentity.makeAdminRegion +DA:721,15965 +DA:731,4477 +FN:731,FleetIdentity._countryFromRegion +FNDA:4477,FleetIdentity._countryFromRegion +DA:732,4477 +DA:736,5 +FN:736,FleetIdentity._adminFromRegion +FNDA:5,FleetIdentity._adminFromRegion +DA:737,5 +DA:742,54970 +FN:742,FleetIdentity._isCountryRegion +FNDA:54970,FleetIdentity._isCountryRegion +DA:743,54970 +DA:749,26513 +FN:749,FleetIdentity._pullBond +FNDA:26513,FleetIdentity._pullBond +DA:750,26513 +BRDA:750,27,0,26512 +DA:751,26512 +DA:756,936 +FN:756,FleetIdentity._refundBond +FNDA:936,FleetIdentity._refundBond +DA:757,936 +BRDA:757,28,0,935 +DA:758,935 +DA:765,25 +FN:765,FleetIdentity._clearUuidOwnership +FNDA:25,FleetIdentity._clearUuidOwnership +DA:766,25 +DA:767,25 +DA:768,25 +DA:769,25 +DA:770,25 +DA:775,0 +FN:775,FleetIdentity._decrementUuidCount +FNDA:0,FleetIdentity._decrementUuidCount +DA:776,0 +DA:777,0 +BRDA:777,29,0,- +BRDA:777,29,1,- +DA:778,0 +DA:780,0 +DA:788,626 +FN:788,FleetIdentity._cleanupFleetFromTier +FNDA:626,FleetIdentity._cleanupFleetFromTier +DA:789,626 +DA:790,626 +DA:791,626 +DA:792,626 +DA:793,626 +DA:800,23906 +FN:800,FleetIdentity._mintFleetToken +FNDA:23906,FleetIdentity._mintFleetToken +DA:801,23906 +DA:802,23906 +DA:803,23906 +DA:804,23906 +DA:805,23906 +DA:810,2277 +FN:810,FleetIdentity._mintFleetTokenTo +FNDA:2277,FleetIdentity._mintFleetTokenTo +DA:811,2277 +DA:812,2277 +DA:813,2277 +DA:814,2277 +DA:815,2277 +DA:824,26703 +FN:824,FleetIdentity._register +FNDA:26703,FleetIdentity._register +DA:825,26703 +DA:826,26703 +DA:827,26703 +DA:828,12140 +DA:830,26703 +BRDA:830,30,0,10 +BRDA:830,30,1,2 +DA:832,10 +DA:833,10 +BRDA:833,31,0,- +DA:834,10 +DA:836,10 +DA:837,10 +DA:838,10 +DA:840,10 +DA:843,10 +DA:845,10 +DA:846,26693 +BRDA:846,32,0,23906 +BRDA:846,32,1,2 +DA:848,23906 +DA:849,23906 +DA:850,23906 +DA:851,23906 +DA:854,23906 +DA:857,23906 +DA:859,23906 +DA:862,2787 +DA:863,2787 +BRDA:863,33,0,518 +DA:864,2269 +BRDA:864,34,0,2 +DA:865,2267 +DA:867,2267 +DA:868,2267 +DA:870,2267 +DA:873,2267 +DA:875,2267 +DA:880,293 +FN:880,FleetIdentity._promote +FNDA:293,FleetIdentity._promote +DA:881,293 +DA:882,293 +DA:883,293 +BRDA:883,35,0,260 +DA:885,33 +DA:886,33 +DA:887,33 +BRDA:887,36,0,- +DA:888,33 +BRDA:888,37,0,1 +DA:889,32 +BRDA:889,38,0,1 +DA:891,31 +DA:892,31 +DA:893,31 +DA:894,31 +DA:897,31 +DA:898,31 +DA:899,31 +DA:900,31 +DA:903,31 +DA:905,31 +DA:909,9 +FN:909,FleetIdentity._demote +FNDA:9,FleetIdentity._demote +DA:910,9 +DA:911,9 +DA:912,9 +BRDA:912,39,0,1 +DA:914,8 +DA:915,8 +DA:916,8 +BRDA:916,40,0,- +DA:917,8 +BRDA:917,41,0,1 +DA:919,7 +DA:920,7 +DA:921,7 +DA:922,7 +DA:925,7 +DA:926,7 +DA:927,7 +DA:928,7 +DA:929,7 +DA:932,7 +DA:934,7 +DA:938,26704 +FN:938,FleetIdentity._validateExplicitTier +FNDA:26704,FleetIdentity._validateExplicitTier +DA:939,26704 +BRDA:939,42,0,- +DA:940,26704 +BRDA:940,43,0,1 +DA:946,1370 +FN:946,FleetIdentity._findMaxTierIndex +FNDA:1370,FleetIdentity._findMaxTierIndex +DA:951,1370 +DA:952,1370 +DA:954,1370 +DA:955,1370 +BRDA:955,44,0,380 +DA:956,1370 +DA:976,22 +FN:976,FleetIdentity._findCheapestInclusionTier +FNDA:22,FleetIdentity._findCheapestInclusionTier +DA:981,22 +DA:982,22 +DA:983,22 +DA:985,22 +DA:990,22 +DA:991,26 +DA:992,26 +DA:994,26 +BRDA:994,45,0,11 +DA:995,11 +DA:1000,15 +DA:1001,15 +DA:1002,15 +DA:1003,15 +DA:1007,11 +BRDA:1007,46,0,8 +DA:1008,8 +DA:1011,3 +DA:1016,26221 +FN:1016,FleetIdentity._addToTier +FNDA:26221,FleetIdentity._addToTier +DA:1017,26221 +DA:1018,26221 +DA:1021,26221 +BRDA:1021,47,0,11237 +DA:1022,11237 +DA:1027,664 +FN:1027,FleetIdentity._removeFromTier +FNDA:664,FleetIdentity._removeFromTier +DA:1028,664 +DA:1029,664 +DA:1030,664 +DA:1032,664 +BRDA:1032,48,0,3 +DA:1033,3 +DA:1034,3 +DA:1035,3 +DA:1037,664 +DA:1041,633 +FN:1041,FleetIdentity._trimTierCount +FNDA:633,FleetIdentity._trimTierCount +DA:1042,633 +DA:1043,1294 +DA:1044,661 +DA:1046,633 +DA:1052,26183 +FN:1052,FleetIdentity._addToRegionIndex +FNDA:26183,FleetIdentity._addToRegionIndex +DA:1053,26183 +BRDA:1053,49,0,11881 +BRDA:1053,49,1,3500 +DA:1055,11881 +DA:1056,11881 +BRDA:1056,50,0,4143 +DA:1057,4143 +DA:1058,4143 +DA:1062,14302 +BRDA:1062,51,0,4442 +DA:1063,4442 +DA:1065,4442 +BRDA:1065,52,0,3500 +DA:1066,3500 +DA:1067,3500 +DA:1069,4442 +DA:1070,4442 +DA:1076,626 +FN:1076,FleetIdentity._removeFromRegionIndex +FNDA:626,FleetIdentity._removeFromRegionIndex +DA:1077,626 +DA:1079,623 +BRDA:1079,54,0,588 +BRDA:1079,54,1,4 +DA:1080,588 +DA:1081,588 +DA:1082,588 +BRDA:1082,55,0,588 +DA:1084,588 +DA:1086,585 +DA:1087,585 +DA:1088,585 +BRDA:1088,57,0,418 +DA:1089,418 +DA:1090,418 +DA:1091,418 +DA:1093,585 +DA:1094,585 +DA:1098,35 +DA:1099,35 +BRDA:1099,58,0,35 +DA:1100,35 +DA:1101,35 +DA:1102,35 +DA:1103,35 +DA:1104,35 +BRDA:1104,59,0,- +DA:1105,0 +DA:1106,0 +DA:1107,0 +DA:1109,35 +DA:1110,35 +DA:1113,35 +BRDA:1113,60,0,35 +DA:1114,35 +DA:1115,35 +BRDA:1115,61,0,35 +DA:1116,35 +DA:1117,35 +DA:1118,35 +BRDA:1118,62,0,4 +DA:1119,4 +DA:1120,4 +DA:1121,4 +DA:1123,35 +DA:1124,35 +DA:1135,26915 +FN:1135,FleetIdentity._update +FNDA:26915,FleetIdentity._update +DA:1136,26915 +DA:1140,26915 +DA:1141,26915 +BRDA:1141,63,0,3 +DA:1142,3 +DA:1145,26915 +DA:1148,0 +FN:1148,FleetIdentity._increaseBalance +FNDA:0,FleetIdentity._increaseBalance +DA:1149,0 +DA:1152,3 +FN:1152,FleetIdentity.supportsInterface +FNDA:3,FleetIdentity.supportsInterface +DA:1153,3 +FNF:53 +FNH:50 +LF:387 +LH:371 +BRF:67 +BRH:52 +end_of_record +TN: +SF:src/swarms/ServiceProvider.sol +DA:27,1942 +FN:27,ServiceProvider.registerProvider +FNDA:1942,ServiceProvider.registerProvider +DA:28,1942 +BRDA:28,0,0,1 +DA:29,1 +DA:32,1941 +DA:34,1941 +DA:36,1941 +DA:38,1941 +DA:43,276 +FN:43,ServiceProvider.burn +FNDA:276,ServiceProvider.burn +DA:44,276 +BRDA:44,1,0,258 +DA:45,258 +DA:48,18 +DA:50,18 +DA:52,18 +FNF:2 +FNH:2 +LF:13 +LH:13 +BRF:2 +BRH:2 +end_of_record +TN: +SF:src/swarms/SwarmRegistryL1.sol +DA:83,318 +FN:83,SwarmRegistryL1.computeSwarmId +FNDA:318,SwarmRegistryL1.computeSwarmId +DA:88,318 +DA:91,69 +FN:91,SwarmRegistryL1.constructor +FNDA:69,SwarmRegistryL1.constructor +DA:92,69 +BRDA:92,0,0,3 +DA:93,3 +DA:95,66 +DA:96,66 +DA:106,578 +FN:106,SwarmRegistryL1.registerSwarm +FNDA:578,SwarmRegistryL1.registerSwarm +DA:113,578 +BRDA:113,1,0,1 +DA:114,1 +DA:116,577 +BRDA:116,2,0,258 +DA:117,258 +DA:119,319 +BRDA:119,3,0,2 +DA:120,2 +DA:124,317 +BRDA:124,4,0,1 +DA:125,1 +DA:127,316 +BRDA:127,5,0,- +DA:128,0 +DA:131,315 +DA:133,315 +BRDA:133,6,0,1 +DA:134,1 +DA:137,314 +DA:138,314 +DA:139,314 +DA:140,314 +DA:141,314 +DA:142,314 +DA:144,314 +DA:145,314 +DA:147,314 +DA:149,314 +DA:154,8 +FN:154,SwarmRegistryL1.acceptSwarm +FNDA:8,SwarmRegistryL1.acceptSwarm +DA:155,8 +DA:156,8 +BRDA:156,7,0,1 +DA:158,7 +DA:159,7 +BRDA:159,8,0,2 +DA:161,5 +BRDA:161,9,0,1 +DA:162,1 +DA:164,4 +DA:165,4 +DA:170,4 +FN:170,SwarmRegistryL1.rejectSwarm +FNDA:4,SwarmRegistryL1.rejectSwarm +DA:171,4 +DA:172,4 +BRDA:172,10,0,- +DA:174,4 +DA:175,4 +BRDA:175,11,0,1 +DA:177,3 +BRDA:177,12,0,1 +DA:178,1 +DA:180,2 +DA:181,2 +DA:187,7 +FN:187,SwarmRegistryL1.updateSwarmFilter +FNDA:7,SwarmRegistryL1.updateSwarmFilter +DA:188,7 +DA:189,7 +BRDA:189,13,0,1 +DA:190,1 +DA:192,6 +BRDA:192,14,0,1 +DA:193,1 +DA:195,5 +BRDA:195,15,0,2 +DA:196,2 +DA:199,3 +DA:201,3 +DA:203,3 +DA:209,4 +FN:209,SwarmRegistryL1.updateSwarmProvider +FNDA:4,SwarmRegistryL1.updateSwarmProvider +DA:210,4 +DA:211,4 +BRDA:211,16,0,1 +DA:212,1 +DA:214,3 +BRDA:214,17,0,1 +DA:215,1 +DA:217,2 +BRDA:217,18,0,- +DA:218,0 +DA:221,1 +DA:223,1 +DA:225,1 +DA:227,1 +DA:232,7 +FN:232,SwarmRegistryL1.deleteSwarm +FNDA:7,SwarmRegistryL1.deleteSwarm +DA:233,7 +DA:234,7 +BRDA:234,19,0,1 +DA:235,1 +DA:237,6 +BRDA:237,20,0,1 +DA:238,1 +DA:241,5 +DA:243,5 +DA:245,5 +DA:247,5 +DA:254,27 +FN:254,SwarmRegistryL1.isSwarmValid +FNDA:27,SwarmRegistryL1.isSwarmValid +DA:255,27 +DA:256,27 +BRDA:256,21,0,1 +DA:259,26 +DA:261,26 +BRDA:261,22,0,26 +DA:262,19 +DA:263,7 +BRDA:263,22,1,7 +DA:264,7 +DA:270,6 +FN:270,SwarmRegistryL1.purgeOrphanedSwarm +FNDA:6,SwarmRegistryL1.purgeOrphanedSwarm +DA:271,6 +DA:272,6 +BRDA:272,23,0,1 +DA:274,5 +DA:275,5 +BRDA:275,24,0,1 +DA:277,4 +DA:279,4 +DA:281,4 +DA:283,4 +DA:290,7 +FN:290,SwarmRegistryL1.checkMembership +FNDA:7,SwarmRegistryL1.checkMembership +DA:291,7 +DA:292,7 +BRDA:292,25,0,1 +DA:293,1 +DA:297,6 +DA:298,6 +BRDA:298,26,0,1 +DA:300,5 +DA:301,5 +DA:303,0 +DA:307,5 +BRDA:307,27,0,5 +DA:309,5 +DA:314,5 +DA:315,5 +DA:317,0 +DA:319,4 +DA:320,4 +DA:321,4 +DA:323,4 +DA:324,4 +DA:326,4 +DA:327,4 +DA:328,4 +DA:330,4 +DA:336,9 +FN:336,SwarmRegistryL1._removeFromUuidSwarms +FNDA:9,SwarmRegistryL1._removeFromUuidSwarms +DA:337,9 +DA:338,9 +DA:339,9 +DA:341,9 +DA:342,9 +DA:343,9 +DA:344,9 +DA:353,12 +FN:353,SwarmRegistryL1._readFingerprint +FNDA:12,SwarmRegistryL1._readFingerprint +DA:354,12 +DA:355,12 +DA:356,12 +DA:359,12 +DA:362,12 +DA:363,12 +DA:364,24 +DA:366,24 +DA:370,12 +DA:371,12 +DA:372,12 +DA:374,12 +FNF:13 +FNH:13 +LF:143 +LH:139 +BRF:29 +BRH:26 +end_of_record +TN: +SF:src/swarms/SwarmRegistryUniversal.sol +DA:89,581 +FN:89,SwarmRegistryUniversal.computeSwarmId +FNDA:581,SwarmRegistryUniversal.computeSwarmId +DA:90,581 +DA:93,77 +FN:93,SwarmRegistryUniversal.constructor +FNDA:77,SwarmRegistryUniversal.constructor +DA:94,77 +BRDA:94,0,0,3 +DA:95,3 +DA:97,74 +DA:98,74 +DA:108,841 +FN:108,SwarmRegistryUniversal.registerSwarm +FNDA:841,SwarmRegistryUniversal.registerSwarm +DA:115,841 +BRDA:115,1,0,1 +DA:116,1 +DA:118,840 +BRDA:118,2,0,258 +DA:119,258 +DA:121,582 +BRDA:121,3,0,1 +DA:122,1 +DA:124,581 +BRDA:124,4,0,1 +DA:125,1 +DA:129,580 +BRDA:129,5,0,1 +DA:130,1 +DA:132,579 +BRDA:132,6,0,- +DA:133,0 +DA:136,578 +DA:138,578 +BRDA:138,7,0,1 +DA:139,1 +DA:142,577 +DA:143,577 +DA:144,577 +DA:145,577 +DA:146,577 +DA:147,577 +DA:148,577 +DA:150,577 +DA:152,577 +DA:153,577 +DA:155,577 +DA:160,10 +FN:160,SwarmRegistryUniversal.acceptSwarm +FNDA:10,SwarmRegistryUniversal.acceptSwarm +DA:161,10 +DA:162,10 +BRDA:162,8,0,1 +DA:164,9 +DA:165,9 +BRDA:165,9,0,2 +DA:167,7 +BRDA:167,10,0,2 +DA:168,2 +DA:170,5 +DA:171,5 +DA:176,5 +FN:176,SwarmRegistryUniversal.rejectSwarm +FNDA:5,SwarmRegistryUniversal.rejectSwarm +DA:177,5 +DA:178,5 +BRDA:178,11,0,- +DA:180,5 +DA:181,5 +BRDA:181,12,0,1 +DA:183,4 +BRDA:183,13,0,1 +DA:184,1 +DA:186,3 +DA:187,3 +DA:193,7 +FN:193,SwarmRegistryUniversal.updateSwarmFilter +FNDA:7,SwarmRegistryUniversal.updateSwarmFilter +DA:194,7 +DA:195,7 +BRDA:195,14,0,1 +DA:196,1 +DA:198,6 +BRDA:198,15,0,1 +DA:199,1 +DA:201,5 +BRDA:201,16,0,1 +DA:202,1 +DA:204,4 +BRDA:204,17,0,1 +DA:205,1 +DA:208,3 +DA:209,3 +DA:210,3 +DA:212,3 +DA:218,4 +FN:218,SwarmRegistryUniversal.updateSwarmProvider +FNDA:4,SwarmRegistryUniversal.updateSwarmProvider +DA:219,4 +DA:220,4 +BRDA:220,18,0,1 +DA:221,1 +DA:223,3 +BRDA:223,19,0,1 +DA:224,1 +DA:226,2 +BRDA:226,20,0,- +DA:227,0 +DA:230,1 +DA:233,1 +DA:234,1 +DA:236,1 +DA:241,8 +FN:241,SwarmRegistryUniversal.deleteSwarm +FNDA:8,SwarmRegistryUniversal.deleteSwarm +DA:242,8 +DA:243,8 +BRDA:243,21,0,1 +DA:244,1 +DA:246,7 +BRDA:246,22,0,1 +DA:247,1 +DA:250,6 +DA:252,6 +DA:254,6 +DA:255,6 +DA:257,6 +DA:264,30 +FN:264,SwarmRegistryUniversal.isSwarmValid +FNDA:30,SwarmRegistryUniversal.isSwarmValid +DA:265,30 +DA:266,30 +BRDA:266,23,0,1 +DA:269,29 +DA:271,29 +BRDA:271,24,0,29 +DA:272,21 +DA:273,8 +BRDA:273,24,1,8 +DA:274,8 +DA:280,7 +FN:280,SwarmRegistryUniversal.purgeOrphanedSwarm +FNDA:7,SwarmRegistryUniversal.purgeOrphanedSwarm +DA:281,7 +DA:282,7 +BRDA:282,25,0,1 +DA:284,6 +DA:285,6 +BRDA:285,26,0,1 +DA:287,5 +DA:289,5 +DA:291,5 +DA:292,5 +DA:294,5 +DA:301,6 +FN:301,SwarmRegistryUniversal.checkMembership +FNDA:6,SwarmRegistryUniversal.checkMembership +DA:302,6 +DA:303,6 +BRDA:303,27,0,1 +DA:304,1 +DA:308,5 +DA:309,5 +BRDA:309,28,0,1 +DA:311,4 +DA:312,4 +DA:315,4 +DA:316,4 +DA:319,4 +DA:320,4 +DA:321,4 +DA:323,4 +DA:324,4 +DA:327,4 +DA:328,4 +DA:329,4 +DA:331,4 +DA:337,3 +FN:337,SwarmRegistryUniversal.getFilterData +FNDA:3,SwarmRegistryUniversal.getFilterData +DA:338,3 +BRDA:338,30,0,1 +DA:339,1 +DA:341,2 +DA:347,11 +FN:347,SwarmRegistryUniversal._removeFromUuidSwarms +FNDA:11,SwarmRegistryUniversal._removeFromUuidSwarms +DA:348,11 +DA:349,11 +DA:350,11 +DA:352,11 +DA:353,11 +DA:354,11 +DA:355,11 +DA:364,12 +FN:364,SwarmRegistryUniversal._readFingerprint +FNDA:12,SwarmRegistryUniversal._readFingerprint +DA:365,12 +DA:366,12 +DA:367,12 +DA:370,12 +DA:371,12 +DA:372,24 +DA:374,24 +DA:379,12 +DA:380,12 +DA:381,12 +DA:383,12 +FNF:14 +FNH:14 +LF:150 +LH:148 +BRF:31 +BRH:28 +end_of_record +TN: +SF:test/FleetIdentity.t.sol +DA:12,639 +FN:12,MockERC20.mint +FNDA:639,MockERC20.mint +DA:13,639 +DA:23,1 +FN:23,BadERC20.mint +FNDA:1,BadERC20.mint +DA:24,1 +DA:27,1 +FN:27,BadERC20.setFail +FNDA:1,BadERC20.setFail +DA:28,1 +DA:31,0 +FN:31,BadERC20.transfer +FNDA:0,BadERC20.transfer +DA:33,0 +DA:36,1 +FN:36,BadERC20.transferFrom +FNDA:1,BadERC20.transferFrom +DA:38,0 +FNF:5 +FNH:4 +LF:10 +LH:7 +BRF:0 +BRH:0 +end_of_record +TN: +SF:test/FleetIdentityFairness.t.sol +DA:12,403 +FN:12,MockERC20Fairness.mint +FNDA:403,MockERC20Fairness.mint +DA:13,403 +FNF:1 +FNH:1 +LF:2 +LH:2 +BRF:0 +BRH:0 +end_of_record +TN: +SF:test/Grants.t.sol +DA:9,0 +FN:9,MockToken.constructor +FNDA:0,MockToken.constructor +DA:10,0 +FNF:1 +FNH:0 +LF:2 +LH:0 +BRF:0 +BRH:0 +end_of_record +TN: +SF:test/Payment.t.sol +DA:12,0 +FN:12,MockToken.constructor +FNDA:0,MockToken.constructor +DA:13,0 +FNF:1 +FNH:0 +LF:2 +LH:0 +BRF:0 +BRH:0 +end_of_record +TN: +SF:test/QuotaControl.t.sol +DA:14,0 +FN:14,TestableQuotaControl.exposeCheckedResetClaimed +FNDA:0,TestableQuotaControl.exposeCheckedResetClaimed +DA:15,0 +DA:18,0 +FN:18,TestableQuotaControl.exposeCheckedUpdateClaimed +FNDA:0,TestableQuotaControl.exposeCheckedUpdateClaimed +DA:19,0 +FNF:2 +FNH:0 +LF:4 +LH:0 +BRF:0 +BRH:0 +end_of_record +TN: +SF:test/SwarmRegistryL1.t.sol +DA:13,66 +FN:13,MockBondTokenL1.mint +FNDA:66,MockBondTokenL1.mint +DA:14,66 +FNF:1 +FNH:1 +LF:2 +LH:2 +BRF:0 +BRH:0 +end_of_record +TN: +SF:test/SwarmRegistryUniversal.t.sol +DA:13,74 +FN:13,MockBondTokenUniv.mint +FNDA:74,MockBondTokenUniv.mint +DA:14,74 +FNF:1 +FNH:1 +LF:2 +LH:2 +BRF:0 +BRH:0 +end_of_record +TN: +SF:test/__helpers__/AccessControlUtils.sol +DA:9,0 +FN:9,AccessControlUtils.expectRevert_AccessControlUnauthorizedAccount +FNDA:0,AccessControlUtils.expectRevert_AccessControlUnauthorizedAccount +DA:10,0 +FNF:1 +FNH:0 +LF:2 +LH:0 +BRF:0 +BRH:0 +end_of_record +TN: +SF:test/bridge/L1Bridge.t.sol +DA:31,0 +FN:31,MockMailbox.setL1ToL2Failed +FNDA:0,MockMailbox.setL1ToL2Failed +DA:32,0 +DA:35,0 +FN:35,MockMailbox.setInclusion +FNDA:0,MockMailbox.setInclusion +DA:36,0 +DA:39,0 +FN:39,MockMailbox.setBaseCostReturn +FNDA:0,MockMailbox.setBaseCostReturn +DA:40,0 +DA:43,0 +FN:43,MockMailbox.expectBaseCostParams +FNDA:0,MockMailbox.expectBaseCostParams +DA:44,0 +DA:45,0 +DA:46,0 +DA:50,0 +FN:50,MockMailbox.requestL2Transaction +FNDA:0,MockMailbox.requestL2Transaction +DA:59,0 +DA:60,0 +DA:63,0 +DA:66,0 +FN:66,MockMailbox.proveL1ToL2TransactionStatus +FNDA:0,MockMailbox.proveL1ToL2TransactionStatus +DA:74,0 +BRDA:74,0,0,- +DA:75,0 +DA:77,0 +DA:80,0 +FN:80,MockMailbox.proveL2MessageInclusion +FNDA:0,MockMailbox.proveL2MessageInclusion +DA:86,0 +DA:89,0 +FN:89,MockMailbox.l2TransactionBaseCost +FNDA:0,MockMailbox.l2TransactionBaseCost +DA:95,0 +BRDA:95,1,0,- +BRDA:95,1,1,- +DA:96,0 +BRDA:96,2,0,- +BRDA:96,2,1,- +DA:97,0 +BRDA:97,3,0,- +BRDA:97,3,1,- +DA:98,0 +FNF:8 +FNH:0 +LF:25 +LH:0 +BRF:7 +BRH:0 +end_of_record +TN: +SF:test/bridge/MigrationNFT.t.sol +DA:12,0 +FN:12,MigrationNFTTestUtils.bridgeTokens +FNDA:0,MigrationNFTTestUtils.bridgeTokens +DA:20,0 +DA:21,0 +DA:22,0 +FNF:1 +FNH:0 +LF:4 +LH:0 +BRF:0 +BRH:0 +end_of_record +TN: +SF:test/contentsign/BaseContentSign.t.sol +DA:13,0 +FN:13,MockContentSign.setWhitelisted +FNDA:0,MockContentSign.setWhitelisted +DA:14,0 +DA:17,0 +FN:17,MockContentSign._userIsWhitelisted +FNDA:0,MockContentSign._userIsWhitelisted +DA:18,0 +FNF:2 +FNH:0 +LF:4 +LH:0 +BRF:0 +BRH:0 +end_of_record +TN: +SF:test/contentsign/ClickBounty.t.sol +DA:13,0 +FN:13,MockERC20.constructor +FNDA:0,MockERC20.constructor +DA:14,0 +DA:21,0 +FN:21,MockContentSign._userIsWhitelisted +FNDA:0,MockContentSign._userIsWhitelisted +DA:22,0 +FNF:2 +FNH:0 +LF:4 +LH:0 +BRF:0 +BRH:0 +end_of_record +TN: +SF:test/contentsign/PaymentMiddleware.t.sol +DA:16,0 +FN:16,MockToken.mint +FNDA:0,MockToken.mint +DA:17,0 +DA:24,0 +FN:24,MockWhitelist.mint +FNDA:0,MockWhitelist.mint +DA:25,0 +DA:32,0 +FN:32,MockContentSign._userIsWhitelisted +FNDA:0,MockContentSign._userIsWhitelisted +DA:33,0 +FNF:3 +FNH:0 +LF:6 +LH:0 +BRF:0 +BRH:0 +end_of_record +TN: +SF:test/paymasters/BasePaymaster.t.sol +DA:13,0 +FN:13,MockPaymaster._validateAndPayGeneralFlow +FNDA:0,MockPaymaster._validateAndPayGeneralFlow +DA:15,0 +DA:18,0 +FN:18,MockPaymaster._validateAndPayApprovalBasedFlow +FNDA:0,MockPaymaster._validateAndPayApprovalBasedFlow +DA:23,0 +FNF:2 +FNH:0 +LF:4 +LH:0 +BRF:0 +BRH:0 +end_of_record +TN: +SF:test/paymasters/WhitelistPaymaster.t.sol +DA:14,0 +FN:14,MockWhitelistPaymaster.mock_validateAndPayGeneralFlow +FNDA:0,MockWhitelistPaymaster.mock_validateAndPayGeneralFlow +DA:15,0 +DA:18,0 +FN:18,MockWhitelistPaymaster.mock_validateAndPayApprovalBasedFlow +FNDA:0,MockWhitelistPaymaster.mock_validateAndPayApprovalBasedFlow +DA:26,0 +FNF:2 +FNH:0 +LF:4 +LH:0 +BRF:0 +BRH:0 +end_of_record diff --git a/coverage3.lcov b/coverage3.lcov new file mode 100644 index 00000000..2a24175d --- /dev/null +++ b/coverage3.lcov @@ -0,0 +1,3337 @@ +TN: +SF:script/CheckBridge.s.sol +DA:13,0 +FN:13,CheckBridge.setUp +FNDA:0,CheckBridge.setUp +DA:14,0 +DA:15,0 +DA:18,0 +FN:18,CheckBridge.run +FNDA:0,CheckBridge.run +DA:19,0 +DA:20,0 +DA:22,0 +DA:23,0 +DA:25,0 +BRDA:25,0,0,- +BRDA:25,0,1,- +DA:26,0 +DA:28,0 +DA:30,0 +BRDA:30,1,0,- +BRDA:30,1,1,- +DA:31,0 +DA:32,0 +BRDA:32,2,0,- +BRDA:32,2,1,- +DA:33,0 +DA:35,0 +DA:38,0 +FNF:2 +FNH:0 +LF:17 +LH:0 +BRF:6 +BRH:0 +end_of_record +TN: +SF:script/ContentSignWhitelist.s.sol +DA:13,0 +FN:13,ContentSignWhitelist.setUp +FNDA:0,ContentSignWhitelist.setUp +DA:14,0 +DA:15,0 +DA:18,0 +FN:18,ContentSignWhitelist.run +FNDA:0,ContentSignWhitelist.run +DA:19,0 +DA:21,0 +BRDA:21,0,0,- +BRDA:21,0,1,- +DA:22,0 +DA:24,0 +DA:26,0 +DA:27,0 +DA:28,0 +FNF:2 +FNH:0 +LF:11 +LH:0 +BRF:2 +BRH:0 +end_of_record +TN: +SF:script/DeployClick.s.sol +DA:14,0 +FN:14,DeployClick.setUp +FNDA:0,DeployClick.setUp +DA:15,0 +DA:16,0 +DA:18,0 +DA:19,0 +DA:22,0 +FN:22,DeployClick.run +FNDA:0,DeployClick.run +DA:23,0 +DA:25,0 +DA:26,0 +DA:28,0 +DA:29,0 +DA:32,0 +DA:35,0 +DA:37,0 +DA:39,0 +DA:40,0 +DA:41,0 +FNF:2 +FNH:0 +LF:17 +LH:0 +BRF:0 +BRH:0 +end_of_record +TN: +SF:script/DeployContentSignEnterprise.s.sol +DA:15,0 +FN:15,DeployContentSignEnterprise.setUp +FNDA:0,DeployContentSignEnterprise.setUp +DA:16,0 +DA:17,0 +DA:18,0 +DA:21,0 +FN:21,DeployContentSignEnterprise.run +FNDA:0,DeployContentSignEnterprise.run +DA:22,0 +DA:24,0 +DA:26,0 +DA:28,0 +DA:29,0 +DA:30,0 +DA:31,0 +FNF:2 +FNH:0 +LF:12 +LH:0 +BRF:0 +BRH:0 +end_of_record +TN: +SF:script/DeployL1Bridge.s.sol +DA:22,0 +FN:22,DeployL1Bridge.setUp +FNDA:0,DeployL1Bridge.setUp +DA:23,0 +DA:24,0 +DA:25,0 +DA:26,0 +DA:28,0 +DA:29,0 +DA:30,0 +DA:31,0 +DA:34,0 +FN:34,DeployL1Bridge.run +FNDA:0,DeployL1Bridge.run +DA:35,0 +DA:37,0 +DA:39,0 +DA:40,0 +DA:41,0 +DA:43,0 +DA:45,0 +DA:46,0 +FNF:2 +FNH:0 +LF:18 +LH:0 +BRF:0 +BRH:0 +end_of_record +TN: +SF:script/DeployL1Ens.s.sol +DA:18,0 +FN:18,DeployL1Ens.run +FNDA:0,DeployL1Ens.run +DA:19,0 +DA:20,0 +DA:22,0 +DA:24,0 +BRDA:24,0,0,- +BRDA:24,0,1,- +DA:25,0 +DA:26,0 +BRDA:26,1,0,- +BRDA:26,1,1,- +DA:27,0 +DA:28,0 +DA:29,0 +DA:30,0 +DA:32,0 +DA:35,0 +DA:36,0 +DA:39,0 +DA:40,0 +DA:42,0 +DA:45,0 +DA:47,0 +BRDA:47,2,0,- +DA:48,0 +DA:49,0 +DA:55,0 +DA:56,0 +DA:59,0 +DA:60,0 +DA:62,0 +DA:63,0 +DA:65,0 +DA:66,0 +DA:68,0 +FNF:1 +FNH:0 +LF:30 +LH:0 +BRF:5 +BRH:0 +end_of_record +TN: +SF:script/DeployL1Nodl.s.sol +DA:17,0 +FN:17,DeployL1Nodl.setUp +FNDA:0,DeployL1Nodl.setUp +DA:18,0 +DA:19,0 +DA:21,0 +DA:22,0 +DA:25,0 +FN:25,DeployL1Nodl.run +FNDA:0,DeployL1Nodl.run +DA:26,0 +DA:28,0 +DA:30,0 +DA:32,0 +FNF:2 +FNH:0 +LF:10 +LH:0 +BRF:0 +BRH:0 +end_of_record +TN: +SF:script/DeployL2Bridge.s.sol +DA:19,0 +FN:19,DeployL2Bridge.setUp +FNDA:0,DeployL2Bridge.setUp +DA:20,0 +DA:21,0 +DA:23,0 +DA:24,0 +DA:27,0 +FN:27,DeployL2Bridge.run +FNDA:0,DeployL2Bridge.run +DA:28,0 +DA:30,0 +DA:32,0 +DA:33,0 +DA:34,0 +DA:36,0 +DA:38,0 +DA:39,0 +FNF:2 +FNH:0 +LF:14 +LH:0 +BRF:0 +BRH:0 +end_of_record +TN: +SF:script/DeployL2Nodl.s.sol +DA:17,0 +FN:17,DeployL2Nodl.setUp +FNDA:0,DeployL2Nodl.setUp +DA:18,0 +DA:19,0 +DA:21,0 +DA:22,0 +DA:25,0 +FN:25,DeployL2Nodl.run +FNDA:0,DeployL2Nodl.run +DA:26,0 +DA:28,0 +DA:29,0 +DA:30,0 +DA:32,0 +DA:34,0 +FNF:2 +FNH:0 +LF:12 +LH:0 +BRF:0 +BRH:0 +end_of_record +TN: +SF:script/DeployMigrationNFT.s.sol +DA:18,0 +FN:18,DeployMigrationNFT.setUp +FNDA:0,DeployMigrationNFT.setUp +DA:19,0 +DA:20,0 +DA:22,0 +DA:23,0 +DA:24,0 +DA:25,0 +DA:27,0 +DA:28,0 +DA:32,0 +FN:32,DeployMigrationNFT.run +FNDA:0,DeployMigrationNFT.run +DA:33,0 +DA:35,0 +DA:37,0 +DA:39,0 +FNF:2 +FNH:0 +LF:14 +LH:0 +BRF:0 +BRH:0 +end_of_record +TN: +SF:script/DeployNodlMigration.sol +DA:14,0 +FN:14,DeployNodlMigration.setUp +FNDA:0,DeployNodlMigration.setUp +DA:15,0 +DA:16,0 +DA:18,0 +DA:19,0 +DA:20,0 +DA:23,0 +FN:23,DeployNodlMigration.run +FNDA:0,DeployNodlMigration.run +DA:24,0 +DA:26,0 +DA:27,0 +DA:29,0 +DA:30,0 +DA:32,0 +DA:34,0 +DA:36,0 +DA:37,0 +FNF:2 +FNH:0 +LF:16 +LH:0 +BRF:0 +BRH:0 +end_of_record +TN: +SF:script/DeployRewards.sol +DA:18,0 +FN:18,DeployRewards.setUp +FNDA:0,DeployRewards.setUp +DA:19,0 +DA:20,0 +DA:21,0 +DA:22,0 +DA:23,0 +DA:24,0 +DA:27,0 +FN:27,DeployRewards.run +FNDA:0,DeployRewards.run +DA:28,0 +DA:29,0 +BRDA:29,0,0,- +DA:30,0 +DA:31,0 +DA:32,0 +DA:35,0 +DA:36,0 +DA:39,0 +DA:40,0 +DA:42,0 +DA:44,0 +FNF:2 +FNH:0 +LF:19 +LH:0 +BRF:1 +BRH:0 +end_of_record +TN: +SF:script/RewardsSig.s.sol +DA:15,0 +FN:15,RewardsSig.setUp +FNDA:0,RewardsSig.setUp +DA:16,0 +DA:17,0 +DA:18,0 +DA:19,0 +DA:22,0 +FN:22,RewardsSig.run +FNDA:0,RewardsSig.run +DA:23,0 +DA:25,0 +DA:26,0 +DA:27,0 +DA:29,0 +DA:31,0 +DA:33,0 +DA:34,0 +DA:36,0 +DA:37,0 +DA:39,0 +DA:40,0 +DA:42,0 +DA:43,0 +FNF:2 +FNH:0 +LF:20 +LH:0 +BRF:0 +BRH:0 +end_of_record +TN: +SF:src/Grants.sol +DA:56,0 +FN:56,Grants.constructor +FNDA:0,Grants.constructor +DA:57,0 +BRDA:57,0,0,- +DA:58,0 +DA:61,0 +DA:62,0 +DA:63,0 +DA:75,0 +FN:75,Grants.addVestingSchedule +FNDA:0,Grants.addVestingSchedule +DA:83,0 +DA:85,0 +DA:87,0 +DA:88,0 +BRDA:88,1,0,- +DA:89,0 +DA:90,0 +DA:92,0 +DA:94,0 +DA:96,0 +DA:106,0 +FN:106,Grants.validateVestingSchedule +FNDA:0,Grants.validateVestingSchedule +DA:110,0 +DA:111,0 +DA:112,0 +DA:113,0 +DA:114,0 +DA:123,0 +FN:123,Grants.claim +FNDA:0,Grants.claim +DA:124,0 +DA:125,0 +DA:127,0 +DA:129,0 +DA:130,0 +DA:131,0 +DA:132,0 +DA:133,0 +DA:134,0 +BRDA:134,2,0,- +DA:135,0 +DA:136,0 +DA:137,0 +DA:138,0 +DA:139,0 +DA:140,0 +DA:141,0 +DA:142,0 +BRDA:142,3,0,- +DA:143,0 +DA:144,0 +DA:145,0 +DA:148,0 +DA:152,0 +BRDA:152,4,0,- +BRDA:152,4,1,- +DA:153,0 +DA:154,0 +DA:156,0 +DA:167,0 +FN:167,Grants.renounce +FNDA:0,Grants.renounce +DA:168,0 +DA:170,0 +DA:171,0 +DA:172,0 +DA:173,0 +DA:174,0 +BRDA:174,5,0,- +DA:175,0 +DA:176,0 +DA:181,0 +BRDA:181,6,0,- +BRDA:181,6,1,- +DA:182,0 +DA:184,0 +DA:195,0 +FN:195,Grants.cancelVestingSchedules +FNDA:0,Grants.cancelVestingSchedules +DA:196,0 +DA:197,0 +DA:198,0 +DA:200,0 +DA:201,0 +DA:202,0 +DA:203,0 +DA:204,0 +DA:205,0 +DA:206,0 +BRDA:206,7,0,- +DA:207,0 +DA:208,0 +DA:209,0 +DA:210,0 +DA:211,0 +DA:212,0 +DA:213,0 +DA:214,0 +DA:215,0 +DA:216,0 +DA:217,0 +DA:219,0 +DA:223,0 +BRDA:223,8,0,- +DA:224,0 +DA:227,0 +BRDA:227,9,0,- +DA:228,0 +DA:231,0 +BRDA:231,10,0,- +DA:232,0 +DA:235,0 +DA:243,0 +FN:243,Grants.getGrantsCount +FNDA:0,Grants.getGrantsCount +DA:244,0 +DA:245,0 +DA:246,0 +DA:248,0 +DA:253,0 +FN:253,Grants._mustBeNonZero +FNDA:0,Grants._mustBeNonZero +DA:254,0 +BRDA:254,11,0,- +DA:255,0 +DA:259,0 +FN:259,Grants._mustBeNonZeroAddress +FNDA:0,Grants._mustBeNonZeroAddress +DA:260,0 +BRDA:260,12,0,- +DA:261,0 +DA:265,0 +FN:265,Grants._mustNotBeSelf +FNDA:0,Grants._mustNotBeSelf +DA:266,0 +BRDA:266,13,0,- +DA:267,0 +DA:271,0 +FN:271,Grants._mustBeEqualOrExceedMinAmount +FNDA:0,Grants._mustBeEqualOrExceedMinAmount +DA:272,0 +BRDA:272,14,0,- +DA:273,0 +DA:277,0 +FN:277,Grants._sanitizePageRange +FNDA:0,Grants._sanitizePageRange +DA:278,0 +DA:279,0 +BRDA:279,15,0,- +DA:280,0 +DA:282,0 +BRDA:282,16,0,- +DA:283,0 +DA:285,0 +FNF:12 +FNH:0 +LF:114 +LH:0 +BRF:19 +BRH:0 +end_of_record +TN: +SF:src/L1Nodl.sol +DA:18,0 +FN:18,L1Nodl.constructor +FNDA:0,L1Nodl.constructor +DA:19,0 +BRDA:19,0,0,- +DA:20,0 +DA:22,0 +DA:23,0 +DA:26,0 +FN:26,L1Nodl.mint +FNDA:0,L1Nodl.mint +DA:27,0 +DA:30,0 +FN:30,L1Nodl.clock +FNDA:0,L1Nodl.clock +DA:31,0 +DA:35,0 +FN:35,L1Nodl.CLOCK_MODE +FNDA:0,L1Nodl.CLOCK_MODE +DA:36,0 +DA:39,0 +FN:39,L1Nodl.nonces +FNDA:0,L1Nodl.nonces +DA:40,0 +DA:43,0 +FN:43,L1Nodl._update +FNDA:0,L1Nodl._update +DA:44,0 +FNF:6 +FNH:0 +LF:15 +LH:0 +BRF:1 +BRH:0 +end_of_record +TN: +SF:src/NODL.sol +DA:12,0 +FN:12,NODL.constructor +FNDA:0,NODL.constructor +DA:13,0 +DA:14,0 +DA:17,0 +FN:17,NODL.mint +FNDA:0,NODL.mint +DA:18,0 +DA:20,0 +FNF:2 +FNH:0 +LF:6 +LH:0 +BRF:0 +BRH:0 +end_of_record +TN: +SF:src/Payment.sol +DA:40,0 +FN:40,Payment.constructor +FNDA:0,Payment.constructor +DA:43,0 +DA:44,0 +DA:58,0 +FN:58,Payment.pay +FNDA:0,Payment.pay +DA:59,0 +DA:60,0 +DA:62,0 +BRDA:62,0,0,- +DA:63,0 +DA:66,0 +DA:67,0 +DA:69,0 +DA:70,0 +DA:83,0 +FN:83,Payment.withdraw +FNDA:0,Payment.withdraw +DA:84,0 +FNF:3 +FNH:0 +LF:14 +LH:0 +BRF:1 +BRH:0 +end_of_record +TN: +SF:src/QuotaControl.sol +DA:84,0 +FN:84,QuotaControl.constructor +FNDA:0,QuotaControl.constructor +DA:85,0 +DA:86,0 +DA:88,0 +DA:89,0 +DA:90,0 +DA:97,0 +FN:97,QuotaControl.setQuota +FNDA:0,QuotaControl.setQuota +DA:98,0 +DA:99,0 +DA:109,0 +FN:109,QuotaControl.setPeriod +FNDA:0,QuotaControl.setPeriod +DA:110,0 +DA:111,0 +DA:112,0 +DA:121,0 +FN:121,QuotaControl._checkedResetClaimed +FNDA:0,QuotaControl._checkedResetClaimed +DA:122,0 +BRDA:122,0,0,- +DA:123,0 +DA:126,0 +DA:127,0 +DA:140,0 +FN:140,QuotaControl._checkedUpdateClaimed +FNDA:0,QuotaControl._checkedUpdateClaimed +DA:141,0 +DA:142,0 +BRDA:142,1,0,- +DA:143,0 +DA:145,0 +DA:156,0 +FN:156,QuotaControl._mustBeWithinPeriodRange +FNDA:0,QuotaControl._mustBeWithinPeriodRange +DA:157,0 +BRDA:157,2,0,- +DA:158,0 +DA:160,0 +BRDA:160,3,0,- +DA:161,0 +FNF:6 +FNH:0 +LF:28 +LH:0 +BRF:4 +BRH:0 +end_of_record +TN: +SF:src/Rewards.sol +DA:137,0 +FN:137,Rewards.constructor +FNDA:0,Rewards.constructor +DA:145,0 +DA:147,0 +DA:148,0 +DA:149,0 +DA:157,0 +FN:157,Rewards.mintReward +FNDA:0,Rewards.mintReward +DA:158,0 +DA:159,0 +DA:161,0 +DA:162,0 +DA:165,0 +DA:166,0 +DA:168,0 +DA:176,0 +FN:176,Rewards.mintBatchReward +FNDA:0,Rewards.mintBatchReward +DA:177,0 +DA:178,0 +DA:180,0 +DA:182,0 +DA:184,0 +DA:186,0 +DA:187,0 +DA:189,0 +DA:192,0 +DA:194,0 +DA:196,0 +DA:197,0 +DA:199,0 +DA:201,0 +DA:208,0 +FN:208,Rewards.setBatchSubmitterRewardBasisPoints +FNDA:0,Rewards.setBatchSubmitterRewardBasisPoints +DA:209,0 +DA:210,0 +DA:211,0 +DA:213,0 +DA:220,0 +FN:220,Rewards._mustBeLessThanBasisPointsDivisor +FNDA:0,Rewards._mustBeLessThanBasisPointsDivisor +DA:221,0 +BRDA:221,0,0,- +DA:222,0 +DA:231,0 +FN:231,Rewards._mustBeExpectedSequence +FNDA:0,Rewards._mustBeExpectedSequence +DA:232,0 +BRDA:232,1,0,- +DA:233,0 +DA:241,0 +FN:241,Rewards._mustBeExpectedBatchSequence +FNDA:0,Rewards._mustBeExpectedBatchSequence +DA:242,0 +BRDA:242,2,0,- +DA:243,0 +DA:251,0 +FN:251,Rewards._mustBeValidBatchStructure +FNDA:0,Rewards._mustBeValidBatchStructure +DA:252,0 +BRDA:252,3,0,- +DA:253,0 +DA:263,0 +FN:263,Rewards._mustBeFromAuthorizedOracle +FNDA:0,Rewards._mustBeFromAuthorizedOracle +DA:264,0 +BRDA:264,4,0,- +DA:265,0 +DA:274,0 +FN:274,Rewards._batchSum +FNDA:0,Rewards._batchSum +DA:275,0 +DA:276,0 +DA:277,0 +DA:279,0 +DA:287,0 +FN:287,Rewards.digestReward +FNDA:0,Rewards.digestReward +DA:288,0 +DA:289,0 +DA:297,0 +FN:297,Rewards.digestBatchReward +FNDA:0,Rewards.digestBatchReward +DA:298,0 +DA:299,0 +DA:300,0 +DA:301,0 +DA:308,0 +FN:308,Rewards.latestBatchDetails +FNDA:0,Rewards.latestBatchDetails +DA:309,0 +FNF:13 +FNH:0 +LF:63 +LH:0 +BRF:5 +BRH:0 +end_of_record +TN: +SF:src/bridge/BridgeBase.sol +DA:70,0 +FN:70,BridgeBase.constructor +FNDA:0,BridgeBase.constructor +DA:71,0 +DA:72,0 +DA:73,0 +DA:75,0 +DA:76,0 +DA:79,0 +DA:80,0 +DA:81,0 +DA:90,0 +FN:90,BridgeBase._createVote +FNDA:0,BridgeBase._createVote +DA:91,0 +DA:93,0 +DA:99,0 +FN:99,BridgeBase._recordVote +FNDA:0,BridgeBase._recordVote +DA:100,0 +DA:102,0 +DA:104,0 +DA:108,0 +FN:108,BridgeBase._processVote +FNDA:0,BridgeBase._processVote +DA:109,0 +DA:110,0 +DA:111,0 +DA:116,0 +FN:116,BridgeBase._execute +FNDA:0,BridgeBase._execute +DA:117,0 +DA:118,0 +DA:119,0 +DA:121,0 +DA:164,0 +FN:164,BridgeBase._mustNotHaveExecutedYet +FNDA:0,BridgeBase._mustNotHaveExecutedYet +DA:165,0 +BRDA:165,0,0,- +DA:166,0 +DA:170,0 +FN:170,BridgeBase._mustBePastSafetyDelay +FNDA:0,BridgeBase._mustBePastSafetyDelay +DA:171,0 +BRDA:171,1,0,- +DA:172,0 +DA:176,0 +FN:176,BridgeBase._mustHaveEnoughVotes +FNDA:0,BridgeBase._mustHaveEnoughVotes +DA:177,0 +BRDA:177,2,0,- +DA:178,0 +DA:182,0 +FN:182,BridgeBase._mustHaveEnoughOracles +FNDA:0,BridgeBase._mustHaveEnoughOracles +DA:183,0 +BRDA:183,3,0,- +DA:184,0 +DA:188,0 +FN:188,BridgeBase._mustBeAnOracle +FNDA:0,BridgeBase._mustBeAnOracle +DA:189,0 +BRDA:189,4,0,- +DA:190,0 +DA:194,0 +FN:194,BridgeBase._mustNotExceedMaxOracles +FNDA:0,BridgeBase._mustNotExceedMaxOracles +DA:195,0 +BRDA:195,5,0,- +DA:196,0 +DA:200,0 +FN:200,BridgeBase._mustNotBeZeroMinVotes +FNDA:0,BridgeBase._mustNotBeZeroMinVotes +DA:201,0 +BRDA:201,6,0,- +DA:202,0 +DA:206,0 +FN:206,BridgeBase._mustNotHaveVotedYet +FNDA:0,BridgeBase._mustNotHaveVotedYet +DA:207,0 +BRDA:207,7,0,- +DA:208,0 +FNF:13 +FNH:0 +LF:49 +LH:0 +BRF:8 +BRH:0 +end_of_record +TN: +SF:src/bridge/GrantsMigration.sol +DA:57,0 +FN:57,GrantsMigration.constructor +FNDA:0,GrantsMigration.constructor +DA:60,0 +DA:70,0 +FN:70,GrantsMigration.bridge +FNDA:0,GrantsMigration.bridge +DA:73,0 +DA:74,0 +DA:76,0 +BRDA:76,0,0,- +BRDA:76,0,1,- +DA:77,0 +DA:78,0 +DA:80,0 +DA:88,0 +FN:88,GrantsMigration.grant +FNDA:0,GrantsMigration.grant +DA:89,0 +DA:90,0 +DA:91,0 +DA:92,0 +DA:93,0 +DA:94,0 +DA:103,0 +DA:108,0 +FN:108,GrantsMigration._mustNotBeChangingParameters +FNDA:0,GrantsMigration._mustNotBeChangingParameters +DA:114,0 +DA:116,0 +BRDA:116,1,0,- +DA:117,0 +DA:120,0 +DA:121,0 +BRDA:121,2,0,- +DA:122,0 +DA:125,0 +DA:127,0 +DA:128,0 +DA:129,0 +DA:130,0 +DA:131,0 +DA:132,0 +BRDA:132,3,0,- +DA:133,0 +DA:138,0 +FN:138,GrantsMigration._createProposal +FNDA:0,GrantsMigration._createProposal +DA:145,0 +BRDA:145,4,0,- +DA:146,0 +DA:148,0 +BRDA:148,5,0,- +DA:149,0 +DA:151,0 +DA:152,0 +DA:153,0 +DA:156,0 +DA:158,0 +BRDA:158,6,0,- +DA:159,0 +DA:161,0 +DA:162,0 +DA:165,0 +FN:165,GrantsMigration._proposalExists +FNDA:0,GrantsMigration._proposalExists +DA:166,0 +DA:169,0 +FN:169,GrantsMigration._flagAsExecuted +FNDA:0,GrantsMigration._flagAsExecuted +DA:170,0 +DA:173,0 +FN:173,GrantsMigration._incTotalVotes +FNDA:0,GrantsMigration._incTotalVotes +DA:174,0 +DA:177,0 +FN:177,GrantsMigration._updateLastVote +FNDA:0,GrantsMigration._updateLastVote +DA:178,0 +DA:181,0 +FN:181,GrantsMigration._totalVotes +FNDA:0,GrantsMigration._totalVotes +DA:182,0 +DA:185,0 +FN:185,GrantsMigration._lastVote +FNDA:0,GrantsMigration._lastVote +DA:186,0 +DA:189,0 +FN:189,GrantsMigration._executed +FNDA:0,GrantsMigration._executed +DA:190,0 +FNF:12 +FNH:0 +LF:59 +LH:0 +BRF:8 +BRH:0 +end_of_record +TN: +SF:src/bridge/L1Bridge.sol +DA:80,0 +FN:80,L1Bridge.constructor +FNDA:0,L1Bridge.constructor +DA:81,0 +BRDA:81,0,0,- +DA:82,0 +DA:84,0 +DA:85,0 +DA:86,0 +DA:94,0 +FN:94,L1Bridge.pause +FNDA:0,L1Bridge.pause +DA:95,0 +DA:99,0 +FN:99,L1Bridge.unpause +FNDA:0,L1Bridge.unpause +DA:100,0 +DA:115,0 +FN:115,L1Bridge.quoteL2BaseCost +FNDA:0,L1Bridge.quoteL2BaseCost +DA:120,0 +DA:130,0 +FN:130,L1Bridge.quoteL2BaseCostAtGasPrice +FNDA:0,L1Bridge.quoteL2BaseCostAtGasPrice +DA:135,0 +DA:152,0 +FN:152,L1Bridge.deposit +FNDA:0,L1Bridge.deposit +DA:159,0 +BRDA:159,1,0,- +DA:160,0 +DA:162,0 +BRDA:162,2,0,- +DA:163,0 +DA:166,0 +DA:168,0 +DA:169,0 +DA:171,0 +DA:175,0 +DA:177,0 +DA:183,0 +FN:183,L1Bridge.deposit +FNDA:0,L1Bridge.deposit +DA:189,0 +DA:202,0 +FN:202,L1Bridge.claimFailedDeposit +FNDA:0,L1Bridge.claimFailedDeposit +DA:210,0 +DA:211,0 +BRDA:211,3,0,- +DA:212,0 +DA:214,0 +DA:217,0 +BRDA:217,4,0,- +DA:218,0 +DA:220,0 +DA:221,0 +DA:222,0 +DA:234,0 +FN:234,L1Bridge.finalizeWithdrawal +FNDA:0,L1Bridge.finalizeWithdrawal +DA:241,0 +BRDA:241,5,0,- +DA:242,0 +DA:245,0 +DA:246,0 +DA:249,0 +DA:255,0 +BRDA:255,6,0,- +DA:256,0 +DA:259,0 +DA:261,0 +DA:262,0 +DA:276,0 +FN:276,L1Bridge._parseL2WithdrawalMessage +FNDA:0,L1Bridge._parseL2WithdrawalMessage +DA:282,0 +BRDA:282,7,0,- +DA:283,0 +DA:287,0 +DA:288,0 +BRDA:288,8,0,- +DA:289,0 +DA:291,0 +DA:292,0 +FNF:10 +FNH:0 +LF:56 +LH:0 +BRF:9 +BRH:0 +end_of_record +TN: +SF:src/bridge/L2Bridge.sol +DA:49,0 +FN:49,L2Bridge.onlyL1Bridge +FNDA:0,L2Bridge.onlyL1Bridge +DA:50,0 +BRDA:50,0,0,- +DA:51,0 +DA:65,0 +FN:65,L2Bridge.constructor +FNDA:0,L2Bridge.constructor +DA:66,0 +BRDA:66,1,0,- +DA:67,0 +DA:69,0 +DA:70,0 +DA:78,0 +FN:78,L2Bridge.pause +FNDA:0,L2Bridge.pause +DA:79,0 +DA:83,0 +FN:83,L2Bridge.unpause +FNDA:0,L2Bridge.unpause +DA:84,0 +DA:90,0 +FN:90,L2Bridge.initialize +FNDA:0,L2Bridge.initialize +DA:91,0 +BRDA:91,2,0,- +DA:92,0 +DA:94,0 +BRDA:94,3,0,- +DA:95,0 +DA:97,0 +DA:103,0 +FN:103,L2Bridge.finalizeDeposit +FNDA:0,L2Bridge.finalizeDeposit +DA:109,0 +BRDA:109,4,0,- +DA:110,0 +DA:112,0 +BRDA:112,5,0,- +DA:113,0 +DA:116,0 +DA:118,0 +DA:124,0 +FN:124,L2Bridge.withdraw +FNDA:0,L2Bridge.withdraw +DA:125,0 +BRDA:125,6,0,- +DA:126,0 +DA:128,0 +BRDA:128,7,0,- +DA:129,0 +DA:132,0 +DA:135,0 +DA:136,0 +DA:138,0 +FNF:7 +FNH:0 +LF:34 +LH:0 +BRF:8 +BRH:0 +end_of_record +TN: +SF:src/bridge/MigrationNFT.sol +DA:69,0 +FN:69,MigrationNFT.constructor +FNDA:0,MigrationNFT.constructor +DA:75,0 +BRDA:75,0,0,- +DA:76,0 +DA:78,0 +BRDA:78,1,0,- +DA:79,0 +DA:81,0 +BRDA:81,2,0,- +DA:82,0 +DA:85,0 +DA:86,0 +BRDA:86,3,0,- +DA:87,0 +DA:91,0 +DA:92,0 +DA:93,0 +DA:94,0 +DA:101,0 +FN:101,MigrationNFT.tokenURI +FNDA:0,MigrationNFT.tokenURI +DA:102,0 +DA:104,0 +DA:105,0 +DA:112,0 +FN:112,MigrationNFT.safeMint +FNDA:0,MigrationNFT.safeMint +DA:113,0 +DA:115,0 +DA:117,0 +DA:118,0 +DA:119,0 +DA:121,0 +DA:123,0 +DA:124,0 +BRDA:124,4,0,- +DA:125,0 +DA:128,0 +DA:129,0 +DA:130,0 +DA:131,0 +DA:132,0 +DA:136,0 +FN:136,MigrationNFT._computeLevelUps +FNDA:0,MigrationNFT._computeLevelUps +DA:141,0 +DA:142,0 +DA:147,0 +DA:148,0 +DA:149,0 +BRDA:149,5,0,- +DA:150,0 +DA:151,0 +DA:155,0 +BRDA:155,6,0,- +DA:156,0 +DA:160,0 +FN:160,MigrationNFT._mustNotHaveBeenClaimed +FNDA:0,MigrationNFT._mustNotHaveBeenClaimed +DA:161,0 +BRDA:161,7,0,- +DA:162,0 +DA:166,0 +FN:166,MigrationNFT._mustBeAnExistingProposal +FNDA:0,MigrationNFT._mustBeAnExistingProposal +DA:168,0 +BRDA:168,8,0,- +DA:169,0 +DA:173,0 +FN:173,MigrationNFT._mustBeExecuted +FNDA:0,MigrationNFT._mustBeExecuted +DA:174,0 +BRDA:174,9,0,- +DA:175,0 +DA:179,0 +FN:179,MigrationNFT._mustAlreadyBeHolderOrEnoughHoldersRemaining +FNDA:0,MigrationNFT._mustAlreadyBeHolderOrEnoughHoldersRemaining +DA:180,0 +DA:181,0 +BRDA:181,10,0,- +DA:182,0 +DA:186,0 +FN:186,MigrationNFT._update +FNDA:0,MigrationNFT._update +DA:187,0 +DA:188,0 +BRDA:188,11,0,- +DA:190,0 +DA:193,0 +FNF:9 +FNH:0 +LF:61 +LH:0 +BRF:12 +BRH:0 +end_of_record +TN: +SF:src/bridge/NODLMigration.sol +DA:35,0 +FN:35,NODLMigration.bridge +FNDA:0,NODLMigration.bridge +DA:36,0 +DA:37,0 +DA:39,0 +BRDA:39,0,0,- +BRDA:39,0,1,- +DA:40,0 +DA:41,0 +DA:43,0 +DA:50,0 +FN:50,NODLMigration.withdraw +FNDA:0,NODLMigration.withdraw +DA:51,0 +DA:52,0 +DA:55,0 +FN:55,NODLMigration._mustNotBeChangingParameters +FNDA:0,NODLMigration._mustNotBeChangingParameters +DA:56,0 +BRDA:56,1,0,- +DA:57,0 +DA:61,0 +FN:61,NODLMigration._proposalExists +FNDA:0,NODLMigration._proposalExists +DA:62,0 +DA:65,0 +FN:65,NODLMigration._createVote +FNDA:0,NODLMigration._createVote +DA:66,0 +DA:67,0 +DA:68,0 +DA:71,0 +FN:71,NODLMigration._withdraw +FNDA:0,NODLMigration._withdraw +DA:72,0 +DA:73,0 +DA:76,0 +FN:76,NODLMigration._flagAsExecuted +FNDA:0,NODLMigration._flagAsExecuted +DA:77,0 +DA:80,0 +FN:80,NODLMigration._incTotalVotes +FNDA:0,NODLMigration._incTotalVotes +DA:81,0 +DA:84,0 +FN:84,NODLMigration._updateLastVote +FNDA:0,NODLMigration._updateLastVote +DA:85,0 +DA:88,0 +FN:88,NODLMigration._totalVotes +FNDA:0,NODLMigration._totalVotes +DA:89,0 +DA:92,0 +FN:92,NODLMigration._lastVote +FNDA:0,NODLMigration._lastVote +DA:93,0 +DA:96,0 +FN:96,NODLMigration._executed +FNDA:0,NODLMigration._executed +DA:97,0 +FNF:12 +FNH:0 +LF:34 +LH:0 +BRF:3 +BRH:0 +end_of_record +TN: +SF:src/contentsign/BaseContentSign.sol +DA:17,0 +FN:17,BaseContentSign.safeMint +FNDA:0,BaseContentSign.safeMint +DA:18,0 +DA:20,0 +DA:21,0 +DA:22,0 +DA:25,0 +FN:25,BaseContentSign.tokenURI +FNDA:0,BaseContentSign.tokenURI +DA:26,0 +DA:29,0 +FN:29,BaseContentSign.supportsInterface +FNDA:0,BaseContentSign.supportsInterface +DA:36,0 +DA:39,0 +FN:39,BaseContentSign._mustBeWhitelisted +FNDA:0,BaseContentSign._mustBeWhitelisted +DA:40,0 +BRDA:40,0,0,- +DA:41,0 +FNF:4 +FNH:0 +LF:12 +LH:0 +BRF:1 +BRH:0 +end_of_record +TN: +SF:src/contentsign/ClickBounty.sol +DA:158,0 +FN:158,ClickBounty.constructor +FNDA:0,ClickBounty.constructor +DA:159,0 +DA:160,0 +DA:162,0 +DA:163,0 +DA:164,0 +DA:181,0 +FN:181,ClickBounty.payEntryFee +FNDA:0,ClickBounty.payEntryFee +DA:182,0 +DA:184,0 +BRDA:184,0,0,- +DA:185,0 +DA:188,0 +DA:189,0 +BRDA:189,1,0,- +DA:190,0 +DA:193,0 +DA:194,0 +DA:209,0 +FN:209,ClickBounty.setEntryFee +FNDA:0,ClickBounty.setEntryFee +DA:210,0 +DA:211,0 +DA:212,0 +DA:224,0 +FN:224,ClickBounty.withdraw +FNDA:0,ClickBounty.withdraw +DA:225,0 +DA:226,0 +DA:254,0 +FN:254,ClickBounty.awardBounty +FNDA:0,ClickBounty.awardBounty +DA:255,0 +DA:257,0 +BRDA:257,2,0,- +DA:258,0 +DA:260,0 +DA:262,0 +BRDA:262,3,0,- +DA:263,0 +DA:265,0 +BRDA:265,4,0,- +DA:266,0 +DA:269,0 +DA:272,0 +DA:275,0 +DA:277,0 +DA:280,0 +DA:294,0 +FN:294,ClickBounty.getLeaderboard +FNDA:0,ClickBounty.getLeaderboard +DA:295,0 +DA:296,0 +DA:297,0 +DA:298,0 +DA:317,0 +FN:317,ClickBounty._updateLeaderboard +FNDA:0,ClickBounty._updateLeaderboard +DA:319,0 +BRDA:319,5,0,- +DA:320,0 +DA:321,0 +DA:322,0 +DA:326,0 +DA:327,0 +DA:328,0 +DA:329,0 +DA:330,0 +BRDA:330,6,0,- +DA:331,0 +DA:332,0 +DA:337,0 +BRDA:337,7,0,- +DA:338,0 +DA:339,0 +FNF:7 +FNH:0 +LF:56 +LH:0 +BRF:8 +BRH:0 +end_of_record +TN: +SF:src/contentsign/ClickContentSign.sol +DA:12,0 +FN:12,ClickContentSign.constructor +FNDA:0,ClickContentSign.constructor +DA:13,0 +DA:16,0 +FN:16,ClickContentSign._userIsWhitelisted +FNDA:0,ClickContentSign._userIsWhitelisted +DA:17,0 +FNF:2 +FNH:0 +LF:4 +LH:0 +BRF:0 +BRH:0 +end_of_record +TN: +SF:src/contentsign/EnterpriseContentSign.sol +DA:13,0 +FN:13,EnterpriseContentSign.constructor +FNDA:0,EnterpriseContentSign.constructor +DA:14,0 +DA:17,0 +FN:17,EnterpriseContentSign.supportsInterface +FNDA:0,EnterpriseContentSign.supportsInterface +DA:23,0 +DA:26,0 +FN:26,EnterpriseContentSign._userIsWhitelisted +FNDA:0,EnterpriseContentSign._userIsWhitelisted +DA:27,0 +FNF:3 +FNH:0 +LF:6 +LH:0 +BRF:0 +BRH:0 +end_of_record +TN: +SF:src/contentsign/PaymentMiddleware.sol +DA:21,0 +FN:21,PaymentMiddleware.constructor +FNDA:0,PaymentMiddleware.constructor +DA:24,0 +DA:25,0 +DA:26,0 +DA:27,0 +DA:30,0 +FN:30,PaymentMiddleware.safeMint +FNDA:0,PaymentMiddleware.safeMint +DA:32,0 +BRDA:32,0,0,- +DA:33,0 +DA:37,0 +DA:40,0 +DA:43,0 +FN:43,PaymentMiddleware.withdraw +FNDA:0,PaymentMiddleware.withdraw +DA:44,0 +DA:46,0 +DA:47,0 +DA:50,0 +FN:50,PaymentMiddleware.setFeeAmount +FNDA:0,PaymentMiddleware.setFeeAmount +DA:51,0 +DA:53,0 +DA:56,0 +FN:56,PaymentMiddleware.setTarget +FNDA:0,PaymentMiddleware.setTarget +DA:57,0 +DA:59,0 +DA:62,0 +FN:62,PaymentMiddleware.setWhitelist +FNDA:0,PaymentMiddleware.setWhitelist +DA:63,0 +DA:65,0 +DA:68,0 +FN:68,PaymentMiddleware.setFeeToken +FNDA:0,PaymentMiddleware.setFeeToken +DA:69,0 +DA:71,0 +FNF:7 +FNH:0 +LF:26 +LH:0 +BRF:1 +BRH:0 +end_of_record +TN: +SF:src/nameservice/ClickNameService.sol +DA:62,0 +FN:62,ClickNameService.constructor +FNDA:0,ClickNameService.constructor +DA:63,0 +DA:64,0 +DA:68,0 +FN:68,ClickNameService.resolve +FNDA:0,ClickNameService.resolve +DA:69,0 +DA:70,0 +DA:71,0 +BRDA:71,0,0,- +DA:72,0 +DA:74,0 +DA:80,0 +FN:80,ClickNameService.batchRegister +FNDA:0,ClickNameService.batchRegister +DA:81,0 +BRDA:81,1,0,- +DA:82,0 +DA:85,0 +DA:86,0 +DA:94,0 +FN:94,ClickNameService.setDefaultExpiry +FNDA:0,ClickNameService.setDefaultExpiry +DA:95,0 +DA:99,0 +FN:99,ClickNameService.register +FNDA:0,ClickNameService.register +DA:100,0 +DA:104,0 +FN:104,ClickNameService.registerWithExpiry +FNDA:0,ClickNameService.registerWithExpiry +DA:105,0 +BRDA:105,2,0,- +DA:106,0 +DA:109,0 +DA:110,0 +DA:111,0 +DA:112,0 +DA:115,0 +FN:115,ClickNameService._register +FNDA:0,ClickNameService._register +DA:116,0 +BRDA:116,3,0,- +DA:117,0 +DA:119,0 +BRDA:119,4,0,- +DA:120,0 +DA:123,0 +DA:124,0 +DA:125,0 +BRDA:125,5,0,- +BRDA:125,5,1,- +DA:126,0 +DA:128,0 +BRDA:128,6,0,- +DA:129,0 +DA:131,0 +DA:134,0 +DA:140,0 +FN:140,ClickNameService.supportsInterface +FNDA:0,ClickNameService.supportsInterface +DA:141,0 +DA:142,0 +DA:149,0 +FN:149,ClickNameService.burn +FNDA:0,ClickNameService.burn +DA:150,0 +DA:151,0 +DA:158,0 +FN:158,ClickNameService.removeExpired +FNDA:0,ClickNameService.removeExpired +DA:159,0 +DA:160,0 +BRDA:160,7,0,- +DA:161,0 +DA:163,0 +DA:164,0 +DA:171,0 +FN:171,ClickNameService.extendExpiry +FNDA:0,ClickNameService.extendExpiry +DA:172,0 +BRDA:172,8,0,- +DA:173,0 +DA:175,0 +BRDA:175,9,0,- +DA:176,0 +DA:179,0 +BRDA:179,10,0,- +BRDA:179,10,1,- +DA:180,0 +DA:182,0 +DA:187,0 +FN:187,ClickNameService._isAlphanumeric +FNDA:0,ClickNameService._isAlphanumeric +DA:188,0 +DA:189,0 +DA:190,0 +DA:191,0 +DA:193,0 +DA:197,0 +FN:197,ClickNameService._isAuthorized +FNDA:0,ClickNameService._isAuthorized +DA:198,0 +FNF:13 +FNH:0 +LF:66 +LH:0 +BRF:13 +BRH:0 +end_of_record +TN: +SF:src/nameservice/NameService.sol +DA:75,0 +FN:75,NameService.constructor +FNDA:0,NameService.constructor +DA:78,0 +DA:79,0 +DA:83,0 +FN:83,NameService.resolve +FNDA:0,NameService.resolve +DA:84,0 +DA:85,0 +DA:86,0 +BRDA:86,0,0,- +DA:87,0 +DA:89,0 +DA:95,0 +FN:95,NameService.batchRegister +FNDA:0,NameService.batchRegister +DA:96,0 +BRDA:96,1,0,- +DA:97,0 +DA:100,0 +DA:101,0 +DA:109,0 +FN:109,NameService.setDefaultExpiry +FNDA:0,NameService.setDefaultExpiry +DA:110,0 +DA:114,0 +FN:114,NameService.register +FNDA:0,NameService.register +DA:115,0 +DA:119,0 +FN:119,NameService.registerWithExpiry +FNDA:0,NameService.registerWithExpiry +DA:120,0 +BRDA:120,2,0,- +DA:121,0 +DA:124,0 +DA:125,0 +DA:126,0 +DA:127,0 +DA:130,0 +FN:130,NameService._register +FNDA:0,NameService._register +DA:131,0 +BRDA:131,3,0,- +DA:132,0 +DA:134,0 +BRDA:134,4,0,- +DA:135,0 +DA:138,0 +DA:139,0 +DA:140,0 +BRDA:140,5,0,- +BRDA:140,5,1,- +DA:141,0 +DA:143,0 +BRDA:143,6,0,- +DA:144,0 +DA:146,0 +DA:149,0 +DA:155,0 +FN:155,NameService.supportsInterface +FNDA:0,NameService.supportsInterface +DA:156,0 +DA:157,0 +DA:164,0 +FN:164,NameService.burn +FNDA:0,NameService.burn +DA:165,0 +DA:167,0 +DA:168,0 +DA:169,0 +DA:171,0 +DA:173,0 +DA:174,0 +DA:181,0 +FN:181,NameService.removeExpired +FNDA:0,NameService.removeExpired +DA:182,0 +DA:183,0 +BRDA:183,7,0,- +DA:184,0 +DA:186,0 +DA:187,0 +DA:194,0 +FN:194,NameService.extendExpiry +FNDA:0,NameService.extendExpiry +DA:195,0 +BRDA:195,8,0,- +DA:196,0 +DA:198,0 +BRDA:198,9,0,- +DA:199,0 +DA:202,0 +BRDA:202,10,0,- +BRDA:202,10,1,- +DA:203,0 +DA:205,0 +DA:210,0 +FN:210,NameService._isAlphanumeric +FNDA:0,NameService._isAlphanumeric +DA:211,0 +DA:212,0 +DA:213,0 +DA:214,0 +DA:216,0 +DA:220,0 +FN:220,NameService._isAuthorized +FNDA:0,NameService._isAuthorized +DA:221,0 +DA:228,0 +FN:228,NameService.setTextRecord +FNDA:0,NameService.setTextRecord +DA:229,0 +DA:231,0 +DA:232,0 +DA:233,0 +DA:235,0 +BRDA:235,12,0,- +DA:236,0 +DA:238,0 +BRDA:238,13,0,- +DA:239,0 +DA:241,0 +DA:242,0 +DA:249,0 +FN:249,NameService.getTextRecord +FNDA:0,NameService.getTextRecord +DA:250,0 +DA:251,0 +BRDA:251,14,0,- +DA:252,0 +DA:254,0 +FNF:15 +FNH:0 +LF:87 +LH:0 +BRF:16 +BRH:0 +end_of_record +TN: +SF:src/nameservice/PaymasterTest.sol +DA:23,0 +FN:23,PaymasterTest.register +FNDA:0,PaymasterTest.register +DA:24,0 +BRDA:24,0,0,- +DA:25,0 +DA:27,0 +BRDA:27,1,0,- +DA:28,0 +DA:31,0 +DA:32,0 +DA:33,0 +BRDA:33,2,0,- +BRDA:33,2,1,- +DA:34,0 +DA:36,0 +DA:39,0 +DA:43,0 +FN:43,PaymasterTest._isAlphanumeric +FNDA:0,PaymasterTest._isAlphanumeric +DA:44,0 +DA:45,0 +DA:46,0 +DA:47,0 +DA:49,0 +FNF:2 +FNH:0 +LF:17 +LH:0 +BRF:4 +BRH:0 +end_of_record +TN: +SF:src/nameservice/UniversalResolver.sol +DA:53,0 +FN:53,UniversalResolver.constructor +FNDA:0,UniversalResolver.constructor +DA:56,0 +DA:57,0 +DA:58,0 +DA:59,0 +DA:62,0 +DA:63,0 +DA:66,0 +FN:66,UniversalResolver.setUrl +FNDA:0,UniversalResolver.setUrl +DA:67,0 +DA:81,0 +FN:81,UniversalResolver._parseDnsDomain +FNDA:0,UniversalResolver._parseDnsDomain +DA:86,0 +DA:88,0 +DA:89,0 +DA:93,0 +DA:95,0 +DA:96,0 +DA:100,0 +DA:102,0 +DA:103,0 +DA:105,0 +DA:110,0 +FN:110,UniversalResolver.getStorageKey +FNDA:0,UniversalResolver.getStorageKey +DA:111,0 +DA:112,0 +DA:119,0 +FN:119,UniversalResolver.getTextRecordStorageKey +FNDA:0,UniversalResolver.getTextRecordStorageKey +DA:120,0 +DA:121,0 +DA:122,0 +DA:130,0 +FN:130,UniversalResolver.resolve +FNDA:0,UniversalResolver.resolve +DA:131,0 +DA:133,0 +BRDA:133,2,0,- +DA:134,0 +DA:137,0 +DA:138,0 +DA:140,0 +BRDA:140,3,0,- +BRDA:140,3,1,- +DA:141,0 +DA:142,0 +DA:143,0 +BRDA:143,4,0,- +BRDA:143,4,1,- +DA:144,0 +DA:145,0 +BRDA:145,5,0,- +DA:146,0 +DA:147,0 +BRDA:147,6,0,- +DA:148,0 +DA:152,0 +DA:155,0 +DA:156,0 +DA:158,0 +DA:159,0 +DA:161,0 +DA:167,0 +FN:167,UniversalResolver.resolveWithProof +FNDA:0,UniversalResolver.resolveWithProof +DA:168,0 +DA:169,0 +DA:172,0 +DA:174,0 +DA:176,0 +DA:178,0 +BRDA:178,7,0,- +DA:179,0 +DA:182,0 +BRDA:182,8,0,- +BRDA:182,8,1,- +DA:183,0 +DA:184,0 +BRDA:184,9,0,- +BRDA:184,9,1,- +DA:185,0 +DA:187,0 +DA:194,0 +FN:194,UniversalResolver.supportsInterface +FNDA:0,UniversalResolver.supportsInterface +DA:195,0 +DA:196,0 +FNF:8 +FNH:0 +LF:64 +LH:0 +BRF:12 +BRH:0 +end_of_record +TN: +SF:src/paymasters/BasePaymaster.sol +DA:33,0 +FN:33,BasePaymaster.constructor +FNDA:0,BasePaymaster.constructor +DA:34,0 +DA:35,0 +DA:38,0 +FN:38,BasePaymaster.validateAndPayForPaymasterTransaction +FNDA:0,BasePaymaster.validateAndPayForPaymasterTransaction +DA:43,0 +DA:46,0 +DA:48,0 +BRDA:48,0,0,- +DA:49,0 +DA:52,0 +DA:56,0 +DA:57,0 +DA:58,0 +DA:60,0 +BRDA:60,1,0,- +BRDA:60,1,1,- +DA:61,0 +DA:62,0 +BRDA:62,2,0,- +BRDA:62,2,1,- +DA:63,0 +DA:64,0 +DA:66,0 +DA:68,0 +DA:72,0 +DA:73,0 +BRDA:73,3,0,- +DA:74,0 +DA:77,0 +DA:80,0 +FN:80,BasePaymaster.postTransaction +FNDA:0,BasePaymaster.postTransaction +DA:88,0 +DA:93,0 +FN:93,BasePaymaster.withdraw +FNDA:0,BasePaymaster.withdraw +DA:94,0 +DA:96,0 +DA:97,0 +BRDA:97,4,0,- +DA:99,0 +DA:104,0 +FN:104,BasePaymaster._mustBeBootloader +FNDA:0,BasePaymaster._mustBeBootloader +DA:105,0 +BRDA:105,5,0,- +DA:106,0 +FNF:5 +FNH:0 +LF:33 +LH:0 +BRF:8 +BRH:0 +end_of_record +TN: +SF:src/paymasters/WhitelistPaymaster.sol +DA:22,0 +FN:22,WhitelistPaymaster.constructor +FNDA:0,WhitelistPaymaster.constructor +DA:23,0 +DA:26,0 +FN:26,WhitelistPaymaster.addWhitelistedContracts +FNDA:0,WhitelistPaymaster.addWhitelistedContracts +DA:27,0 +DA:29,0 +DA:30,0 +DA:33,0 +DA:36,0 +FN:36,WhitelistPaymaster.removeWhitelistedContracts +FNDA:0,WhitelistPaymaster.removeWhitelistedContracts +DA:37,0 +DA:39,0 +DA:40,0 +DA:43,0 +DA:46,0 +FN:46,WhitelistPaymaster.addWhitelistedUsers +FNDA:0,WhitelistPaymaster.addWhitelistedUsers +DA:47,0 +DA:49,0 +DA:50,0 +DA:53,0 +DA:56,0 +FN:56,WhitelistPaymaster.removeWhitelistedUsers +FNDA:0,WhitelistPaymaster.removeWhitelistedUsers +DA:57,0 +DA:59,0 +DA:60,0 +DA:63,0 +DA:66,0 +FN:66,WhitelistPaymaster._validateAndPayGeneralFlow +FNDA:0,WhitelistPaymaster._validateAndPayGeneralFlow +DA:67,0 +BRDA:67,0,0,- +DA:68,0 +DA:71,0 +BRDA:71,1,0,- +DA:72,0 +DA:76,0 +FN:76,WhitelistPaymaster._validateAndPayApprovalBasedFlow +FNDA:0,WhitelistPaymaster._validateAndPayApprovalBasedFlow +DA:81,0 +FNF:7 +FNH:0 +LF:29 +LH:0 +BRF:2 +BRH:0 +end_of_record +TN: +SF:src/swarms/FleetIdentity.sol +DA:227,883 +FN:227,FleetIdentity.constructor +FNDA:883,FleetIdentity.constructor +DA:228,883 +DA:229,883 +DA:243,12006 +FN:243,FleetIdentity.registerFleetCountry +FNDA:12006,FleetIdentity.registerFleetCountry +DA:248,12006 +BRDA:248,0,0,- +DA:249,12006 +BRDA:249,1,0,2 +DA:250,12004 +DA:251,12004 +DA:252,12004 +DA:267,14448 +FN:267,FleetIdentity.registerFleetLocal +FNDA:14448,FleetIdentity.registerFleetLocal +DA:272,14448 +BRDA:272,2,0,- +DA:273,14448 +BRDA:273,3,0,1 +DA:274,14447 +BRDA:274,4,0,2 +DA:275,14445 +DA:276,14445 +DA:277,14445 +DA:286,265 +FN:286,FleetIdentity.promote +FNDA:265,FleetIdentity.promote +DA:287,265 +DA:294,38 +FN:294,FleetIdentity.reassignTier +FNDA:38,FleetIdentity.reassignTier +DA:295,38 +DA:296,38 +BRDA:296,5,0,1 +DA:297,37 +BRDA:297,6,0,28 +BRDA:297,6,1,9 +DA:298,28 +DA:300,9 +DA:316,282 +FN:316,FleetIdentity.setOperator +FNDA:282,FleetIdentity.setOperator +DA:318,282 +BRDA:318,7,0,2 +DA:321,280 +BRDA:321,8,0,- +DA:322,0 +DA:325,280 +DA:328,280 +DA:329,280 +DA:332,280 +DA:335,280 +DA:338,280 +BRDA:338,9,0,278 +DA:340,278 +DA:342,278 +DA:345,1 +DA:367,905 +FN:367,FleetIdentity.burn +FNDA:905,FleetIdentity.burn +DA:368,905 +DA:370,905 +DA:371,905 +DA:372,905 +DA:373,905 +DA:374,905 +DA:376,905 +BRDA:376,10,0,27 +BRDA:376,10,1,574 +DA:378,27 +BRDA:378,11,0,2 +DA:380,25 +DA:381,25 +DA:382,25 +DA:384,25 +DA:387,878 +BRDA:387,12,0,259 +DA:388,259 +DA:391,619 +DA:392,619 +DA:395,619 +DA:397,619 +DA:398,619 +DA:400,45 +BRDA:400,13,0,45 +BRDA:400,13,1,574 +DA:402,45 +DA:403,45 +DA:404,45 +DA:408,574 +DA:412,619 +DA:414,619 +DA:427,24 +FN:427,FleetIdentity.claimUuid +FNDA:24,FleetIdentity.claimUuid +DA:428,24 +BRDA:428,14,0,1 +DA:429,23 +BRDA:429,15,0,2 +DA:432,21 +DA:433,21 +DA:434,21 +DA:436,21 +DA:439,21 +DA:440,21 +DA:442,21 +DA:444,21 +DA:454,29629 +FN:454,FleetIdentity.tierBond +FNDA:29629,FleetIdentity.tierBond +DA:455,29629 +DA:456,29629 +DA:462,9 +FN:462,FleetIdentity.localInclusionHint +FNDA:9,FleetIdentity.localInclusionHint +DA:467,9 +BRDA:467,16,0,- +DA:468,9 +BRDA:468,17,0,- +DA:469,9 +DA:470,7 +DA:478,8 +FN:478,FleetIdentity.countryInclusionHint +FNDA:8,FleetIdentity.countryInclusionHint +DA:479,8 +BRDA:479,18,0,- +DA:482,8 +DA:485,7 +DA:486,7 +DA:487,7 +DA:488,5 +DA:489,5 +DA:490,5 +BRDA:490,19,0,- +DA:492,7 +DA:496,4 +FN:496,FleetIdentity.highestActiveTier +FNDA:4,FleetIdentity.highestActiveTier +DA:497,4 +DA:498,4 +DA:499,1 +DA:503,35 +FN:503,FleetIdentity.tierMemberCount +FNDA:35,FleetIdentity.tierMemberCount +DA:504,35 +DA:508,2236 +FN:508,FleetIdentity.getTierMembers +FNDA:2236,FleetIdentity.getTierMembers +DA:509,2236 +DA:513,2 +FN:513,FleetIdentity.getTierUuids +FNDA:2,FleetIdentity.getTierUuids +DA:514,2 +DA:515,2 +DA:516,2 +DA:517,2 +DA:522,14479 +FN:522,FleetIdentity.tokenUuid +FNDA:14479,FleetIdentity.tokenUuid +DA:523,14479 +DA:527,57648 +FN:527,FleetIdentity.tokenRegion +FNDA:57648,FleetIdentity.tokenRegion +DA:528,57648 +DA:532,25985 +FN:532,FleetIdentity.computeTokenId +FNDA:25985,FleetIdentity.computeTokenId +DA:533,25985 +DA:537,800 +FN:537,FleetIdentity.bonds +FNDA:800,FleetIdentity.bonds +DA:538,800 +DA:539,798 +DA:540,798 +DA:541,797 +DA:545,16 +FN:545,FleetIdentity.isOwnedOnly +FNDA:16,FleetIdentity.isOwnedOnly +DA:546,16 +DA:554,4262 +FN:554,FleetIdentity.operatorOf +FNDA:4262,FleetIdentity.operatorOf +DA:555,4262 +DA:556,4262 +BRDA:556,23,0,4230 +DA:557,4230 +DA:582,1343 +FN:582,FleetIdentity.buildHighestBondedUuidBundle +FNDA:1343,FleetIdentity.buildHighestBondedUuidBundle +DA:587,1343 +BRDA:587,24,0,- +DA:588,1343 +BRDA:588,25,0,1 +DA:590,1342 +DA:591,1342 +DA:593,1342 +DA:610,8 +FN:610,FleetIdentity.buildCountryOnlyBundle +FNDA:8,FleetIdentity.buildCountryOnlyBundle +DA:615,8 +BRDA:615,26,0,2 +DA:617,6 +DA:619,6 +DA:621,6 +DA:633,1370 +FN:633,FleetIdentity._buildHighestBondedUuidBundle +FNDA:1370,FleetIdentity._buildHighestBondedUuidBundle +DA:638,1370 +DA:640,1370 +DA:643,1370 +DA:644,3017 +DA:647,3017 +DA:650,3017 +DA:655,0 +DA:662,6034 +FN:662,FleetIdentity._appendTierUuids +FNDA:6034,FleetIdentity._appendTierUuids +DA:668,6034 +DA:669,6034 +DA:670,6034 +DA:671,6034 +DA:673,6034 +DA:674,13262 +DA:675,13262 +DA:677,0 +DA:685,5 +FN:685,FleetIdentity.getActiveCountries +FNDA:5,FleetIdentity.getActiveCountries +DA:686,5 +DA:691,5 +FN:691,FleetIdentity.getActiveAdminAreas +FNDA:5,FleetIdentity.getActiveAdminAreas +DA:693,5 +DA:694,5 +DA:695,5 +DA:696,5 +DA:700,5 +DA:701,5 +DA:702,5 +DA:703,5 +DA:704,5 +DA:705,5 +DA:706,6 +DA:709,0 +DA:714,0 +FN:714,FleetIdentity.getCountryAdminAreas +FNDA:0,FleetIdentity.getCountryAdminAreas +DA:715,0 +DA:720,15847 +FN:720,FleetIdentity.makeAdminRegion +FNDA:15847,FleetIdentity.makeAdminRegion +DA:721,15847 +DA:731,4493 +FN:731,FleetIdentity._countryFromRegion +FNDA:4493,FleetIdentity._countryFromRegion +DA:732,4493 +DA:736,5 +FN:736,FleetIdentity._adminFromRegion +FNDA:5,FleetIdentity._adminFromRegion +DA:737,5 +DA:742,54446 +FN:742,FleetIdentity._isCountryRegion +FNDA:54446,FleetIdentity._isCountryRegion +DA:743,54446 +DA:749,26258 +FN:749,FleetIdentity._pullBond +FNDA:26258,FleetIdentity._pullBond +DA:750,26258 +BRDA:750,27,0,26257 +DA:751,26257 +DA:756,929 +FN:756,FleetIdentity._refundBond +FNDA:929,FleetIdentity._refundBond +DA:757,929 +BRDA:757,28,0,928 +DA:758,928 +DA:765,25 +FN:765,FleetIdentity._clearUuidOwnership +FNDA:25,FleetIdentity._clearUuidOwnership +DA:766,25 +DA:767,25 +DA:768,25 +DA:769,25 +DA:770,25 +DA:775,0 +FN:775,FleetIdentity._decrementUuidCount +FNDA:0,FleetIdentity._decrementUuidCount +DA:776,0 +DA:777,0 +BRDA:777,29,0,- +BRDA:777,29,1,- +DA:778,0 +DA:780,0 +DA:788,619 +FN:788,FleetIdentity._cleanupFleetFromTier +FNDA:619,FleetIdentity._cleanupFleetFromTier +DA:789,619 +DA:790,619 +DA:791,619 +DA:792,619 +DA:793,619 +DA:800,23712 +FN:800,FleetIdentity._mintFleetToken +FNDA:23712,FleetIdentity._mintFleetToken +DA:801,23712 +DA:802,23712 +DA:803,23712 +DA:804,23712 +DA:805,23712 +DA:810,2216 +FN:810,FleetIdentity._mintFleetTokenTo +FNDA:2216,FleetIdentity._mintFleetTokenTo +DA:811,2216 +DA:812,2216 +DA:813,2216 +DA:814,2216 +DA:815,2216 +DA:824,26448 +FN:824,FleetIdentity._register +FNDA:26448,FleetIdentity._register +DA:825,26448 +DA:826,26448 +DA:827,26448 +DA:828,12004 +DA:830,26448 +BRDA:830,30,0,10 +BRDA:830,30,1,2 +DA:832,10 +DA:833,10 +BRDA:833,31,0,- +DA:834,10 +DA:836,10 +DA:837,10 +DA:838,10 +DA:840,10 +DA:843,10 +DA:845,10 +DA:846,26438 +BRDA:846,32,0,23712 +BRDA:846,32,1,2 +DA:848,23712 +DA:849,23712 +DA:850,23712 +DA:851,23712 +DA:854,23712 +DA:857,23712 +DA:859,23712 +DA:862,2726 +DA:863,2726 +BRDA:863,33,0,518 +DA:864,2208 +BRDA:864,34,0,2 +DA:865,2206 +DA:867,2206 +DA:868,2206 +DA:870,2206 +DA:873,2206 +DA:875,2206 +DA:880,293 +FN:880,FleetIdentity._promote +FNDA:293,FleetIdentity._promote +DA:881,293 +DA:882,293 +DA:883,293 +BRDA:883,35,0,260 +DA:885,33 +DA:886,33 +DA:887,33 +BRDA:887,36,0,- +DA:888,33 +BRDA:888,37,0,1 +DA:889,32 +BRDA:889,38,0,1 +DA:891,31 +DA:892,31 +DA:893,31 +DA:894,31 +DA:897,31 +DA:898,31 +DA:899,31 +DA:900,31 +DA:903,31 +DA:905,31 +DA:909,9 +FN:909,FleetIdentity._demote +FNDA:9,FleetIdentity._demote +DA:910,9 +DA:911,9 +DA:912,9 +BRDA:912,39,0,1 +DA:914,8 +DA:915,8 +DA:916,8 +BRDA:916,40,0,- +DA:917,8 +BRDA:917,41,0,1 +DA:919,7 +DA:920,7 +DA:921,7 +DA:922,7 +DA:925,7 +DA:926,7 +DA:927,7 +DA:928,7 +DA:929,7 +DA:932,7 +DA:934,7 +DA:938,26449 +FN:938,FleetIdentity._validateExplicitTier +FNDA:26449,FleetIdentity._validateExplicitTier +DA:939,26449 +BRDA:939,42,0,- +DA:940,26449 +BRDA:940,43,0,1 +DA:946,1370 +FN:946,FleetIdentity._findMaxTierIndex +FNDA:1370,FleetIdentity._findMaxTierIndex +DA:951,1370 +DA:952,1370 +DA:954,1370 +DA:955,1370 +BRDA:955,44,0,378 +DA:956,1370 +DA:976,22 +FN:976,FleetIdentity._findCheapestInclusionTier +FNDA:22,FleetIdentity._findCheapestInclusionTier +DA:981,22 +DA:982,22 +DA:983,22 +DA:985,22 +DA:990,22 +DA:991,26 +DA:992,26 +DA:994,26 +BRDA:994,45,0,11 +DA:995,11 +DA:1000,15 +DA:1001,15 +DA:1002,15 +DA:1003,15 +DA:1007,11 +BRDA:1007,46,0,8 +DA:1008,8 +DA:1011,3 +DA:1016,25966 +FN:1016,FleetIdentity._addToTier +FNDA:25966,FleetIdentity._addToTier +DA:1017,25966 +DA:1018,25966 +DA:1021,25966 +BRDA:1021,47,0,11176 +DA:1022,11176 +DA:1027,657 +FN:1027,FleetIdentity._removeFromTier +FNDA:657,FleetIdentity._removeFromTier +DA:1028,657 +DA:1029,657 +DA:1030,657 +DA:1032,657 +BRDA:1032,48,0,3 +DA:1033,3 +DA:1034,3 +DA:1035,3 +DA:1037,657 +DA:1041,626 +FN:1041,FleetIdentity._trimTierCount +FNDA:626,FleetIdentity._trimTierCount +DA:1042,626 +DA:1043,1280 +DA:1044,654 +DA:1046,626 +DA:1052,25928 +FN:1052,FleetIdentity._addToRegionIndex +FNDA:25928,FleetIdentity._addToRegionIndex +DA:1053,25928 +BRDA:1053,49,0,11745 +BRDA:1053,49,1,3514 +DA:1055,11745 +DA:1056,11745 +BRDA:1056,50,0,4077 +DA:1057,4077 +DA:1058,4077 +DA:1062,14183 +BRDA:1062,51,0,4456 +DA:1063,4456 +DA:1065,4456 +BRDA:1065,52,0,3514 +DA:1066,3514 +DA:1067,3514 +DA:1069,4456 +DA:1070,4456 +DA:1076,619 +FN:1076,FleetIdentity._removeFromRegionIndex +FNDA:619,FleetIdentity._removeFromRegionIndex +DA:1077,619 +DA:1079,616 +BRDA:1079,54,0,579 +BRDA:1079,54,1,5 +DA:1080,579 +DA:1081,579 +DA:1082,579 +BRDA:1082,55,0,579 +DA:1084,579 +DA:1086,576 +DA:1087,576 +DA:1088,576 +BRDA:1088,57,0,416 +DA:1089,416 +DA:1090,416 +DA:1091,416 +DA:1093,576 +DA:1094,576 +DA:1098,37 +DA:1099,37 +BRDA:1099,58,0,37 +DA:1100,37 +DA:1101,37 +DA:1102,37 +DA:1103,37 +DA:1104,37 +BRDA:1104,59,0,1 +DA:1105,1 +DA:1106,1 +DA:1107,1 +DA:1109,37 +DA:1110,37 +DA:1113,37 +BRDA:1113,60,0,36 +DA:1114,36 +DA:1115,36 +BRDA:1115,61,0,36 +DA:1116,36 +DA:1117,36 +DA:1118,36 +BRDA:1118,62,0,5 +DA:1119,5 +DA:1120,5 +DA:1121,5 +DA:1123,36 +DA:1124,36 +DA:1135,26655 +FN:1135,FleetIdentity._update +FNDA:26655,FleetIdentity._update +DA:1136,26655 +DA:1140,26655 +DA:1141,26655 +BRDA:1141,63,0,3 +DA:1142,3 +DA:1145,26655 +DA:1148,0 +FN:1148,FleetIdentity._increaseBalance +FNDA:0,FleetIdentity._increaseBalance +DA:1149,0 +DA:1152,3 +FN:1152,FleetIdentity.supportsInterface +FNDA:3,FleetIdentity.supportsInterface +DA:1153,3 +FNF:53 +FNH:50 +LF:387 +LH:374 +BRF:67 +BRH:53 +end_of_record +TN: +SF:src/swarms/ServiceProvider.sol +DA:27,1942 +FN:27,ServiceProvider.registerProvider +FNDA:1942,ServiceProvider.registerProvider +DA:28,1942 +BRDA:28,0,0,1 +DA:29,1 +DA:32,1941 +DA:34,1941 +DA:36,1941 +DA:38,1941 +DA:43,276 +FN:43,ServiceProvider.burn +FNDA:276,ServiceProvider.burn +DA:44,276 +BRDA:44,1,0,258 +DA:45,258 +DA:48,18 +DA:50,18 +DA:52,18 +FNF:2 +FNH:2 +LF:13 +LH:13 +BRF:2 +BRH:2 +end_of_record +TN: +SF:src/swarms/SwarmRegistryL1.sol +DA:83,318 +FN:83,SwarmRegistryL1.computeSwarmId +FNDA:318,SwarmRegistryL1.computeSwarmId +DA:88,318 +DA:91,69 +FN:91,SwarmRegistryL1.constructor +FNDA:69,SwarmRegistryL1.constructor +DA:92,69 +BRDA:92,0,0,3 +DA:93,3 +DA:95,66 +DA:96,66 +DA:106,578 +FN:106,SwarmRegistryL1.registerSwarm +FNDA:578,SwarmRegistryL1.registerSwarm +DA:113,578 +BRDA:113,1,0,1 +DA:114,1 +DA:116,577 +BRDA:116,2,0,258 +DA:117,258 +DA:119,319 +BRDA:119,3,0,2 +DA:120,2 +DA:124,317 +BRDA:124,4,0,1 +DA:125,1 +DA:127,316 +BRDA:127,5,0,- +DA:128,0 +DA:131,315 +DA:133,315 +BRDA:133,6,0,1 +DA:134,1 +DA:137,314 +DA:138,314 +DA:139,314 +DA:140,314 +DA:141,314 +DA:142,314 +DA:144,314 +DA:145,314 +DA:147,314 +DA:149,314 +DA:154,8 +FN:154,SwarmRegistryL1.acceptSwarm +FNDA:8,SwarmRegistryL1.acceptSwarm +DA:155,8 +DA:156,8 +BRDA:156,7,0,1 +DA:158,7 +DA:159,7 +BRDA:159,8,0,2 +DA:161,5 +BRDA:161,9,0,1 +DA:162,1 +DA:164,4 +DA:165,4 +DA:170,4 +FN:170,SwarmRegistryL1.rejectSwarm +FNDA:4,SwarmRegistryL1.rejectSwarm +DA:171,4 +DA:172,4 +BRDA:172,10,0,- +DA:174,4 +DA:175,4 +BRDA:175,11,0,1 +DA:177,3 +BRDA:177,12,0,1 +DA:178,1 +DA:180,2 +DA:181,2 +DA:187,7 +FN:187,SwarmRegistryL1.updateSwarmFilter +FNDA:7,SwarmRegistryL1.updateSwarmFilter +DA:188,7 +DA:189,7 +BRDA:189,13,0,1 +DA:190,1 +DA:192,6 +BRDA:192,14,0,1 +DA:193,1 +DA:195,5 +BRDA:195,15,0,2 +DA:196,2 +DA:199,3 +DA:201,3 +DA:203,3 +DA:209,4 +FN:209,SwarmRegistryL1.updateSwarmProvider +FNDA:4,SwarmRegistryL1.updateSwarmProvider +DA:210,4 +DA:211,4 +BRDA:211,16,0,1 +DA:212,1 +DA:214,3 +BRDA:214,17,0,1 +DA:215,1 +DA:217,2 +BRDA:217,18,0,- +DA:218,0 +DA:221,1 +DA:223,1 +DA:225,1 +DA:227,1 +DA:232,7 +FN:232,SwarmRegistryL1.deleteSwarm +FNDA:7,SwarmRegistryL1.deleteSwarm +DA:233,7 +DA:234,7 +BRDA:234,19,0,1 +DA:235,1 +DA:237,6 +BRDA:237,20,0,1 +DA:238,1 +DA:241,5 +DA:243,5 +DA:245,5 +DA:247,5 +DA:254,27 +FN:254,SwarmRegistryL1.isSwarmValid +FNDA:27,SwarmRegistryL1.isSwarmValid +DA:255,27 +DA:256,27 +BRDA:256,21,0,1 +DA:259,26 +DA:261,26 +BRDA:261,22,0,26 +DA:262,19 +DA:263,7 +BRDA:263,22,1,7 +DA:264,7 +DA:270,6 +FN:270,SwarmRegistryL1.purgeOrphanedSwarm +FNDA:6,SwarmRegistryL1.purgeOrphanedSwarm +DA:271,6 +DA:272,6 +BRDA:272,23,0,1 +DA:274,5 +DA:275,5 +BRDA:275,24,0,1 +DA:277,4 +DA:279,4 +DA:281,4 +DA:283,4 +DA:290,7 +FN:290,SwarmRegistryL1.checkMembership +FNDA:7,SwarmRegistryL1.checkMembership +DA:291,7 +DA:292,7 +BRDA:292,25,0,1 +DA:293,1 +DA:297,6 +DA:298,6 +BRDA:298,26,0,1 +DA:300,5 +DA:301,5 +DA:303,0 +DA:307,5 +BRDA:307,27,0,5 +DA:309,5 +DA:314,5 +DA:315,5 +DA:317,0 +DA:319,4 +DA:320,4 +DA:321,4 +DA:323,4 +DA:324,4 +DA:326,4 +DA:327,4 +DA:328,4 +DA:330,4 +DA:336,9 +FN:336,SwarmRegistryL1._removeFromUuidSwarms +FNDA:9,SwarmRegistryL1._removeFromUuidSwarms +DA:337,9 +DA:338,9 +DA:339,9 +DA:341,9 +DA:342,9 +DA:343,9 +DA:344,9 +DA:353,12 +FN:353,SwarmRegistryL1._readFingerprint +FNDA:12,SwarmRegistryL1._readFingerprint +DA:354,12 +DA:355,12 +DA:356,12 +DA:359,12 +DA:362,12 +DA:363,12 +DA:364,24 +DA:366,24 +DA:370,12 +DA:371,12 +DA:372,12 +DA:374,12 +FNF:13 +FNH:13 +LF:143 +LH:139 +BRF:29 +BRH:26 +end_of_record +TN: +SF:src/swarms/SwarmRegistryUniversal.sol +DA:89,581 +FN:89,SwarmRegistryUniversal.computeSwarmId +FNDA:581,SwarmRegistryUniversal.computeSwarmId +DA:90,581 +DA:93,77 +FN:93,SwarmRegistryUniversal.constructor +FNDA:77,SwarmRegistryUniversal.constructor +DA:94,77 +BRDA:94,0,0,3 +DA:95,3 +DA:97,74 +DA:98,74 +DA:108,841 +FN:108,SwarmRegistryUniversal.registerSwarm +FNDA:841,SwarmRegistryUniversal.registerSwarm +DA:115,841 +BRDA:115,1,0,1 +DA:116,1 +DA:118,840 +BRDA:118,2,0,258 +DA:119,258 +DA:121,582 +BRDA:121,3,0,1 +DA:122,1 +DA:124,581 +BRDA:124,4,0,1 +DA:125,1 +DA:129,580 +BRDA:129,5,0,1 +DA:130,1 +DA:132,579 +BRDA:132,6,0,- +DA:133,0 +DA:136,578 +DA:138,578 +BRDA:138,7,0,1 +DA:139,1 +DA:142,577 +DA:143,577 +DA:144,577 +DA:145,577 +DA:146,577 +DA:147,577 +DA:148,577 +DA:150,577 +DA:152,577 +DA:153,577 +DA:155,577 +DA:160,10 +FN:160,SwarmRegistryUniversal.acceptSwarm +FNDA:10,SwarmRegistryUniversal.acceptSwarm +DA:161,10 +DA:162,10 +BRDA:162,8,0,1 +DA:164,9 +DA:165,9 +BRDA:165,9,0,2 +DA:167,7 +BRDA:167,10,0,2 +DA:168,2 +DA:170,5 +DA:171,5 +DA:176,5 +FN:176,SwarmRegistryUniversal.rejectSwarm +FNDA:5,SwarmRegistryUniversal.rejectSwarm +DA:177,5 +DA:178,5 +BRDA:178,11,0,- +DA:180,5 +DA:181,5 +BRDA:181,12,0,1 +DA:183,4 +BRDA:183,13,0,1 +DA:184,1 +DA:186,3 +DA:187,3 +DA:193,7 +FN:193,SwarmRegistryUniversal.updateSwarmFilter +FNDA:7,SwarmRegistryUniversal.updateSwarmFilter +DA:194,7 +DA:195,7 +BRDA:195,14,0,1 +DA:196,1 +DA:198,6 +BRDA:198,15,0,1 +DA:199,1 +DA:201,5 +BRDA:201,16,0,1 +DA:202,1 +DA:204,4 +BRDA:204,17,0,1 +DA:205,1 +DA:208,3 +DA:209,3 +DA:210,3 +DA:212,3 +DA:218,4 +FN:218,SwarmRegistryUniversal.updateSwarmProvider +FNDA:4,SwarmRegistryUniversal.updateSwarmProvider +DA:219,4 +DA:220,4 +BRDA:220,18,0,1 +DA:221,1 +DA:223,3 +BRDA:223,19,0,1 +DA:224,1 +DA:226,2 +BRDA:226,20,0,- +DA:227,0 +DA:230,1 +DA:233,1 +DA:234,1 +DA:236,1 +DA:241,8 +FN:241,SwarmRegistryUniversal.deleteSwarm +FNDA:8,SwarmRegistryUniversal.deleteSwarm +DA:242,8 +DA:243,8 +BRDA:243,21,0,1 +DA:244,1 +DA:246,7 +BRDA:246,22,0,1 +DA:247,1 +DA:250,6 +DA:252,6 +DA:254,6 +DA:255,6 +DA:257,6 +DA:264,30 +FN:264,SwarmRegistryUniversal.isSwarmValid +FNDA:30,SwarmRegistryUniversal.isSwarmValid +DA:265,30 +DA:266,30 +BRDA:266,23,0,1 +DA:269,29 +DA:271,29 +BRDA:271,24,0,29 +DA:272,21 +DA:273,8 +BRDA:273,24,1,8 +DA:274,8 +DA:280,7 +FN:280,SwarmRegistryUniversal.purgeOrphanedSwarm +FNDA:7,SwarmRegistryUniversal.purgeOrphanedSwarm +DA:281,7 +DA:282,7 +BRDA:282,25,0,1 +DA:284,6 +DA:285,6 +BRDA:285,26,0,1 +DA:287,5 +DA:289,5 +DA:291,5 +DA:292,5 +DA:294,5 +DA:301,6 +FN:301,SwarmRegistryUniversal.checkMembership +FNDA:6,SwarmRegistryUniversal.checkMembership +DA:302,6 +DA:303,6 +BRDA:303,27,0,1 +DA:304,1 +DA:308,5 +DA:309,5 +BRDA:309,28,0,1 +DA:311,4 +DA:312,4 +DA:315,4 +DA:316,4 +DA:319,4 +DA:320,4 +DA:321,4 +DA:323,4 +DA:324,4 +DA:327,4 +DA:328,4 +DA:329,4 +DA:331,4 +DA:337,3 +FN:337,SwarmRegistryUniversal.getFilterData +FNDA:3,SwarmRegistryUniversal.getFilterData +DA:338,3 +BRDA:338,30,0,1 +DA:339,1 +DA:341,2 +DA:347,11 +FN:347,SwarmRegistryUniversal._removeFromUuidSwarms +FNDA:11,SwarmRegistryUniversal._removeFromUuidSwarms +DA:348,11 +DA:349,11 +DA:350,11 +DA:352,11 +DA:353,11 +DA:354,11 +DA:355,11 +DA:364,12 +FN:364,SwarmRegistryUniversal._readFingerprint +FNDA:12,SwarmRegistryUniversal._readFingerprint +DA:365,12 +DA:366,12 +DA:367,12 +DA:370,12 +DA:371,12 +DA:372,24 +DA:374,24 +DA:379,12 +DA:380,12 +DA:381,12 +DA:383,12 +FNF:14 +FNH:14 +LF:150 +LH:148 +BRF:31 +BRH:28 +end_of_record +TN: +SF:test/FleetIdentity.t.sol +DA:12,648 +FN:12,MockERC20.mint +FNDA:648,MockERC20.mint +DA:13,648 +DA:23,1 +FN:23,BadERC20.mint +FNDA:1,BadERC20.mint +DA:24,1 +DA:27,1 +FN:27,BadERC20.setFail +FNDA:1,BadERC20.setFail +DA:28,1 +DA:31,0 +FN:31,BadERC20.transfer +FNDA:0,BadERC20.transfer +DA:33,0 +DA:36,1 +FN:36,BadERC20.transferFrom +FNDA:1,BadERC20.transferFrom +DA:38,0 +FNF:5 +FNH:4 +LF:10 +LH:7 +BRF:0 +BRH:0 +end_of_record +TN: +SF:test/FleetIdentityFairness.t.sol +DA:12,403 +FN:12,MockERC20Fairness.mint +FNDA:403,MockERC20Fairness.mint +DA:13,403 +FNF:1 +FNH:1 +LF:2 +LH:2 +BRF:0 +BRH:0 +end_of_record +TN: +SF:test/Grants.t.sol +DA:9,0 +FN:9,MockToken.constructor +FNDA:0,MockToken.constructor +DA:10,0 +FNF:1 +FNH:0 +LF:2 +LH:0 +BRF:0 +BRH:0 +end_of_record +TN: +SF:test/Payment.t.sol +DA:12,0 +FN:12,MockToken.constructor +FNDA:0,MockToken.constructor +DA:13,0 +FNF:1 +FNH:0 +LF:2 +LH:0 +BRF:0 +BRH:0 +end_of_record +TN: +SF:test/QuotaControl.t.sol +DA:14,0 +FN:14,TestableQuotaControl.exposeCheckedResetClaimed +FNDA:0,TestableQuotaControl.exposeCheckedResetClaimed +DA:15,0 +DA:18,0 +FN:18,TestableQuotaControl.exposeCheckedUpdateClaimed +FNDA:0,TestableQuotaControl.exposeCheckedUpdateClaimed +DA:19,0 +FNF:2 +FNH:0 +LF:4 +LH:0 +BRF:0 +BRH:0 +end_of_record +TN: +SF:test/SwarmRegistryL1.t.sol +DA:13,66 +FN:13,MockBondTokenL1.mint +FNDA:66,MockBondTokenL1.mint +DA:14,66 +FNF:1 +FNH:1 +LF:2 +LH:2 +BRF:0 +BRH:0 +end_of_record +TN: +SF:test/SwarmRegistryUniversal.t.sol +DA:13,74 +FN:13,MockBondTokenUniv.mint +FNDA:74,MockBondTokenUniv.mint +DA:14,74 +FNF:1 +FNH:1 +LF:2 +LH:2 +BRF:0 +BRH:0 +end_of_record +TN: +SF:test/__helpers__/AccessControlUtils.sol +DA:9,0 +FN:9,AccessControlUtils.expectRevert_AccessControlUnauthorizedAccount +FNDA:0,AccessControlUtils.expectRevert_AccessControlUnauthorizedAccount +DA:10,0 +FNF:1 +FNH:0 +LF:2 +LH:0 +BRF:0 +BRH:0 +end_of_record +TN: +SF:test/bridge/L1Bridge.t.sol +DA:31,0 +FN:31,MockMailbox.setL1ToL2Failed +FNDA:0,MockMailbox.setL1ToL2Failed +DA:32,0 +DA:35,0 +FN:35,MockMailbox.setInclusion +FNDA:0,MockMailbox.setInclusion +DA:36,0 +DA:39,0 +FN:39,MockMailbox.setBaseCostReturn +FNDA:0,MockMailbox.setBaseCostReturn +DA:40,0 +DA:43,0 +FN:43,MockMailbox.expectBaseCostParams +FNDA:0,MockMailbox.expectBaseCostParams +DA:44,0 +DA:45,0 +DA:46,0 +DA:50,0 +FN:50,MockMailbox.requestL2Transaction +FNDA:0,MockMailbox.requestL2Transaction +DA:59,0 +DA:60,0 +DA:63,0 +DA:66,0 +FN:66,MockMailbox.proveL1ToL2TransactionStatus +FNDA:0,MockMailbox.proveL1ToL2TransactionStatus +DA:74,0 +BRDA:74,0,0,- +DA:75,0 +DA:77,0 +DA:80,0 +FN:80,MockMailbox.proveL2MessageInclusion +FNDA:0,MockMailbox.proveL2MessageInclusion +DA:86,0 +DA:89,0 +FN:89,MockMailbox.l2TransactionBaseCost +FNDA:0,MockMailbox.l2TransactionBaseCost +DA:95,0 +BRDA:95,1,0,- +BRDA:95,1,1,- +DA:96,0 +BRDA:96,2,0,- +BRDA:96,2,1,- +DA:97,0 +BRDA:97,3,0,- +BRDA:97,3,1,- +DA:98,0 +FNF:8 +FNH:0 +LF:25 +LH:0 +BRF:7 +BRH:0 +end_of_record +TN: +SF:test/bridge/MigrationNFT.t.sol +DA:12,0 +FN:12,MigrationNFTTestUtils.bridgeTokens +FNDA:0,MigrationNFTTestUtils.bridgeTokens +DA:20,0 +DA:21,0 +DA:22,0 +FNF:1 +FNH:0 +LF:4 +LH:0 +BRF:0 +BRH:0 +end_of_record +TN: +SF:test/contentsign/BaseContentSign.t.sol +DA:13,0 +FN:13,MockContentSign.setWhitelisted +FNDA:0,MockContentSign.setWhitelisted +DA:14,0 +DA:17,0 +FN:17,MockContentSign._userIsWhitelisted +FNDA:0,MockContentSign._userIsWhitelisted +DA:18,0 +FNF:2 +FNH:0 +LF:4 +LH:0 +BRF:0 +BRH:0 +end_of_record +TN: +SF:test/contentsign/ClickBounty.t.sol +DA:13,0 +FN:13,MockERC20.constructor +FNDA:0,MockERC20.constructor +DA:14,0 +DA:21,0 +FN:21,MockContentSign._userIsWhitelisted +FNDA:0,MockContentSign._userIsWhitelisted +DA:22,0 +FNF:2 +FNH:0 +LF:4 +LH:0 +BRF:0 +BRH:0 +end_of_record +TN: +SF:test/contentsign/PaymentMiddleware.t.sol +DA:16,0 +FN:16,MockToken.mint +FNDA:0,MockToken.mint +DA:17,0 +DA:24,0 +FN:24,MockWhitelist.mint +FNDA:0,MockWhitelist.mint +DA:25,0 +DA:32,0 +FN:32,MockContentSign._userIsWhitelisted +FNDA:0,MockContentSign._userIsWhitelisted +DA:33,0 +FNF:3 +FNH:0 +LF:6 +LH:0 +BRF:0 +BRH:0 +end_of_record +TN: +SF:test/paymasters/BasePaymaster.t.sol +DA:13,0 +FN:13,MockPaymaster._validateAndPayGeneralFlow +FNDA:0,MockPaymaster._validateAndPayGeneralFlow +DA:15,0 +DA:18,0 +FN:18,MockPaymaster._validateAndPayApprovalBasedFlow +FNDA:0,MockPaymaster._validateAndPayApprovalBasedFlow +DA:23,0 +FNF:2 +FNH:0 +LF:4 +LH:0 +BRF:0 +BRH:0 +end_of_record +TN: +SF:test/paymasters/WhitelistPaymaster.t.sol +DA:14,0 +FN:14,MockWhitelistPaymaster.mock_validateAndPayGeneralFlow +FNDA:0,MockWhitelistPaymaster.mock_validateAndPayGeneralFlow +DA:15,0 +DA:18,0 +FN:18,MockWhitelistPaymaster.mock_validateAndPayApprovalBasedFlow +FNDA:0,MockWhitelistPaymaster.mock_validateAndPayApprovalBasedFlow +DA:26,0 +FNF:2 +FNH:0 +LF:4 +LH:0 +BRF:0 +BRH:0 +end_of_record diff --git a/lcov.info b/lcov.info new file mode 100644 index 00000000..e533fc28 --- /dev/null +++ b/lcov.info @@ -0,0 +1,3301 @@ +TN: +SF:script/CheckBridge.s.sol +DA:13,0 +FN:13,CheckBridge.setUp +FNDA:0,CheckBridge.setUp +DA:14,0 +DA:15,0 +DA:18,0 +FN:18,CheckBridge.run +FNDA:0,CheckBridge.run +DA:19,0 +DA:20,0 +DA:22,0 +DA:23,0 +DA:25,0 +BRDA:25,0,0,- +BRDA:25,0,1,- +DA:26,0 +DA:28,0 +DA:30,0 +BRDA:30,1,0,- +BRDA:30,1,1,- +DA:31,0 +DA:32,0 +BRDA:32,2,0,- +BRDA:32,2,1,- +DA:33,0 +DA:35,0 +DA:38,0 +FNF:2 +FNH:0 +LF:17 +LH:0 +BRF:6 +BRH:0 +end_of_record +TN: +SF:script/ContentSignWhitelist.s.sol +DA:13,0 +FN:13,ContentSignWhitelist.setUp +FNDA:0,ContentSignWhitelist.setUp +DA:14,0 +DA:15,0 +DA:18,0 +FN:18,ContentSignWhitelist.run +FNDA:0,ContentSignWhitelist.run +DA:19,0 +DA:21,0 +BRDA:21,0,0,- +BRDA:21,0,1,- +DA:22,0 +DA:24,0 +DA:26,0 +DA:27,0 +DA:28,0 +FNF:2 +FNH:0 +LF:11 +LH:0 +BRF:2 +BRH:0 +end_of_record +TN: +SF:script/DeployClick.s.sol +DA:14,0 +FN:14,DeployClick.setUp +FNDA:0,DeployClick.setUp +DA:15,0 +DA:16,0 +DA:18,0 +DA:19,0 +DA:22,0 +FN:22,DeployClick.run +FNDA:0,DeployClick.run +DA:23,0 +DA:25,0 +DA:26,0 +DA:28,0 +DA:29,0 +DA:32,0 +DA:35,0 +DA:37,0 +DA:39,0 +DA:40,0 +DA:41,0 +FNF:2 +FNH:0 +LF:17 +LH:0 +BRF:0 +BRH:0 +end_of_record +TN: +SF:script/DeployContentSignEnterprise.s.sol +DA:15,0 +FN:15,DeployContentSignEnterprise.setUp +FNDA:0,DeployContentSignEnterprise.setUp +DA:16,0 +DA:17,0 +DA:18,0 +DA:21,0 +FN:21,DeployContentSignEnterprise.run +FNDA:0,DeployContentSignEnterprise.run +DA:22,0 +DA:24,0 +DA:26,0 +DA:28,0 +DA:29,0 +DA:30,0 +DA:31,0 +FNF:2 +FNH:0 +LF:12 +LH:0 +BRF:0 +BRH:0 +end_of_record +TN: +SF:script/DeployL1Bridge.s.sol +DA:22,0 +FN:22,DeployL1Bridge.setUp +FNDA:0,DeployL1Bridge.setUp +DA:23,0 +DA:24,0 +DA:25,0 +DA:26,0 +DA:28,0 +DA:29,0 +DA:30,0 +DA:31,0 +DA:34,0 +FN:34,DeployL1Bridge.run +FNDA:0,DeployL1Bridge.run +DA:35,0 +DA:37,0 +DA:39,0 +DA:40,0 +DA:41,0 +DA:43,0 +DA:45,0 +DA:46,0 +FNF:2 +FNH:0 +LF:18 +LH:0 +BRF:0 +BRH:0 +end_of_record +TN: +SF:script/DeployL1Ens.s.sol +DA:18,0 +FN:18,DeployL1Ens.run +FNDA:0,DeployL1Ens.run +DA:19,0 +DA:20,0 +DA:22,0 +DA:24,0 +BRDA:24,0,0,- +BRDA:24,0,1,- +DA:25,0 +DA:26,0 +BRDA:26,1,0,- +BRDA:26,1,1,- +DA:27,0 +DA:28,0 +DA:29,0 +DA:30,0 +DA:32,0 +DA:35,0 +DA:36,0 +DA:39,0 +DA:40,0 +DA:42,0 +DA:45,0 +DA:47,0 +BRDA:47,2,0,- +DA:48,0 +DA:49,0 +DA:55,0 +DA:56,0 +DA:59,0 +DA:60,0 +DA:62,0 +DA:63,0 +DA:65,0 +DA:66,0 +DA:68,0 +FNF:1 +FNH:0 +LF:30 +LH:0 +BRF:5 +BRH:0 +end_of_record +TN: +SF:script/DeployL1Nodl.s.sol +DA:17,0 +FN:17,DeployL1Nodl.setUp +FNDA:0,DeployL1Nodl.setUp +DA:18,0 +DA:19,0 +DA:21,0 +DA:22,0 +DA:25,0 +FN:25,DeployL1Nodl.run +FNDA:0,DeployL1Nodl.run +DA:26,0 +DA:28,0 +DA:30,0 +DA:32,0 +FNF:2 +FNH:0 +LF:10 +LH:0 +BRF:0 +BRH:0 +end_of_record +TN: +SF:script/DeployL2Bridge.s.sol +DA:19,0 +FN:19,DeployL2Bridge.setUp +FNDA:0,DeployL2Bridge.setUp +DA:20,0 +DA:21,0 +DA:23,0 +DA:24,0 +DA:27,0 +FN:27,DeployL2Bridge.run +FNDA:0,DeployL2Bridge.run +DA:28,0 +DA:30,0 +DA:32,0 +DA:33,0 +DA:34,0 +DA:36,0 +DA:38,0 +DA:39,0 +FNF:2 +FNH:0 +LF:14 +LH:0 +BRF:0 +BRH:0 +end_of_record +TN: +SF:script/DeployL2Nodl.s.sol +DA:17,0 +FN:17,DeployL2Nodl.setUp +FNDA:0,DeployL2Nodl.setUp +DA:18,0 +DA:19,0 +DA:21,0 +DA:22,0 +DA:25,0 +FN:25,DeployL2Nodl.run +FNDA:0,DeployL2Nodl.run +DA:26,0 +DA:28,0 +DA:29,0 +DA:30,0 +DA:32,0 +DA:34,0 +FNF:2 +FNH:0 +LF:12 +LH:0 +BRF:0 +BRH:0 +end_of_record +TN: +SF:script/DeployMigrationNFT.s.sol +DA:18,0 +FN:18,DeployMigrationNFT.setUp +FNDA:0,DeployMigrationNFT.setUp +DA:19,0 +DA:20,0 +DA:22,0 +DA:23,0 +DA:24,0 +DA:25,0 +DA:27,0 +DA:28,0 +DA:32,0 +FN:32,DeployMigrationNFT.run +FNDA:0,DeployMigrationNFT.run +DA:33,0 +DA:35,0 +DA:37,0 +DA:39,0 +FNF:2 +FNH:0 +LF:14 +LH:0 +BRF:0 +BRH:0 +end_of_record +TN: +SF:script/DeployNodlMigration.sol +DA:14,0 +FN:14,DeployNodlMigration.setUp +FNDA:0,DeployNodlMigration.setUp +DA:15,0 +DA:16,0 +DA:18,0 +DA:19,0 +DA:20,0 +DA:23,0 +FN:23,DeployNodlMigration.run +FNDA:0,DeployNodlMigration.run +DA:24,0 +DA:26,0 +DA:27,0 +DA:29,0 +DA:30,0 +DA:32,0 +DA:34,0 +DA:36,0 +DA:37,0 +FNF:2 +FNH:0 +LF:16 +LH:0 +BRF:0 +BRH:0 +end_of_record +TN: +SF:script/DeployRewards.sol +DA:18,0 +FN:18,DeployRewards.setUp +FNDA:0,DeployRewards.setUp +DA:19,0 +DA:20,0 +DA:21,0 +DA:22,0 +DA:23,0 +DA:24,0 +DA:27,0 +FN:27,DeployRewards.run +FNDA:0,DeployRewards.run +DA:28,0 +DA:29,0 +BRDA:29,0,0,- +DA:30,0 +DA:31,0 +DA:32,0 +DA:35,0 +DA:36,0 +DA:39,0 +DA:40,0 +DA:42,0 +DA:44,0 +FNF:2 +FNH:0 +LF:19 +LH:0 +BRF:1 +BRH:0 +end_of_record +TN: +SF:script/RewardsSig.s.sol +DA:15,0 +FN:15,RewardsSig.setUp +FNDA:0,RewardsSig.setUp +DA:16,0 +DA:17,0 +DA:18,0 +DA:19,0 +DA:22,0 +FN:22,RewardsSig.run +FNDA:0,RewardsSig.run +DA:23,0 +DA:25,0 +DA:26,0 +DA:27,0 +DA:29,0 +DA:31,0 +DA:33,0 +DA:34,0 +DA:36,0 +DA:37,0 +DA:39,0 +DA:40,0 +DA:42,0 +DA:43,0 +FNF:2 +FNH:0 +LF:20 +LH:0 +BRF:0 +BRH:0 +end_of_record +TN: +SF:src/Grants.sol +DA:56,0 +FN:56,Grants.constructor +FNDA:0,Grants.constructor +DA:57,0 +BRDA:57,0,0,- +DA:58,0 +DA:61,0 +DA:62,0 +DA:63,0 +DA:75,0 +FN:75,Grants.addVestingSchedule +FNDA:0,Grants.addVestingSchedule +DA:83,0 +DA:85,0 +DA:87,0 +DA:88,0 +BRDA:88,1,0,- +DA:89,0 +DA:90,0 +DA:92,0 +DA:94,0 +DA:96,0 +DA:106,0 +FN:106,Grants.validateVestingSchedule +FNDA:0,Grants.validateVestingSchedule +DA:110,0 +DA:111,0 +DA:112,0 +DA:113,0 +DA:114,0 +DA:123,0 +FN:123,Grants.claim +FNDA:0,Grants.claim +DA:124,0 +DA:125,0 +DA:127,0 +DA:129,0 +DA:130,0 +DA:131,0 +DA:132,0 +DA:133,0 +DA:134,0 +BRDA:134,2,0,- +DA:135,0 +DA:136,0 +DA:137,0 +DA:138,0 +DA:139,0 +DA:140,0 +DA:141,0 +DA:142,0 +BRDA:142,3,0,- +DA:143,0 +DA:144,0 +DA:145,0 +DA:148,0 +DA:152,0 +BRDA:152,4,0,- +BRDA:152,4,1,- +DA:153,0 +DA:154,0 +DA:156,0 +DA:167,0 +FN:167,Grants.renounce +FNDA:0,Grants.renounce +DA:168,0 +DA:170,0 +DA:171,0 +DA:172,0 +DA:173,0 +DA:174,0 +BRDA:174,5,0,- +DA:175,0 +DA:176,0 +DA:181,0 +BRDA:181,6,0,- +BRDA:181,6,1,- +DA:182,0 +DA:184,0 +DA:195,0 +FN:195,Grants.cancelVestingSchedules +FNDA:0,Grants.cancelVestingSchedules +DA:196,0 +DA:197,0 +DA:198,0 +DA:200,0 +DA:201,0 +DA:202,0 +DA:203,0 +DA:204,0 +DA:205,0 +DA:206,0 +BRDA:206,7,0,- +DA:207,0 +DA:208,0 +DA:209,0 +DA:210,0 +DA:211,0 +DA:212,0 +DA:213,0 +DA:214,0 +DA:215,0 +DA:216,0 +DA:217,0 +DA:219,0 +DA:223,0 +BRDA:223,8,0,- +DA:224,0 +DA:227,0 +BRDA:227,9,0,- +DA:228,0 +DA:231,0 +BRDA:231,10,0,- +DA:232,0 +DA:235,0 +DA:243,0 +FN:243,Grants.getGrantsCount +FNDA:0,Grants.getGrantsCount +DA:244,0 +DA:245,0 +DA:246,0 +DA:248,0 +DA:253,0 +FN:253,Grants._mustBeNonZero +FNDA:0,Grants._mustBeNonZero +DA:254,0 +BRDA:254,11,0,- +DA:255,0 +DA:259,0 +FN:259,Grants._mustBeNonZeroAddress +FNDA:0,Grants._mustBeNonZeroAddress +DA:260,0 +BRDA:260,12,0,- +DA:261,0 +DA:265,0 +FN:265,Grants._mustNotBeSelf +FNDA:0,Grants._mustNotBeSelf +DA:266,0 +BRDA:266,13,0,- +DA:267,0 +DA:271,0 +FN:271,Grants._mustBeEqualOrExceedMinAmount +FNDA:0,Grants._mustBeEqualOrExceedMinAmount +DA:272,0 +BRDA:272,14,0,- +DA:273,0 +DA:277,0 +FN:277,Grants._sanitizePageRange +FNDA:0,Grants._sanitizePageRange +DA:278,0 +DA:279,0 +BRDA:279,15,0,- +DA:280,0 +DA:282,0 +BRDA:282,16,0,- +DA:283,0 +DA:285,0 +FNF:12 +FNH:0 +LF:114 +LH:0 +BRF:19 +BRH:0 +end_of_record +TN: +SF:src/L1Nodl.sol +DA:18,0 +FN:18,L1Nodl.constructor +FNDA:0,L1Nodl.constructor +DA:19,0 +BRDA:19,0,0,- +DA:20,0 +DA:22,0 +DA:23,0 +DA:26,0 +FN:26,L1Nodl.mint +FNDA:0,L1Nodl.mint +DA:27,0 +DA:30,0 +FN:30,L1Nodl.clock +FNDA:0,L1Nodl.clock +DA:31,0 +DA:35,0 +FN:35,L1Nodl.CLOCK_MODE +FNDA:0,L1Nodl.CLOCK_MODE +DA:36,0 +DA:39,0 +FN:39,L1Nodl.nonces +FNDA:0,L1Nodl.nonces +DA:40,0 +DA:43,0 +FN:43,L1Nodl._update +FNDA:0,L1Nodl._update +DA:44,0 +FNF:6 +FNH:0 +LF:15 +LH:0 +BRF:1 +BRH:0 +end_of_record +TN: +SF:src/NODL.sol +DA:12,0 +FN:12,NODL.constructor +FNDA:0,NODL.constructor +DA:13,0 +DA:14,0 +DA:17,0 +FN:17,NODL.mint +FNDA:0,NODL.mint +DA:18,0 +DA:20,0 +FNF:2 +FNH:0 +LF:6 +LH:0 +BRF:0 +BRH:0 +end_of_record +TN: +SF:src/Payment.sol +DA:40,0 +FN:40,Payment.constructor +FNDA:0,Payment.constructor +DA:43,0 +DA:44,0 +DA:58,0 +FN:58,Payment.pay +FNDA:0,Payment.pay +DA:59,0 +DA:60,0 +DA:62,0 +BRDA:62,0,0,- +DA:63,0 +DA:66,0 +DA:67,0 +DA:69,0 +DA:70,0 +DA:83,0 +FN:83,Payment.withdraw +FNDA:0,Payment.withdraw +DA:84,0 +FNF:3 +FNH:0 +LF:14 +LH:0 +BRF:1 +BRH:0 +end_of_record +TN: +SF:src/QuotaControl.sol +DA:84,0 +FN:84,QuotaControl.constructor +FNDA:0,QuotaControl.constructor +DA:85,0 +DA:86,0 +DA:88,0 +DA:89,0 +DA:90,0 +DA:97,0 +FN:97,QuotaControl.setQuota +FNDA:0,QuotaControl.setQuota +DA:98,0 +DA:99,0 +DA:109,0 +FN:109,QuotaControl.setPeriod +FNDA:0,QuotaControl.setPeriod +DA:110,0 +DA:111,0 +DA:112,0 +DA:121,0 +FN:121,QuotaControl._checkedResetClaimed +FNDA:0,QuotaControl._checkedResetClaimed +DA:122,0 +BRDA:122,0,0,- +DA:123,0 +DA:126,0 +DA:127,0 +DA:140,0 +FN:140,QuotaControl._checkedUpdateClaimed +FNDA:0,QuotaControl._checkedUpdateClaimed +DA:141,0 +DA:142,0 +BRDA:142,1,0,- +DA:143,0 +DA:145,0 +DA:156,0 +FN:156,QuotaControl._mustBeWithinPeriodRange +FNDA:0,QuotaControl._mustBeWithinPeriodRange +DA:157,0 +BRDA:157,2,0,- +DA:158,0 +DA:160,0 +BRDA:160,3,0,- +DA:161,0 +FNF:6 +FNH:0 +LF:28 +LH:0 +BRF:4 +BRH:0 +end_of_record +TN: +SF:src/Rewards.sol +DA:137,0 +FN:137,Rewards.constructor +FNDA:0,Rewards.constructor +DA:145,0 +DA:147,0 +DA:148,0 +DA:149,0 +DA:157,0 +FN:157,Rewards.mintReward +FNDA:0,Rewards.mintReward +DA:158,0 +DA:159,0 +DA:161,0 +DA:162,0 +DA:165,0 +DA:166,0 +DA:168,0 +DA:176,0 +FN:176,Rewards.mintBatchReward +FNDA:0,Rewards.mintBatchReward +DA:177,0 +DA:178,0 +DA:180,0 +DA:182,0 +DA:184,0 +DA:186,0 +DA:187,0 +DA:189,0 +DA:192,0 +DA:194,0 +DA:196,0 +DA:197,0 +DA:199,0 +DA:201,0 +DA:208,0 +FN:208,Rewards.setBatchSubmitterRewardBasisPoints +FNDA:0,Rewards.setBatchSubmitterRewardBasisPoints +DA:209,0 +DA:210,0 +DA:211,0 +DA:213,0 +DA:220,0 +FN:220,Rewards._mustBeLessThanBasisPointsDivisor +FNDA:0,Rewards._mustBeLessThanBasisPointsDivisor +DA:221,0 +BRDA:221,0,0,- +DA:222,0 +DA:231,0 +FN:231,Rewards._mustBeExpectedSequence +FNDA:0,Rewards._mustBeExpectedSequence +DA:232,0 +BRDA:232,1,0,- +DA:233,0 +DA:241,0 +FN:241,Rewards._mustBeExpectedBatchSequence +FNDA:0,Rewards._mustBeExpectedBatchSequence +DA:242,0 +BRDA:242,2,0,- +DA:243,0 +DA:251,0 +FN:251,Rewards._mustBeValidBatchStructure +FNDA:0,Rewards._mustBeValidBatchStructure +DA:252,0 +BRDA:252,3,0,- +DA:253,0 +DA:263,0 +FN:263,Rewards._mustBeFromAuthorizedOracle +FNDA:0,Rewards._mustBeFromAuthorizedOracle +DA:264,0 +BRDA:264,4,0,- +DA:265,0 +DA:274,0 +FN:274,Rewards._batchSum +FNDA:0,Rewards._batchSum +DA:275,0 +DA:276,0 +DA:277,0 +DA:279,0 +DA:287,0 +FN:287,Rewards.digestReward +FNDA:0,Rewards.digestReward +DA:288,0 +DA:289,0 +DA:297,0 +FN:297,Rewards.digestBatchReward +FNDA:0,Rewards.digestBatchReward +DA:298,0 +DA:299,0 +DA:300,0 +DA:301,0 +DA:308,0 +FN:308,Rewards.latestBatchDetails +FNDA:0,Rewards.latestBatchDetails +DA:309,0 +FNF:13 +FNH:0 +LF:63 +LH:0 +BRF:5 +BRH:0 +end_of_record +TN: +SF:src/bridge/BridgeBase.sol +DA:70,0 +FN:70,BridgeBase.constructor +FNDA:0,BridgeBase.constructor +DA:71,0 +DA:72,0 +DA:73,0 +DA:75,0 +DA:76,0 +DA:79,0 +DA:80,0 +DA:81,0 +DA:90,0 +FN:90,BridgeBase._createVote +FNDA:0,BridgeBase._createVote +DA:91,0 +DA:93,0 +DA:99,0 +FN:99,BridgeBase._recordVote +FNDA:0,BridgeBase._recordVote +DA:100,0 +DA:102,0 +DA:104,0 +DA:108,0 +FN:108,BridgeBase._processVote +FNDA:0,BridgeBase._processVote +DA:109,0 +DA:110,0 +DA:111,0 +DA:116,0 +FN:116,BridgeBase._execute +FNDA:0,BridgeBase._execute +DA:117,0 +DA:118,0 +DA:119,0 +DA:121,0 +DA:164,0 +FN:164,BridgeBase._mustNotHaveExecutedYet +FNDA:0,BridgeBase._mustNotHaveExecutedYet +DA:165,0 +BRDA:165,0,0,- +DA:166,0 +DA:170,0 +FN:170,BridgeBase._mustBePastSafetyDelay +FNDA:0,BridgeBase._mustBePastSafetyDelay +DA:171,0 +BRDA:171,1,0,- +DA:172,0 +DA:176,0 +FN:176,BridgeBase._mustHaveEnoughVotes +FNDA:0,BridgeBase._mustHaveEnoughVotes +DA:177,0 +BRDA:177,2,0,- +DA:178,0 +DA:182,0 +FN:182,BridgeBase._mustHaveEnoughOracles +FNDA:0,BridgeBase._mustHaveEnoughOracles +DA:183,0 +BRDA:183,3,0,- +DA:184,0 +DA:188,0 +FN:188,BridgeBase._mustBeAnOracle +FNDA:0,BridgeBase._mustBeAnOracle +DA:189,0 +BRDA:189,4,0,- +DA:190,0 +DA:194,0 +FN:194,BridgeBase._mustNotExceedMaxOracles +FNDA:0,BridgeBase._mustNotExceedMaxOracles +DA:195,0 +BRDA:195,5,0,- +DA:196,0 +DA:200,0 +FN:200,BridgeBase._mustNotBeZeroMinVotes +FNDA:0,BridgeBase._mustNotBeZeroMinVotes +DA:201,0 +BRDA:201,6,0,- +DA:202,0 +DA:206,0 +FN:206,BridgeBase._mustNotHaveVotedYet +FNDA:0,BridgeBase._mustNotHaveVotedYet +DA:207,0 +BRDA:207,7,0,- +DA:208,0 +FNF:13 +FNH:0 +LF:49 +LH:0 +BRF:8 +BRH:0 +end_of_record +TN: +SF:src/bridge/GrantsMigration.sol +DA:57,0 +FN:57,GrantsMigration.constructor +FNDA:0,GrantsMigration.constructor +DA:60,0 +DA:70,0 +FN:70,GrantsMigration.bridge +FNDA:0,GrantsMigration.bridge +DA:73,0 +DA:74,0 +DA:76,0 +BRDA:76,0,0,- +BRDA:76,0,1,- +DA:77,0 +DA:78,0 +DA:80,0 +DA:88,0 +FN:88,GrantsMigration.grant +FNDA:0,GrantsMigration.grant +DA:89,0 +DA:90,0 +DA:91,0 +DA:92,0 +DA:93,0 +DA:94,0 +DA:103,0 +DA:108,0 +FN:108,GrantsMigration._mustNotBeChangingParameters +FNDA:0,GrantsMigration._mustNotBeChangingParameters +DA:114,0 +DA:116,0 +BRDA:116,1,0,- +DA:117,0 +DA:120,0 +DA:121,0 +BRDA:121,2,0,- +DA:122,0 +DA:125,0 +DA:127,0 +DA:128,0 +DA:129,0 +DA:130,0 +DA:131,0 +DA:132,0 +BRDA:132,3,0,- +DA:133,0 +DA:138,0 +FN:138,GrantsMigration._createProposal +FNDA:0,GrantsMigration._createProposal +DA:145,0 +BRDA:145,4,0,- +DA:146,0 +DA:148,0 +BRDA:148,5,0,- +DA:149,0 +DA:151,0 +DA:152,0 +DA:153,0 +DA:156,0 +DA:158,0 +BRDA:158,6,0,- +DA:159,0 +DA:161,0 +DA:162,0 +DA:165,0 +FN:165,GrantsMigration._proposalExists +FNDA:0,GrantsMigration._proposalExists +DA:166,0 +DA:169,0 +FN:169,GrantsMigration._flagAsExecuted +FNDA:0,GrantsMigration._flagAsExecuted +DA:170,0 +DA:173,0 +FN:173,GrantsMigration._incTotalVotes +FNDA:0,GrantsMigration._incTotalVotes +DA:174,0 +DA:177,0 +FN:177,GrantsMigration._updateLastVote +FNDA:0,GrantsMigration._updateLastVote +DA:178,0 +DA:181,0 +FN:181,GrantsMigration._totalVotes +FNDA:0,GrantsMigration._totalVotes +DA:182,0 +DA:185,0 +FN:185,GrantsMigration._lastVote +FNDA:0,GrantsMigration._lastVote +DA:186,0 +DA:189,0 +FN:189,GrantsMigration._executed +FNDA:0,GrantsMigration._executed +DA:190,0 +FNF:12 +FNH:0 +LF:59 +LH:0 +BRF:8 +BRH:0 +end_of_record +TN: +SF:src/bridge/L1Bridge.sol +DA:80,0 +FN:80,L1Bridge.constructor +FNDA:0,L1Bridge.constructor +DA:81,0 +BRDA:81,0,0,- +DA:82,0 +DA:84,0 +DA:85,0 +DA:86,0 +DA:94,0 +FN:94,L1Bridge.pause +FNDA:0,L1Bridge.pause +DA:95,0 +DA:99,0 +FN:99,L1Bridge.unpause +FNDA:0,L1Bridge.unpause +DA:100,0 +DA:115,0 +FN:115,L1Bridge.quoteL2BaseCost +FNDA:0,L1Bridge.quoteL2BaseCost +DA:120,0 +DA:130,0 +FN:130,L1Bridge.quoteL2BaseCostAtGasPrice +FNDA:0,L1Bridge.quoteL2BaseCostAtGasPrice +DA:135,0 +DA:152,0 +FN:152,L1Bridge.deposit +FNDA:0,L1Bridge.deposit +DA:159,0 +BRDA:159,1,0,- +DA:160,0 +DA:162,0 +BRDA:162,2,0,- +DA:163,0 +DA:166,0 +DA:168,0 +DA:169,0 +DA:171,0 +DA:175,0 +DA:177,0 +DA:183,0 +FN:183,L1Bridge.deposit +FNDA:0,L1Bridge.deposit +DA:189,0 +DA:202,0 +FN:202,L1Bridge.claimFailedDeposit +FNDA:0,L1Bridge.claimFailedDeposit +DA:210,0 +DA:211,0 +BRDA:211,3,0,- +DA:212,0 +DA:214,0 +DA:217,0 +BRDA:217,4,0,- +DA:218,0 +DA:220,0 +DA:221,0 +DA:222,0 +DA:234,0 +FN:234,L1Bridge.finalizeWithdrawal +FNDA:0,L1Bridge.finalizeWithdrawal +DA:241,0 +BRDA:241,5,0,- +DA:242,0 +DA:245,0 +DA:246,0 +DA:249,0 +DA:255,0 +BRDA:255,6,0,- +DA:256,0 +DA:259,0 +DA:261,0 +DA:262,0 +DA:276,0 +FN:276,L1Bridge._parseL2WithdrawalMessage +FNDA:0,L1Bridge._parseL2WithdrawalMessage +DA:282,0 +BRDA:282,7,0,- +DA:283,0 +DA:287,0 +DA:288,0 +BRDA:288,8,0,- +DA:289,0 +DA:291,0 +DA:292,0 +FNF:10 +FNH:0 +LF:56 +LH:0 +BRF:9 +BRH:0 +end_of_record +TN: +SF:src/bridge/L2Bridge.sol +DA:49,0 +FN:49,L2Bridge.onlyL1Bridge +FNDA:0,L2Bridge.onlyL1Bridge +DA:50,0 +BRDA:50,0,0,- +DA:51,0 +DA:65,0 +FN:65,L2Bridge.constructor +FNDA:0,L2Bridge.constructor +DA:66,0 +BRDA:66,1,0,- +DA:67,0 +DA:69,0 +DA:70,0 +DA:78,0 +FN:78,L2Bridge.pause +FNDA:0,L2Bridge.pause +DA:79,0 +DA:83,0 +FN:83,L2Bridge.unpause +FNDA:0,L2Bridge.unpause +DA:84,0 +DA:90,0 +FN:90,L2Bridge.initialize +FNDA:0,L2Bridge.initialize +DA:91,0 +BRDA:91,2,0,- +DA:92,0 +DA:94,0 +BRDA:94,3,0,- +DA:95,0 +DA:97,0 +DA:103,0 +FN:103,L2Bridge.finalizeDeposit +FNDA:0,L2Bridge.finalizeDeposit +DA:109,0 +BRDA:109,4,0,- +DA:110,0 +DA:112,0 +BRDA:112,5,0,- +DA:113,0 +DA:116,0 +DA:118,0 +DA:124,0 +FN:124,L2Bridge.withdraw +FNDA:0,L2Bridge.withdraw +DA:125,0 +BRDA:125,6,0,- +DA:126,0 +DA:128,0 +BRDA:128,7,0,- +DA:129,0 +DA:132,0 +DA:135,0 +DA:136,0 +DA:138,0 +FNF:7 +FNH:0 +LF:34 +LH:0 +BRF:8 +BRH:0 +end_of_record +TN: +SF:src/bridge/MigrationNFT.sol +DA:69,0 +FN:69,MigrationNFT.constructor +FNDA:0,MigrationNFT.constructor +DA:75,0 +BRDA:75,0,0,- +DA:76,0 +DA:78,0 +BRDA:78,1,0,- +DA:79,0 +DA:81,0 +BRDA:81,2,0,- +DA:82,0 +DA:85,0 +DA:86,0 +BRDA:86,3,0,- +DA:87,0 +DA:91,0 +DA:92,0 +DA:93,0 +DA:94,0 +DA:101,0 +FN:101,MigrationNFT.tokenURI +FNDA:0,MigrationNFT.tokenURI +DA:102,0 +DA:104,0 +DA:105,0 +DA:112,0 +FN:112,MigrationNFT.safeMint +FNDA:0,MigrationNFT.safeMint +DA:113,0 +DA:115,0 +DA:117,0 +DA:118,0 +DA:119,0 +DA:121,0 +DA:123,0 +DA:124,0 +BRDA:124,4,0,- +DA:125,0 +DA:128,0 +DA:129,0 +DA:130,0 +DA:131,0 +DA:132,0 +DA:136,0 +FN:136,MigrationNFT._computeLevelUps +FNDA:0,MigrationNFT._computeLevelUps +DA:141,0 +DA:142,0 +DA:147,0 +DA:148,0 +DA:149,0 +BRDA:149,5,0,- +DA:150,0 +DA:151,0 +DA:155,0 +BRDA:155,6,0,- +DA:156,0 +DA:160,0 +FN:160,MigrationNFT._mustNotHaveBeenClaimed +FNDA:0,MigrationNFT._mustNotHaveBeenClaimed +DA:161,0 +BRDA:161,7,0,- +DA:162,0 +DA:166,0 +FN:166,MigrationNFT._mustBeAnExistingProposal +FNDA:0,MigrationNFT._mustBeAnExistingProposal +DA:168,0 +BRDA:168,8,0,- +DA:169,0 +DA:173,0 +FN:173,MigrationNFT._mustBeExecuted +FNDA:0,MigrationNFT._mustBeExecuted +DA:174,0 +BRDA:174,9,0,- +DA:175,0 +DA:179,0 +FN:179,MigrationNFT._mustAlreadyBeHolderOrEnoughHoldersRemaining +FNDA:0,MigrationNFT._mustAlreadyBeHolderOrEnoughHoldersRemaining +DA:180,0 +DA:181,0 +BRDA:181,10,0,- +DA:182,0 +DA:186,0 +FN:186,MigrationNFT._update +FNDA:0,MigrationNFT._update +DA:187,0 +DA:188,0 +BRDA:188,11,0,- +DA:190,0 +DA:193,0 +FNF:9 +FNH:0 +LF:61 +LH:0 +BRF:12 +BRH:0 +end_of_record +TN: +SF:src/bridge/NODLMigration.sol +DA:35,0 +FN:35,NODLMigration.bridge +FNDA:0,NODLMigration.bridge +DA:36,0 +DA:37,0 +DA:39,0 +BRDA:39,0,0,- +BRDA:39,0,1,- +DA:40,0 +DA:41,0 +DA:43,0 +DA:50,0 +FN:50,NODLMigration.withdraw +FNDA:0,NODLMigration.withdraw +DA:51,0 +DA:52,0 +DA:55,0 +FN:55,NODLMigration._mustNotBeChangingParameters +FNDA:0,NODLMigration._mustNotBeChangingParameters +DA:56,0 +BRDA:56,1,0,- +DA:57,0 +DA:61,0 +FN:61,NODLMigration._proposalExists +FNDA:0,NODLMigration._proposalExists +DA:62,0 +DA:65,0 +FN:65,NODLMigration._createVote +FNDA:0,NODLMigration._createVote +DA:66,0 +DA:67,0 +DA:68,0 +DA:71,0 +FN:71,NODLMigration._withdraw +FNDA:0,NODLMigration._withdraw +DA:72,0 +DA:73,0 +DA:76,0 +FN:76,NODLMigration._flagAsExecuted +FNDA:0,NODLMigration._flagAsExecuted +DA:77,0 +DA:80,0 +FN:80,NODLMigration._incTotalVotes +FNDA:0,NODLMigration._incTotalVotes +DA:81,0 +DA:84,0 +FN:84,NODLMigration._updateLastVote +FNDA:0,NODLMigration._updateLastVote +DA:85,0 +DA:88,0 +FN:88,NODLMigration._totalVotes +FNDA:0,NODLMigration._totalVotes +DA:89,0 +DA:92,0 +FN:92,NODLMigration._lastVote +FNDA:0,NODLMigration._lastVote +DA:93,0 +DA:96,0 +FN:96,NODLMigration._executed +FNDA:0,NODLMigration._executed +DA:97,0 +FNF:12 +FNH:0 +LF:34 +LH:0 +BRF:3 +BRH:0 +end_of_record +TN: +SF:src/contentsign/BaseContentSign.sol +DA:17,0 +FN:17,BaseContentSign.safeMint +FNDA:0,BaseContentSign.safeMint +DA:18,0 +DA:20,0 +DA:21,0 +DA:22,0 +DA:25,0 +FN:25,BaseContentSign.tokenURI +FNDA:0,BaseContentSign.tokenURI +DA:26,0 +DA:29,0 +FN:29,BaseContentSign.supportsInterface +FNDA:0,BaseContentSign.supportsInterface +DA:36,0 +DA:39,0 +FN:39,BaseContentSign._mustBeWhitelisted +FNDA:0,BaseContentSign._mustBeWhitelisted +DA:40,0 +BRDA:40,0,0,- +DA:41,0 +FNF:4 +FNH:0 +LF:12 +LH:0 +BRF:1 +BRH:0 +end_of_record +TN: +SF:src/contentsign/ClickBounty.sol +DA:158,0 +FN:158,ClickBounty.constructor +FNDA:0,ClickBounty.constructor +DA:159,0 +DA:160,0 +DA:162,0 +DA:163,0 +DA:164,0 +DA:181,0 +FN:181,ClickBounty.payEntryFee +FNDA:0,ClickBounty.payEntryFee +DA:182,0 +DA:184,0 +BRDA:184,0,0,- +DA:185,0 +DA:188,0 +DA:189,0 +BRDA:189,1,0,- +DA:190,0 +DA:193,0 +DA:194,0 +DA:209,0 +FN:209,ClickBounty.setEntryFee +FNDA:0,ClickBounty.setEntryFee +DA:210,0 +DA:211,0 +DA:212,0 +DA:224,0 +FN:224,ClickBounty.withdraw +FNDA:0,ClickBounty.withdraw +DA:225,0 +DA:226,0 +DA:254,0 +FN:254,ClickBounty.awardBounty +FNDA:0,ClickBounty.awardBounty +DA:255,0 +DA:257,0 +BRDA:257,2,0,- +DA:258,0 +DA:260,0 +DA:262,0 +BRDA:262,3,0,- +DA:263,0 +DA:265,0 +BRDA:265,4,0,- +DA:266,0 +DA:269,0 +DA:272,0 +DA:275,0 +DA:277,0 +DA:280,0 +DA:294,0 +FN:294,ClickBounty.getLeaderboard +FNDA:0,ClickBounty.getLeaderboard +DA:295,0 +DA:296,0 +DA:297,0 +DA:298,0 +DA:317,0 +FN:317,ClickBounty._updateLeaderboard +FNDA:0,ClickBounty._updateLeaderboard +DA:319,0 +BRDA:319,5,0,- +DA:320,0 +DA:321,0 +DA:322,0 +DA:326,0 +DA:327,0 +DA:328,0 +DA:329,0 +DA:330,0 +BRDA:330,6,0,- +DA:331,0 +DA:332,0 +DA:337,0 +BRDA:337,7,0,- +DA:338,0 +DA:339,0 +FNF:7 +FNH:0 +LF:56 +LH:0 +BRF:8 +BRH:0 +end_of_record +TN: +SF:src/contentsign/ClickContentSign.sol +DA:12,0 +FN:12,ClickContentSign.constructor +FNDA:0,ClickContentSign.constructor +DA:13,0 +DA:16,0 +FN:16,ClickContentSign._userIsWhitelisted +FNDA:0,ClickContentSign._userIsWhitelisted +DA:17,0 +FNF:2 +FNH:0 +LF:4 +LH:0 +BRF:0 +BRH:0 +end_of_record +TN: +SF:src/contentsign/EnterpriseContentSign.sol +DA:13,0 +FN:13,EnterpriseContentSign.constructor +FNDA:0,EnterpriseContentSign.constructor +DA:14,0 +DA:17,0 +FN:17,EnterpriseContentSign.supportsInterface +FNDA:0,EnterpriseContentSign.supportsInterface +DA:23,0 +DA:26,0 +FN:26,EnterpriseContentSign._userIsWhitelisted +FNDA:0,EnterpriseContentSign._userIsWhitelisted +DA:27,0 +FNF:3 +FNH:0 +LF:6 +LH:0 +BRF:0 +BRH:0 +end_of_record +TN: +SF:src/contentsign/PaymentMiddleware.sol +DA:21,0 +FN:21,PaymentMiddleware.constructor +FNDA:0,PaymentMiddleware.constructor +DA:24,0 +DA:25,0 +DA:26,0 +DA:27,0 +DA:30,0 +FN:30,PaymentMiddleware.safeMint +FNDA:0,PaymentMiddleware.safeMint +DA:32,0 +BRDA:32,0,0,- +DA:33,0 +DA:37,0 +DA:40,0 +DA:43,0 +FN:43,PaymentMiddleware.withdraw +FNDA:0,PaymentMiddleware.withdraw +DA:44,0 +DA:46,0 +DA:47,0 +DA:50,0 +FN:50,PaymentMiddleware.setFeeAmount +FNDA:0,PaymentMiddleware.setFeeAmount +DA:51,0 +DA:53,0 +DA:56,0 +FN:56,PaymentMiddleware.setTarget +FNDA:0,PaymentMiddleware.setTarget +DA:57,0 +DA:59,0 +DA:62,0 +FN:62,PaymentMiddleware.setWhitelist +FNDA:0,PaymentMiddleware.setWhitelist +DA:63,0 +DA:65,0 +DA:68,0 +FN:68,PaymentMiddleware.setFeeToken +FNDA:0,PaymentMiddleware.setFeeToken +DA:69,0 +DA:71,0 +FNF:7 +FNH:0 +LF:26 +LH:0 +BRF:1 +BRH:0 +end_of_record +TN: +SF:src/nameservice/ClickNameService.sol +DA:62,0 +FN:62,ClickNameService.constructor +FNDA:0,ClickNameService.constructor +DA:63,0 +DA:64,0 +DA:68,0 +FN:68,ClickNameService.resolve +FNDA:0,ClickNameService.resolve +DA:69,0 +DA:70,0 +DA:71,0 +BRDA:71,0,0,- +DA:72,0 +DA:74,0 +DA:80,0 +FN:80,ClickNameService.batchRegister +FNDA:0,ClickNameService.batchRegister +DA:81,0 +BRDA:81,1,0,- +DA:82,0 +DA:85,0 +DA:86,0 +DA:94,0 +FN:94,ClickNameService.setDefaultExpiry +FNDA:0,ClickNameService.setDefaultExpiry +DA:95,0 +DA:99,0 +FN:99,ClickNameService.register +FNDA:0,ClickNameService.register +DA:100,0 +DA:104,0 +FN:104,ClickNameService.registerWithExpiry +FNDA:0,ClickNameService.registerWithExpiry +DA:105,0 +BRDA:105,2,0,- +DA:106,0 +DA:109,0 +DA:110,0 +DA:111,0 +DA:112,0 +DA:115,0 +FN:115,ClickNameService._register +FNDA:0,ClickNameService._register +DA:116,0 +BRDA:116,3,0,- +DA:117,0 +DA:119,0 +BRDA:119,4,0,- +DA:120,0 +DA:123,0 +DA:124,0 +DA:125,0 +BRDA:125,5,0,- +BRDA:125,5,1,- +DA:126,0 +DA:128,0 +BRDA:128,6,0,- +DA:129,0 +DA:131,0 +DA:134,0 +DA:140,0 +FN:140,ClickNameService.supportsInterface +FNDA:0,ClickNameService.supportsInterface +DA:141,0 +DA:142,0 +DA:149,0 +FN:149,ClickNameService.burn +FNDA:0,ClickNameService.burn +DA:150,0 +DA:151,0 +DA:158,0 +FN:158,ClickNameService.removeExpired +FNDA:0,ClickNameService.removeExpired +DA:159,0 +DA:160,0 +BRDA:160,7,0,- +DA:161,0 +DA:163,0 +DA:164,0 +DA:171,0 +FN:171,ClickNameService.extendExpiry +FNDA:0,ClickNameService.extendExpiry +DA:172,0 +BRDA:172,8,0,- +DA:173,0 +DA:175,0 +BRDA:175,9,0,- +DA:176,0 +DA:179,0 +BRDA:179,10,0,- +BRDA:179,10,1,- +DA:180,0 +DA:182,0 +DA:187,0 +FN:187,ClickNameService._isAlphanumeric +FNDA:0,ClickNameService._isAlphanumeric +DA:188,0 +DA:189,0 +DA:190,0 +DA:191,0 +DA:193,0 +DA:197,0 +FN:197,ClickNameService._isAuthorized +FNDA:0,ClickNameService._isAuthorized +DA:198,0 +FNF:13 +FNH:0 +LF:66 +LH:0 +BRF:13 +BRH:0 +end_of_record +TN: +SF:src/nameservice/NameService.sol +DA:75,0 +FN:75,NameService.constructor +FNDA:0,NameService.constructor +DA:78,0 +DA:79,0 +DA:83,0 +FN:83,NameService.resolve +FNDA:0,NameService.resolve +DA:84,0 +DA:85,0 +DA:86,0 +BRDA:86,0,0,- +DA:87,0 +DA:89,0 +DA:95,0 +FN:95,NameService.batchRegister +FNDA:0,NameService.batchRegister +DA:96,0 +BRDA:96,1,0,- +DA:97,0 +DA:100,0 +DA:101,0 +DA:109,0 +FN:109,NameService.setDefaultExpiry +FNDA:0,NameService.setDefaultExpiry +DA:110,0 +DA:114,0 +FN:114,NameService.register +FNDA:0,NameService.register +DA:115,0 +DA:119,0 +FN:119,NameService.registerWithExpiry +FNDA:0,NameService.registerWithExpiry +DA:120,0 +BRDA:120,2,0,- +DA:121,0 +DA:124,0 +DA:125,0 +DA:126,0 +DA:127,0 +DA:130,0 +FN:130,NameService._register +FNDA:0,NameService._register +DA:131,0 +BRDA:131,3,0,- +DA:132,0 +DA:134,0 +BRDA:134,4,0,- +DA:135,0 +DA:138,0 +DA:139,0 +DA:140,0 +BRDA:140,5,0,- +BRDA:140,5,1,- +DA:141,0 +DA:143,0 +BRDA:143,6,0,- +DA:144,0 +DA:146,0 +DA:149,0 +DA:155,0 +FN:155,NameService.supportsInterface +FNDA:0,NameService.supportsInterface +DA:156,0 +DA:157,0 +DA:164,0 +FN:164,NameService.burn +FNDA:0,NameService.burn +DA:165,0 +DA:167,0 +DA:168,0 +DA:169,0 +DA:171,0 +DA:173,0 +DA:174,0 +DA:181,0 +FN:181,NameService.removeExpired +FNDA:0,NameService.removeExpired +DA:182,0 +DA:183,0 +BRDA:183,7,0,- +DA:184,0 +DA:186,0 +DA:187,0 +DA:194,0 +FN:194,NameService.extendExpiry +FNDA:0,NameService.extendExpiry +DA:195,0 +BRDA:195,8,0,- +DA:196,0 +DA:198,0 +BRDA:198,9,0,- +DA:199,0 +DA:202,0 +BRDA:202,10,0,- +BRDA:202,10,1,- +DA:203,0 +DA:205,0 +DA:210,0 +FN:210,NameService._isAlphanumeric +FNDA:0,NameService._isAlphanumeric +DA:211,0 +DA:212,0 +DA:213,0 +DA:214,0 +DA:216,0 +DA:220,0 +FN:220,NameService._isAuthorized +FNDA:0,NameService._isAuthorized +DA:221,0 +DA:228,0 +FN:228,NameService.setTextRecord +FNDA:0,NameService.setTextRecord +DA:229,0 +DA:231,0 +DA:232,0 +DA:233,0 +DA:235,0 +BRDA:235,12,0,- +DA:236,0 +DA:238,0 +BRDA:238,13,0,- +DA:239,0 +DA:241,0 +DA:242,0 +DA:249,0 +FN:249,NameService.getTextRecord +FNDA:0,NameService.getTextRecord +DA:250,0 +DA:251,0 +BRDA:251,14,0,- +DA:252,0 +DA:254,0 +FNF:15 +FNH:0 +LF:87 +LH:0 +BRF:16 +BRH:0 +end_of_record +TN: +SF:src/nameservice/PaymasterTest.sol +DA:23,0 +FN:23,PaymasterTest.register +FNDA:0,PaymasterTest.register +DA:24,0 +BRDA:24,0,0,- +DA:25,0 +DA:27,0 +BRDA:27,1,0,- +DA:28,0 +DA:31,0 +DA:32,0 +DA:33,0 +BRDA:33,2,0,- +BRDA:33,2,1,- +DA:34,0 +DA:36,0 +DA:39,0 +DA:43,0 +FN:43,PaymasterTest._isAlphanumeric +FNDA:0,PaymasterTest._isAlphanumeric +DA:44,0 +DA:45,0 +DA:46,0 +DA:47,0 +DA:49,0 +FNF:2 +FNH:0 +LF:17 +LH:0 +BRF:4 +BRH:0 +end_of_record +TN: +SF:src/nameservice/UniversalResolver.sol +DA:53,0 +FN:53,UniversalResolver.constructor +FNDA:0,UniversalResolver.constructor +DA:56,0 +DA:57,0 +DA:58,0 +DA:59,0 +DA:62,0 +DA:63,0 +DA:66,0 +FN:66,UniversalResolver.setUrl +FNDA:0,UniversalResolver.setUrl +DA:67,0 +DA:81,0 +FN:81,UniversalResolver._parseDnsDomain +FNDA:0,UniversalResolver._parseDnsDomain +DA:86,0 +DA:88,0 +DA:89,0 +DA:93,0 +DA:95,0 +DA:96,0 +DA:100,0 +DA:102,0 +DA:103,0 +DA:105,0 +DA:110,0 +FN:110,UniversalResolver.getStorageKey +FNDA:0,UniversalResolver.getStorageKey +DA:111,0 +DA:112,0 +DA:119,0 +FN:119,UniversalResolver.getTextRecordStorageKey +FNDA:0,UniversalResolver.getTextRecordStorageKey +DA:120,0 +DA:121,0 +DA:122,0 +DA:130,0 +FN:130,UniversalResolver.resolve +FNDA:0,UniversalResolver.resolve +DA:131,0 +DA:133,0 +BRDA:133,2,0,- +DA:134,0 +DA:137,0 +DA:138,0 +DA:140,0 +BRDA:140,3,0,- +BRDA:140,3,1,- +DA:141,0 +DA:142,0 +DA:143,0 +BRDA:143,4,0,- +BRDA:143,4,1,- +DA:144,0 +DA:145,0 +BRDA:145,5,0,- +DA:146,0 +DA:147,0 +BRDA:147,6,0,- +DA:148,0 +DA:152,0 +DA:155,0 +DA:156,0 +DA:158,0 +DA:159,0 +DA:161,0 +DA:167,0 +FN:167,UniversalResolver.resolveWithProof +FNDA:0,UniversalResolver.resolveWithProof +DA:168,0 +DA:169,0 +DA:172,0 +DA:174,0 +DA:176,0 +DA:178,0 +BRDA:178,7,0,- +DA:179,0 +DA:182,0 +BRDA:182,8,0,- +BRDA:182,8,1,- +DA:183,0 +DA:184,0 +BRDA:184,9,0,- +BRDA:184,9,1,- +DA:185,0 +DA:187,0 +DA:194,0 +FN:194,UniversalResolver.supportsInterface +FNDA:0,UniversalResolver.supportsInterface +DA:195,0 +DA:196,0 +FNF:8 +FNH:0 +LF:64 +LH:0 +BRF:12 +BRH:0 +end_of_record +TN: +SF:src/paymasters/BasePaymaster.sol +DA:33,0 +FN:33,BasePaymaster.constructor +FNDA:0,BasePaymaster.constructor +DA:34,0 +DA:35,0 +DA:38,0 +FN:38,BasePaymaster.validateAndPayForPaymasterTransaction +FNDA:0,BasePaymaster.validateAndPayForPaymasterTransaction +DA:43,0 +DA:46,0 +DA:48,0 +BRDA:48,0,0,- +DA:49,0 +DA:52,0 +DA:56,0 +DA:57,0 +DA:58,0 +DA:60,0 +BRDA:60,1,0,- +BRDA:60,1,1,- +DA:61,0 +DA:62,0 +BRDA:62,2,0,- +BRDA:62,2,1,- +DA:63,0 +DA:64,0 +DA:66,0 +DA:68,0 +DA:72,0 +DA:73,0 +BRDA:73,3,0,- +DA:74,0 +DA:77,0 +DA:80,0 +FN:80,BasePaymaster.postTransaction +FNDA:0,BasePaymaster.postTransaction +DA:88,0 +DA:93,0 +FN:93,BasePaymaster.withdraw +FNDA:0,BasePaymaster.withdraw +DA:94,0 +DA:96,0 +DA:97,0 +BRDA:97,4,0,- +DA:99,0 +DA:104,0 +FN:104,BasePaymaster._mustBeBootloader +FNDA:0,BasePaymaster._mustBeBootloader +DA:105,0 +BRDA:105,5,0,- +DA:106,0 +FNF:5 +FNH:0 +LF:33 +LH:0 +BRF:8 +BRH:0 +end_of_record +TN: +SF:src/paymasters/WhitelistPaymaster.sol +DA:22,0 +FN:22,WhitelistPaymaster.constructor +FNDA:0,WhitelistPaymaster.constructor +DA:23,0 +DA:26,0 +FN:26,WhitelistPaymaster.addWhitelistedContracts +FNDA:0,WhitelistPaymaster.addWhitelistedContracts +DA:27,0 +DA:29,0 +DA:30,0 +DA:33,0 +DA:36,0 +FN:36,WhitelistPaymaster.removeWhitelistedContracts +FNDA:0,WhitelistPaymaster.removeWhitelistedContracts +DA:37,0 +DA:39,0 +DA:40,0 +DA:43,0 +DA:46,0 +FN:46,WhitelistPaymaster.addWhitelistedUsers +FNDA:0,WhitelistPaymaster.addWhitelistedUsers +DA:47,0 +DA:49,0 +DA:50,0 +DA:53,0 +DA:56,0 +FN:56,WhitelistPaymaster.removeWhitelistedUsers +FNDA:0,WhitelistPaymaster.removeWhitelistedUsers +DA:57,0 +DA:59,0 +DA:60,0 +DA:63,0 +DA:66,0 +FN:66,WhitelistPaymaster._validateAndPayGeneralFlow +FNDA:0,WhitelistPaymaster._validateAndPayGeneralFlow +DA:67,0 +BRDA:67,0,0,- +DA:68,0 +DA:71,0 +BRDA:71,1,0,- +DA:72,0 +DA:76,0 +FN:76,WhitelistPaymaster._validateAndPayApprovalBasedFlow +FNDA:0,WhitelistPaymaster._validateAndPayApprovalBasedFlow +DA:81,0 +FNF:7 +FNH:0 +LF:29 +LH:0 +BRF:2 +BRH:0 +end_of_record +TN: +SF:src/swarms/FleetIdentity.sol +DA:227,128 +FN:227,FleetIdentity.constructor +FNDA:128,FleetIdentity.constructor +DA:228,128 +DA:229,128 +DA:243,0 +FN:243,FleetIdentity.registerFleetCountry +FNDA:0,FleetIdentity.registerFleetCountry +DA:248,0 +BRDA:248,0,0,- +DA:249,0 +BRDA:249,1,0,- +DA:250,0 +DA:251,0 +DA:252,0 +DA:267,1389 +FN:267,FleetIdentity.registerFleetLocal +FNDA:1389,FleetIdentity.registerFleetLocal +DA:272,1389 +BRDA:272,2,0,- +DA:273,1389 +BRDA:273,3,0,- +DA:274,1389 +BRDA:274,4,0,- +DA:275,1389 +DA:276,1389 +DA:277,1389 +DA:286,0 +FN:286,FleetIdentity.promote +FNDA:0,FleetIdentity.promote +DA:287,0 +DA:294,0 +FN:294,FleetIdentity.reassignTier +FNDA:0,FleetIdentity.reassignTier +DA:295,0 +DA:296,0 +BRDA:296,5,0,- +DA:297,0 +BRDA:297,6,0,- +BRDA:297,6,1,- +DA:298,0 +DA:300,0 +DA:316,0 +FN:316,FleetIdentity.setOperator +FNDA:0,FleetIdentity.setOperator +DA:318,0 +BRDA:318,7,0,- +DA:321,0 +BRDA:321,8,0,- +DA:322,0 +DA:325,0 +DA:328,0 +DA:329,0 +DA:332,0 +DA:335,0 +DA:338,0 +BRDA:338,9,0,- +DA:340,0 +DA:342,0 +DA:345,0 +DA:367,20 +FN:367,FleetIdentity.burn +FNDA:20,FleetIdentity.burn +DA:368,20 +DA:370,20 +DA:371,20 +DA:372,20 +DA:373,20 +DA:374,20 +DA:376,20 +BRDA:376,10,0,10 +BRDA:376,10,1,- +DA:378,10 +BRDA:378,11,0,- +DA:380,10 +DA:381,10 +DA:382,10 +DA:384,10 +DA:387,10 +BRDA:387,12,0,- +DA:388,0 +DA:391,10 +DA:392,10 +DA:395,10 +DA:397,10 +DA:398,10 +DA:400,10 +BRDA:400,13,0,10 +BRDA:400,13,1,- +DA:402,10 +DA:403,10 +DA:404,10 +DA:408,0 +DA:412,10 +DA:414,10 +DA:427,0 +FN:427,FleetIdentity.claimUuid +FNDA:0,FleetIdentity.claimUuid +DA:428,0 +BRDA:428,14,0,- +DA:429,0 +BRDA:429,15,0,- +DA:432,0 +DA:433,0 +DA:434,0 +DA:436,0 +DA:439,0 +DA:440,0 +DA:442,0 +DA:444,0 +DA:454,1399 +FN:454,FleetIdentity.tierBond +FNDA:1399,FleetIdentity.tierBond +DA:455,1399 +DA:456,1399 +DA:462,0 +FN:462,FleetIdentity.localInclusionHint +FNDA:0,FleetIdentity.localInclusionHint +DA:467,0 +BRDA:467,16,0,- +DA:468,0 +BRDA:468,17,0,- +DA:469,0 +DA:470,0 +DA:478,0 +FN:478,FleetIdentity.countryInclusionHint +FNDA:0,FleetIdentity.countryInclusionHint +DA:479,0 +BRDA:479,18,0,- +DA:482,0 +DA:485,0 +DA:486,0 +DA:487,0 +DA:488,0 +DA:489,0 +DA:490,0 +BRDA:490,19,0,- +DA:492,0 +DA:496,0 +FN:496,FleetIdentity.highestActiveTier +FNDA:0,FleetIdentity.highestActiveTier +DA:497,0 +DA:498,0 +DA:499,0 +DA:503,0 +FN:503,FleetIdentity.tierMemberCount +FNDA:0,FleetIdentity.tierMemberCount +DA:504,0 +DA:508,0 +FN:508,FleetIdentity.getTierMembers +FNDA:0,FleetIdentity.getTierMembers +DA:509,0 +DA:513,0 +FN:513,FleetIdentity.getTierUuids +FNDA:0,FleetIdentity.getTierUuids +DA:514,0 +DA:515,0 +DA:516,0 +DA:517,0 +DA:522,20 +FN:522,FleetIdentity.tokenUuid +FNDA:20,FleetIdentity.tokenUuid +DA:523,20 +DA:527,1439 +FN:527,FleetIdentity.tokenRegion +FNDA:1439,FleetIdentity.tokenRegion +DA:528,1439 +DA:532,1389 +FN:532,FleetIdentity.computeTokenId +FNDA:1389,FleetIdentity.computeTokenId +DA:533,1389 +DA:537,0 +FN:537,FleetIdentity.bonds +FNDA:0,FleetIdentity.bonds +DA:538,0 +DA:539,0 +DA:540,0 +DA:541,0 +DA:545,0 +FN:545,FleetIdentity.isOwnedOnly +FNDA:0,FleetIdentity.isOwnedOnly +DA:546,0 +DA:554,20 +FN:554,FleetIdentity.operatorOf +FNDA:20,FleetIdentity.operatorOf +DA:555,20 +DA:556,20 +BRDA:556,23,0,20 +DA:557,20 +DA:582,0 +FN:582,FleetIdentity.buildHighestBondedUuidBundle +FNDA:0,FleetIdentity.buildHighestBondedUuidBundle +DA:587,0 +BRDA:587,24,0,- +DA:588,0 +BRDA:588,25,0,- +DA:590,0 +DA:591,0 +DA:593,0 +DA:610,0 +FN:610,FleetIdentity.buildCountryOnlyBundle +FNDA:0,FleetIdentity.buildCountryOnlyBundle +DA:615,0 +BRDA:615,26,0,- +DA:617,0 +DA:619,0 +DA:621,0 +DA:633,0 +FN:633,FleetIdentity._buildHighestBondedUuidBundle +FNDA:0,FleetIdentity._buildHighestBondedUuidBundle +DA:638,0 +DA:640,0 +DA:643,0 +DA:644,0 +DA:647,0 +DA:650,0 +DA:655,0 +DA:662,0 +FN:662,FleetIdentity._appendTierUuids +FNDA:0,FleetIdentity._appendTierUuids +DA:668,0 +DA:669,0 +DA:670,0 +DA:671,0 +DA:673,0 +DA:674,0 +DA:675,0 +DA:677,0 +DA:685,0 +FN:685,FleetIdentity.getActiveCountries +FNDA:0,FleetIdentity.getActiveCountries +DA:686,0 +DA:691,0 +FN:691,FleetIdentity.getActiveAdminAreas +FNDA:0,FleetIdentity.getActiveAdminAreas +DA:693,0 +DA:694,0 +DA:695,0 +DA:696,0 +DA:700,0 +DA:701,0 +DA:702,0 +DA:703,0 +DA:704,0 +DA:705,0 +DA:706,0 +DA:709,0 +DA:714,0 +FN:714,FleetIdentity.getCountryAdminAreas +FNDA:0,FleetIdentity.getCountryAdminAreas +DA:715,0 +DA:720,1389 +FN:720,FleetIdentity.makeAdminRegion +FNDA:1389,FleetIdentity.makeAdminRegion +DA:721,1389 +DA:731,1393 +FN:731,FleetIdentity._countryFromRegion +FNDA:1393,FleetIdentity._countryFromRegion +DA:732,1393 +DA:736,0 +FN:736,FleetIdentity._adminFromRegion +FNDA:0,FleetIdentity._adminFromRegion +DA:737,0 +DA:742,2798 +FN:742,FleetIdentity._isCountryRegion +FNDA:2798,FleetIdentity._isCountryRegion +DA:743,2798 +DA:749,1389 +FN:749,FleetIdentity._pullBond +FNDA:1389,FleetIdentity._pullBond +DA:750,1389 +BRDA:750,27,0,1389 +DA:751,1389 +DA:756,20 +FN:756,FleetIdentity._refundBond +FNDA:20,FleetIdentity._refundBond +DA:757,20 +BRDA:757,28,0,20 +DA:758,20 +DA:765,10 +FN:765,FleetIdentity._clearUuidOwnership +FNDA:10,FleetIdentity._clearUuidOwnership +DA:766,10 +DA:767,10 +DA:768,10 +DA:769,10 +DA:770,10 +DA:775,0 +FN:775,FleetIdentity._decrementUuidCount +FNDA:0,FleetIdentity._decrementUuidCount +DA:776,0 +DA:777,0 +BRDA:777,29,0,- +BRDA:777,29,1,- +DA:778,0 +DA:780,0 +DA:788,10 +FN:788,FleetIdentity._cleanupFleetFromTier +FNDA:10,FleetIdentity._cleanupFleetFromTier +DA:789,10 +DA:790,10 +DA:791,10 +DA:792,10 +DA:793,10 +DA:800,1389 +FN:800,FleetIdentity._mintFleetToken +FNDA:1389,FleetIdentity._mintFleetToken +DA:801,1389 +DA:802,1389 +DA:803,1389 +DA:804,1389 +DA:805,1389 +DA:810,0 +FN:810,FleetIdentity._mintFleetTokenTo +FNDA:0,FleetIdentity._mintFleetTokenTo +DA:811,0 +DA:812,0 +DA:813,0 +DA:814,0 +DA:815,0 +DA:824,1389 +FN:824,FleetIdentity._register +FNDA:1389,FleetIdentity._register +DA:825,1389 +DA:826,1389 +DA:827,1389 +DA:828,0 +DA:830,1389 +BRDA:830,30,0,- +BRDA:830,30,1,- +DA:832,0 +DA:833,0 +BRDA:833,31,0,- +DA:834,0 +DA:836,0 +DA:837,0 +DA:838,0 +DA:840,0 +DA:843,0 +DA:845,0 +DA:846,1389 +BRDA:846,32,0,1389 +BRDA:846,32,1,- +DA:848,1389 +DA:849,1389 +DA:850,1389 +DA:851,1389 +DA:854,1389 +DA:857,1389 +DA:859,1389 +DA:862,0 +DA:863,0 +BRDA:863,33,0,- +DA:864,0 +BRDA:864,34,0,- +DA:865,0 +DA:867,0 +DA:868,0 +DA:870,0 +DA:873,0 +DA:875,0 +DA:880,0 +FN:880,FleetIdentity._promote +FNDA:0,FleetIdentity._promote +DA:881,0 +DA:882,0 +DA:883,0 +BRDA:883,35,0,- +DA:885,0 +DA:886,0 +DA:887,0 +BRDA:887,36,0,- +DA:888,0 +BRDA:888,37,0,- +DA:889,0 +BRDA:889,38,0,- +DA:891,0 +DA:892,0 +DA:893,0 +DA:894,0 +DA:897,0 +DA:898,0 +DA:899,0 +DA:900,0 +DA:903,0 +DA:905,0 +DA:909,0 +FN:909,FleetIdentity._demote +FNDA:0,FleetIdentity._demote +DA:910,0 +DA:911,0 +DA:912,0 +BRDA:912,39,0,- +DA:914,0 +DA:915,0 +DA:916,0 +BRDA:916,40,0,- +DA:917,0 +BRDA:917,41,0,- +DA:919,0 +DA:920,0 +DA:921,0 +DA:922,0 +DA:925,0 +DA:926,0 +DA:927,0 +DA:928,0 +DA:929,0 +DA:932,0 +DA:934,0 +DA:938,1389 +FN:938,FleetIdentity._validateExplicitTier +FNDA:1389,FleetIdentity._validateExplicitTier +DA:939,1389 +BRDA:939,42,0,- +DA:940,1389 +BRDA:940,43,0,- +DA:946,0 +FN:946,FleetIdentity._findMaxTierIndex +FNDA:0,FleetIdentity._findMaxTierIndex +DA:951,0 +DA:952,0 +DA:954,0 +DA:955,0 +BRDA:955,44,0,- +DA:956,0 +DA:976,0 +FN:976,FleetIdentity._findCheapestInclusionTier +FNDA:0,FleetIdentity._findCheapestInclusionTier +DA:981,0 +DA:982,0 +DA:983,0 +DA:985,0 +DA:990,0 +DA:991,0 +DA:992,0 +DA:994,0 +BRDA:994,45,0,- +DA:995,0 +DA:1000,0 +DA:1001,0 +DA:1002,0 +DA:1003,0 +DA:1007,0 +BRDA:1007,46,0,- +DA:1008,0 +DA:1011,0 +DA:1016,1389 +FN:1016,FleetIdentity._addToTier +FNDA:1389,FleetIdentity._addToTier +DA:1017,1389 +DA:1018,1389 +DA:1021,1389 +BRDA:1021,47,0,1383 +DA:1022,1383 +DA:1027,10 +FN:1027,FleetIdentity._removeFromTier +FNDA:10,FleetIdentity._removeFromTier +DA:1028,10 +DA:1029,10 +DA:1030,10 +DA:1032,10 +BRDA:1032,48,0,- +DA:1033,0 +DA:1034,0 +DA:1035,0 +DA:1037,10 +DA:1041,10 +FN:1041,FleetIdentity._trimTierCount +FNDA:10,FleetIdentity._trimTierCount +DA:1042,10 +DA:1043,20 +DA:1044,10 +DA:1046,10 +DA:1052,1389 +FN:1052,FleetIdentity._addToRegionIndex +FNDA:1389,FleetIdentity._addToRegionIndex +DA:1053,1389 +BRDA:1053,49,0,- +BRDA:1053,49,1,1383 +DA:1055,0 +DA:1056,0 +BRDA:1056,50,0,- +DA:1057,0 +DA:1058,0 +DA:1062,1389 +BRDA:1062,51,0,1383 +DA:1063,1383 +DA:1065,1383 +BRDA:1065,52,0,1383 +DA:1066,1383 +DA:1067,1383 +DA:1069,1383 +DA:1070,1383 +DA:1076,10 +FN:1076,FleetIdentity._removeFromRegionIndex +FNDA:10,FleetIdentity._removeFromRegionIndex +DA:1077,10 +DA:1079,10 +BRDA:1079,54,0,- +BRDA:1079,54,1,- +DA:1080,0 +DA:1081,0 +DA:1082,0 +BRDA:1082,55,0,- +DA:1084,0 +DA:1086,0 +DA:1087,0 +DA:1088,0 +BRDA:1088,57,0,- +DA:1089,0 +DA:1090,0 +DA:1091,0 +DA:1093,0 +DA:1094,0 +DA:1098,10 +DA:1099,10 +BRDA:1099,58,0,10 +DA:1100,10 +DA:1101,10 +DA:1102,10 +DA:1103,10 +DA:1104,10 +BRDA:1104,59,0,- +DA:1105,0 +DA:1106,0 +DA:1107,0 +DA:1109,10 +DA:1110,10 +DA:1113,10 +BRDA:1113,60,0,10 +DA:1114,10 +DA:1115,10 +BRDA:1115,61,0,10 +DA:1116,10 +DA:1117,10 +DA:1118,10 +BRDA:1118,62,0,- +DA:1119,0 +DA:1120,0 +DA:1121,0 +DA:1123,10 +DA:1124,10 +DA:1135,1419 +FN:1135,FleetIdentity._update +FNDA:1419,FleetIdentity._update +DA:1136,1419 +DA:1140,1419 +DA:1141,1419 +BRDA:1141,63,0,- +DA:1142,0 +DA:1145,1419 +DA:1148,0 +FN:1148,FleetIdentity._increaseBalance +FNDA:0,FleetIdentity._increaseBalance +DA:1149,0 +DA:1152,0 +FN:1152,FleetIdentity.supportsInterface +FNDA:0,FleetIdentity.supportsInterface +DA:1153,0 +FNF:53 +FNH:24 +LF:387 +LH:144 +BRF:67 +BRH:13 +end_of_record +TN: +SF:src/swarms/ServiceProvider.sol +DA:27,1407 +FN:27,ServiceProvider.registerProvider +FNDA:1407,ServiceProvider.registerProvider +DA:28,1407 +BRDA:28,0,0,- +DA:29,0 +DA:32,1407 +DA:34,1407 +DA:36,1407 +DA:38,1407 +DA:43,15 +FN:43,ServiceProvider.burn +FNDA:15,ServiceProvider.burn +DA:44,15 +BRDA:44,1,0,- +DA:45,0 +DA:48,15 +DA:50,15 +DA:52,15 +FNF:2 +FNH:2 +LF:13 +LH:11 +BRF:2 +BRH:0 +end_of_record +TN: +SF:src/swarms/SwarmRegistryL1.sol +DA:83,313 +FN:83,SwarmRegistryL1.computeSwarmId +FNDA:313,SwarmRegistryL1.computeSwarmId +DA:88,313 +DA:91,63 +FN:91,SwarmRegistryL1.constructor +FNDA:63,SwarmRegistryL1.constructor +DA:92,63 +BRDA:92,0,0,3 +DA:93,3 +DA:95,60 +DA:96,60 +DA:106,573 +FN:106,SwarmRegistryL1.registerSwarm +FNDA:573,SwarmRegistryL1.registerSwarm +DA:113,573 +BRDA:113,1,0,1 +DA:114,1 +DA:116,572 +BRDA:116,2,0,258 +DA:117,258 +DA:119,314 +BRDA:119,3,0,2 +DA:120,2 +DA:124,312 +BRDA:124,4,0,1 +DA:125,1 +DA:127,311 +BRDA:127,5,0,- +DA:128,0 +DA:131,310 +DA:133,310 +BRDA:133,6,0,1 +DA:134,1 +DA:137,309 +DA:138,309 +DA:139,309 +DA:140,309 +DA:141,309 +DA:142,309 +DA:144,309 +DA:145,309 +DA:147,309 +DA:149,309 +DA:154,7 +FN:154,SwarmRegistryL1.acceptSwarm +FNDA:7,SwarmRegistryL1.acceptSwarm +DA:155,7 +DA:156,7 +BRDA:156,7,0,1 +DA:158,6 +DA:159,6 +BRDA:159,8,0,2 +DA:161,4 +BRDA:161,9,0,1 +DA:162,1 +DA:164,3 +DA:165,3 +DA:170,4 +FN:170,SwarmRegistryL1.rejectSwarm +FNDA:4,SwarmRegistryL1.rejectSwarm +DA:171,4 +DA:172,4 +BRDA:172,10,0,- +DA:174,4 +DA:175,4 +BRDA:175,11,0,1 +DA:177,3 +BRDA:177,12,0,1 +DA:178,1 +DA:180,2 +DA:181,2 +DA:187,5 +FN:187,SwarmRegistryL1.updateSwarmProvider +FNDA:5,SwarmRegistryL1.updateSwarmProvider +DA:188,5 +DA:189,5 +BRDA:189,13,0,1 +DA:190,1 +DA:192,4 +BRDA:192,14,0,1 +DA:193,1 +DA:195,3 +BRDA:195,15,0,- +DA:196,0 +DA:199,2 +DA:201,2 +DA:203,2 +DA:205,2 +DA:210,7 +FN:210,SwarmRegistryL1.deleteSwarm +FNDA:7,SwarmRegistryL1.deleteSwarm +DA:211,7 +DA:212,7 +BRDA:212,16,0,1 +DA:213,1 +DA:215,6 +BRDA:215,17,0,1 +DA:216,1 +DA:219,5 +DA:221,5 +DA:223,5 +DA:225,5 +DA:232,26 +FN:232,SwarmRegistryL1.isSwarmValid +FNDA:26,SwarmRegistryL1.isSwarmValid +DA:233,26 +DA:234,26 +BRDA:234,18,0,1 +DA:237,25 +DA:239,25 +BRDA:239,19,0,25 +DA:240,18 +DA:241,7 +BRDA:241,19,1,7 +DA:242,7 +DA:248,6 +FN:248,SwarmRegistryL1.purgeOrphanedSwarm +FNDA:6,SwarmRegistryL1.purgeOrphanedSwarm +DA:249,6 +DA:250,6 +BRDA:250,20,0,1 +DA:252,5 +DA:253,5 +BRDA:253,21,0,1 +DA:255,4 +DA:257,4 +DA:259,4 +DA:261,4 +DA:268,7 +FN:268,SwarmRegistryL1.checkMembership +FNDA:7,SwarmRegistryL1.checkMembership +DA:269,7 +DA:270,7 +BRDA:270,22,0,1 +DA:271,1 +DA:275,6 +DA:276,6 +BRDA:276,23,0,1 +DA:278,5 +DA:279,5 +DA:281,0 +DA:285,5 +BRDA:285,24,0,5 +DA:287,5 +DA:292,5 +DA:293,5 +DA:295,0 +DA:297,4 +DA:298,4 +DA:299,4 +DA:301,4 +DA:302,4 +DA:304,4 +DA:305,4 +DA:306,4 +DA:308,4 +DA:314,9 +FN:314,SwarmRegistryL1._removeFromUuidSwarms +FNDA:9,SwarmRegistryL1._removeFromUuidSwarms +DA:315,9 +DA:316,9 +DA:317,9 +DA:319,9 +DA:320,9 +DA:321,9 +DA:322,9 +DA:331,12 +FN:331,SwarmRegistryL1._readFingerprint +FNDA:12,SwarmRegistryL1._readFingerprint +DA:332,12 +DA:333,12 +DA:334,12 +DA:337,12 +DA:340,12 +DA:341,12 +DA:342,24 +DA:344,24 +DA:348,12 +DA:349,12 +DA:350,12 +DA:352,12 +FNF:12 +FNH:12 +LF:132 +LH:128 +BRF:26 +BRH:23 +end_of_record +TN: +SF:src/swarms/SwarmRegistryUniversal.sol +DA:89,576 +FN:89,SwarmRegistryUniversal.computeSwarmId +FNDA:576,SwarmRegistryUniversal.computeSwarmId +DA:94,576 +DA:97,71 +FN:97,SwarmRegistryUniversal.constructor +FNDA:71,SwarmRegistryUniversal.constructor +DA:98,71 +BRDA:98,0,0,3 +DA:99,3 +DA:101,68 +DA:102,68 +DA:112,836 +FN:112,SwarmRegistryUniversal.registerSwarm +FNDA:836,SwarmRegistryUniversal.registerSwarm +DA:119,836 +BRDA:119,1,0,1 +DA:120,1 +DA:122,835 +BRDA:122,2,0,258 +DA:123,258 +DA:125,577 +BRDA:125,3,0,1 +DA:126,1 +DA:128,576 +BRDA:128,4,0,1 +DA:129,1 +DA:133,575 +BRDA:133,5,0,1 +DA:134,1 +DA:136,574 +BRDA:136,6,0,- +DA:137,0 +DA:140,573 +DA:142,573 +BRDA:142,7,0,1 +DA:143,1 +DA:146,572 +DA:147,572 +DA:148,572 +DA:149,572 +DA:150,572 +DA:151,572 +DA:152,572 +DA:154,572 +DA:156,572 +DA:157,572 +DA:159,572 +DA:164,9 +FN:164,SwarmRegistryUniversal.acceptSwarm +FNDA:9,SwarmRegistryUniversal.acceptSwarm +DA:165,9 +DA:166,9 +BRDA:166,8,0,1 +DA:168,8 +DA:169,8 +BRDA:169,9,0,2 +DA:171,6 +BRDA:171,10,0,2 +DA:172,2 +DA:174,4 +DA:175,4 +DA:180,5 +FN:180,SwarmRegistryUniversal.rejectSwarm +FNDA:5,SwarmRegistryUniversal.rejectSwarm +DA:181,5 +DA:182,5 +BRDA:182,11,0,- +DA:184,5 +DA:185,5 +BRDA:185,12,0,1 +DA:187,4 +BRDA:187,13,0,1 +DA:188,1 +DA:190,3 +DA:191,3 +DA:197,5 +FN:197,SwarmRegistryUniversal.updateSwarmProvider +FNDA:5,SwarmRegistryUniversal.updateSwarmProvider +DA:198,5 +DA:199,5 +BRDA:199,14,0,1 +DA:200,1 +DA:202,4 +BRDA:202,15,0,1 +DA:203,1 +DA:205,3 +BRDA:205,16,0,- +DA:206,0 +DA:209,2 +DA:212,2 +DA:213,2 +DA:215,2 +DA:220,8 +FN:220,SwarmRegistryUniversal.deleteSwarm +FNDA:8,SwarmRegistryUniversal.deleteSwarm +DA:221,8 +DA:222,8 +BRDA:222,17,0,1 +DA:223,1 +DA:225,7 +BRDA:225,18,0,1 +DA:226,1 +DA:229,6 +DA:231,6 +DA:233,6 +DA:234,6 +DA:236,6 +DA:243,29 +FN:243,SwarmRegistryUniversal.isSwarmValid +FNDA:29,SwarmRegistryUniversal.isSwarmValid +DA:244,29 +DA:245,29 +BRDA:245,19,0,1 +DA:248,28 +DA:250,28 +BRDA:250,20,0,28 +DA:251,20 +DA:252,8 +BRDA:252,20,1,8 +DA:253,8 +DA:259,7 +FN:259,SwarmRegistryUniversal.purgeOrphanedSwarm +FNDA:7,SwarmRegistryUniversal.purgeOrphanedSwarm +DA:260,7 +DA:261,7 +BRDA:261,21,0,1 +DA:263,6 +DA:264,6 +BRDA:264,22,0,1 +DA:266,5 +DA:268,5 +DA:270,5 +DA:271,5 +DA:273,5 +DA:280,6 +FN:280,SwarmRegistryUniversal.checkMembership +FNDA:6,SwarmRegistryUniversal.checkMembership +DA:281,6 +DA:282,6 +BRDA:282,23,0,1 +DA:283,1 +DA:287,5 +DA:288,5 +BRDA:288,24,0,1 +DA:290,4 +DA:291,4 +DA:294,4 +DA:295,4 +DA:298,4 +DA:299,4 +DA:300,4 +DA:302,4 +DA:303,4 +DA:306,4 +DA:307,4 +DA:308,4 +DA:310,4 +DA:316,3 +FN:316,SwarmRegistryUniversal.getFilterData +FNDA:3,SwarmRegistryUniversal.getFilterData +DA:317,3 +BRDA:317,26,0,1 +DA:318,1 +DA:320,2 +DA:326,11 +FN:326,SwarmRegistryUniversal._removeFromUuidSwarms +FNDA:11,SwarmRegistryUniversal._removeFromUuidSwarms +DA:327,11 +DA:328,11 +DA:329,11 +DA:331,11 +DA:332,11 +DA:333,11 +DA:334,11 +DA:343,12 +FN:343,SwarmRegistryUniversal._readFingerprint +FNDA:12,SwarmRegistryUniversal._readFingerprint +DA:344,12 +DA:345,12 +DA:346,12 +DA:349,12 +DA:350,12 +DA:351,24 +DA:353,24 +DA:358,12 +DA:359,12 +DA:360,12 +DA:362,12 +FNF:13 +FNH:13 +LF:136 +LH:134 +BRF:27 +BRH:24 +end_of_record +TN: +SF:test/FleetIdentity.t.sol +DA:12,0 +FN:12,MockERC20.mint +FNDA:0,MockERC20.mint +DA:13,0 +DA:23,0 +FN:23,BadERC20.mint +FNDA:0,BadERC20.mint +DA:24,0 +DA:27,0 +FN:27,BadERC20.setFail +FNDA:0,BadERC20.setFail +DA:28,0 +DA:31,0 +FN:31,BadERC20.transfer +FNDA:0,BadERC20.transfer +DA:33,0 +DA:36,0 +FN:36,BadERC20.transferFrom +FNDA:0,BadERC20.transferFrom +DA:38,0 +FNF:5 +FNH:0 +LF:10 +LH:0 +BRF:0 +BRH:0 +end_of_record +TN: +SF:test/FleetIdentityFairness.t.sol +DA:12,0 +FN:12,MockERC20Fairness.mint +FNDA:0,MockERC20Fairness.mint +DA:13,0 +FNF:1 +FNH:0 +LF:2 +LH:0 +BRF:0 +BRH:0 +end_of_record +TN: +SF:test/Grants.t.sol +DA:9,0 +FN:9,MockToken.constructor +FNDA:0,MockToken.constructor +DA:10,0 +FNF:1 +FNH:0 +LF:2 +LH:0 +BRF:0 +BRH:0 +end_of_record +TN: +SF:test/Payment.t.sol +DA:12,0 +FN:12,MockToken.constructor +FNDA:0,MockToken.constructor +DA:13,0 +FNF:1 +FNH:0 +LF:2 +LH:0 +BRF:0 +BRH:0 +end_of_record +TN: +SF:test/QuotaControl.t.sol +DA:14,0 +FN:14,TestableQuotaControl.exposeCheckedResetClaimed +FNDA:0,TestableQuotaControl.exposeCheckedResetClaimed +DA:15,0 +DA:18,0 +FN:18,TestableQuotaControl.exposeCheckedUpdateClaimed +FNDA:0,TestableQuotaControl.exposeCheckedUpdateClaimed +DA:19,0 +FNF:2 +FNH:0 +LF:4 +LH:0 +BRF:0 +BRH:0 +end_of_record +TN: +SF:test/SwarmRegistryL1.t.sol +DA:13,60 +FN:13,MockBondTokenL1.mint +FNDA:60,MockBondTokenL1.mint +DA:14,60 +FNF:1 +FNH:1 +LF:2 +LH:2 +BRF:0 +BRH:0 +end_of_record +TN: +SF:test/SwarmRegistryUniversal.t.sol +DA:13,68 +FN:13,MockBondTokenUniv.mint +FNDA:68,MockBondTokenUniv.mint +DA:14,68 +FNF:1 +FNH:1 +LF:2 +LH:2 +BRF:0 +BRH:0 +end_of_record +TN: +SF:test/__helpers__/AccessControlUtils.sol +DA:9,0 +FN:9,AccessControlUtils.expectRevert_AccessControlUnauthorizedAccount +FNDA:0,AccessControlUtils.expectRevert_AccessControlUnauthorizedAccount +DA:10,0 +FNF:1 +FNH:0 +LF:2 +LH:0 +BRF:0 +BRH:0 +end_of_record +TN: +SF:test/bridge/L1Bridge.t.sol +DA:31,0 +FN:31,MockMailbox.setL1ToL2Failed +FNDA:0,MockMailbox.setL1ToL2Failed +DA:32,0 +DA:35,0 +FN:35,MockMailbox.setInclusion +FNDA:0,MockMailbox.setInclusion +DA:36,0 +DA:39,0 +FN:39,MockMailbox.setBaseCostReturn +FNDA:0,MockMailbox.setBaseCostReturn +DA:40,0 +DA:43,0 +FN:43,MockMailbox.expectBaseCostParams +FNDA:0,MockMailbox.expectBaseCostParams +DA:44,0 +DA:45,0 +DA:46,0 +DA:50,0 +FN:50,MockMailbox.requestL2Transaction +FNDA:0,MockMailbox.requestL2Transaction +DA:59,0 +DA:60,0 +DA:63,0 +DA:66,0 +FN:66,MockMailbox.proveL1ToL2TransactionStatus +FNDA:0,MockMailbox.proveL1ToL2TransactionStatus +DA:74,0 +BRDA:74,0,0,- +DA:75,0 +DA:77,0 +DA:80,0 +FN:80,MockMailbox.proveL2MessageInclusion +FNDA:0,MockMailbox.proveL2MessageInclusion +DA:86,0 +DA:89,0 +FN:89,MockMailbox.l2TransactionBaseCost +FNDA:0,MockMailbox.l2TransactionBaseCost +DA:95,0 +BRDA:95,1,0,- +BRDA:95,1,1,- +DA:96,0 +BRDA:96,2,0,- +BRDA:96,2,1,- +DA:97,0 +BRDA:97,3,0,- +BRDA:97,3,1,- +DA:98,0 +FNF:8 +FNH:0 +LF:25 +LH:0 +BRF:7 +BRH:0 +end_of_record +TN: +SF:test/bridge/MigrationNFT.t.sol +DA:12,0 +FN:12,MigrationNFTTestUtils.bridgeTokens +FNDA:0,MigrationNFTTestUtils.bridgeTokens +DA:20,0 +DA:21,0 +DA:22,0 +FNF:1 +FNH:0 +LF:4 +LH:0 +BRF:0 +BRH:0 +end_of_record +TN: +SF:test/contentsign/BaseContentSign.t.sol +DA:13,0 +FN:13,MockContentSign.setWhitelisted +FNDA:0,MockContentSign.setWhitelisted +DA:14,0 +DA:17,0 +FN:17,MockContentSign._userIsWhitelisted +FNDA:0,MockContentSign._userIsWhitelisted +DA:18,0 +FNF:2 +FNH:0 +LF:4 +LH:0 +BRF:0 +BRH:0 +end_of_record +TN: +SF:test/contentsign/ClickBounty.t.sol +DA:13,0 +FN:13,MockERC20.constructor +FNDA:0,MockERC20.constructor +DA:14,0 +DA:21,0 +FN:21,MockContentSign._userIsWhitelisted +FNDA:0,MockContentSign._userIsWhitelisted +DA:22,0 +FNF:2 +FNH:0 +LF:4 +LH:0 +BRF:0 +BRH:0 +end_of_record +TN: +SF:test/contentsign/PaymentMiddleware.t.sol +DA:16,0 +FN:16,MockToken.mint +FNDA:0,MockToken.mint +DA:17,0 +DA:24,0 +FN:24,MockWhitelist.mint +FNDA:0,MockWhitelist.mint +DA:25,0 +DA:32,0 +FN:32,MockContentSign._userIsWhitelisted +FNDA:0,MockContentSign._userIsWhitelisted +DA:33,0 +FNF:3 +FNH:0 +LF:6 +LH:0 +BRF:0 +BRH:0 +end_of_record +TN: +SF:test/paymasters/BasePaymaster.t.sol +DA:13,0 +FN:13,MockPaymaster._validateAndPayGeneralFlow +FNDA:0,MockPaymaster._validateAndPayGeneralFlow +DA:15,0 +DA:18,0 +FN:18,MockPaymaster._validateAndPayApprovalBasedFlow +FNDA:0,MockPaymaster._validateAndPayApprovalBasedFlow +DA:23,0 +FNF:2 +FNH:0 +LF:4 +LH:0 +BRF:0 +BRH:0 +end_of_record +TN: +SF:test/paymasters/WhitelistPaymaster.t.sol +DA:14,0 +FN:14,MockWhitelistPaymaster.mock_validateAndPayGeneralFlow +FNDA:0,MockWhitelistPaymaster.mock_validateAndPayGeneralFlow +DA:15,0 +DA:18,0 +FN:18,MockWhitelistPaymaster.mock_validateAndPayApprovalBasedFlow +FNDA:0,MockWhitelistPaymaster.mock_validateAndPayApprovalBasedFlow +DA:26,0 +FNF:2 +FNH:0 +LF:4 +LH:0 +BRF:0 +BRH:0 +end_of_record diff --git a/src/swarms/SwarmRegistryL1.sol b/src/swarms/SwarmRegistryL1.sol index 05cbaf6c..4885cd66 100644 --- a/src/swarms/SwarmRegistryL1.sol +++ b/src/swarms/SwarmRegistryL1.sol @@ -73,19 +73,19 @@ contract SwarmRegistryL1 is ReentrancyGuard { event SwarmRegistered(uint256 indexed swarmId, bytes16 indexed fleetUuid, uint256 indexed providerId, address owner); event SwarmStatusChanged(uint256 indexed swarmId, SwarmStatus status); - event SwarmFilterUpdated(uint256 indexed swarmId, address indexed owner, uint32 filterSize); event SwarmProviderUpdated(uint256 indexed swarmId, uint256 indexed oldProvider, uint256 indexed newProvider); event SwarmDeleted(uint256 indexed swarmId, bytes16 indexed fleetUuid, address indexed owner); event SwarmPurged(uint256 indexed swarmId, bytes16 indexed fleetUuid, address indexed purgedBy); /// @notice Derives a deterministic swarm ID. Callable off-chain to predict IDs before registration. - /// @return swarmId keccak256(fleetUuid, providerId, filterData) - function computeSwarmId(bytes16 fleetUuid, uint256 providerId, bytes calldata filterData) + /// @dev Swarm identity is based on fleet, filter, fingerprintSize, and tagType. ProviderId is mutable and not part of identity. + /// @return swarmId keccak256(fleetUuid, filterData, fingerprintSize, tagType) + function computeSwarmId(bytes16 fleetUuid, bytes calldata filterData, uint8 fingerprintSize, TagType tagType) public pure returns (uint256) { - return uint256(keccak256(abi.encode(fleetUuid, providerId, filterData))); + return uint256(keccak256(abi.encode(fleetUuid, filterData, fingerprintSize, tagType))); } constructor(address _fleetContract, address _providerContract) { @@ -124,11 +124,12 @@ contract SwarmRegistryL1 is ReentrancyGuard { if (FLEET_CONTRACT.uuidOwner(fleetUuid) != msg.sender) { revert NotUuidOwner(); } - if (PROVIDER_CONTRACT.ownerOf(providerId) == address(0)) { + try PROVIDER_CONTRACT.ownerOf(providerId) returns (address) {} + catch { revert ProviderDoesNotExist(); } - swarmId = computeSwarmId(fleetUuid, providerId, filterData); + swarmId = computeSwarmId(fleetUuid, filterData, fingerprintSize, tagType); if (swarms[swarmId].filterPointer != address(0)) { revert SwarmAlreadyExists(); @@ -181,28 +182,6 @@ contract SwarmRegistryL1 is ReentrancyGuard { emit SwarmStatusChanged(swarmId, SwarmStatus.REJECTED); } - /// @notice Replaces the XOR filter. Resets status to REGISTERED. Caller must own the fleet UUID. - /// @param swarmId The swarm to update. - /// @param newFilterData Replacement filter blob. - function updateSwarmFilter(uint256 swarmId, bytes calldata newFilterData) external nonReentrant { - Swarm storage s = swarms[swarmId]; - if (s.filterPointer == address(0)) { - revert SwarmNotFound(); - } - if (FLEET_CONTRACT.uuidOwner(s.fleetUuid) != msg.sender) { - revert NotUuidOwner(); - } - if (newFilterData.length == 0 || newFilterData.length > 24576) { - revert InvalidFilterSize(); - } - - s.status = SwarmStatus.REGISTERED; - - s.filterPointer = SSTORE2.write(newFilterData); - - emit SwarmFilterUpdated(swarmId, msg.sender, uint32(newFilterData.length)); - } - /// @notice Reassigns the service provider. Resets status to REGISTERED. Caller must own the fleet UUID. /// @param swarmId The swarm to update. /// @param newProviderId New provider token ID. @@ -214,7 +193,8 @@ contract SwarmRegistryL1 is ReentrancyGuard { if (FLEET_CONTRACT.uuidOwner(s.fleetUuid) != msg.sender) { revert NotUuidOwner(); } - if (PROVIDER_CONTRACT.ownerOf(newProviderId) == address(0)) { + try PROVIDER_CONTRACT.ownerOf(newProviderId) returns (address) {} + catch { revert ProviderDoesNotExist(); } diff --git a/src/swarms/SwarmRegistryUniversal.sol b/src/swarms/SwarmRegistryUniversal.sol index 34f3216f..a8f80185 100644 --- a/src/swarms/SwarmRegistryUniversal.sol +++ b/src/swarms/SwarmRegistryUniversal.sol @@ -79,15 +79,19 @@ contract SwarmRegistryUniversal is ReentrancyGuard { ); event SwarmStatusChanged(uint256 indexed swarmId, SwarmStatus status); - event SwarmFilterUpdated(uint256 indexed swarmId, address indexed owner, uint32 filterSize); event SwarmProviderUpdated(uint256 indexed swarmId, uint256 indexed oldProvider, uint256 indexed newProvider); event SwarmDeleted(uint256 indexed swarmId, bytes16 indexed fleetUuid, address indexed owner); event SwarmPurged(uint256 indexed swarmId, bytes16 indexed fleetUuid, address indexed purgedBy); /// @notice Derives a deterministic swarm ID. Callable off-chain to predict IDs before registration. - /// @return swarmId keccak256(fleetUuid, providerId, filter) - function computeSwarmId(bytes16 fleetUuid, uint256 providerId, bytes calldata filter) public pure returns (uint256) { - return uint256(keccak256(abi.encode(fleetUuid, providerId, filter))); + /// @dev Swarm identity is based on fleet, filter, fingerprintSize, and tagType. ProviderId is mutable and not part of identity. + /// @return swarmId keccak256(fleetUuid, filter, fingerprintSize, tagType) + function computeSwarmId(bytes16 fleetUuid, bytes calldata filter, uint8 fingerprintSize, TagType tagType) + public + pure + returns (uint256) + { + return uint256(keccak256(abi.encode(fleetUuid, filter, fingerprintSize, tagType))); } constructor(address _fleetContract, address _providerContract) { @@ -129,11 +133,12 @@ contract SwarmRegistryUniversal is ReentrancyGuard { if (FLEET_CONTRACT.uuidOwner(fleetUuid) != msg.sender) { revert NotUuidOwner(); } - if (PROVIDER_CONTRACT.ownerOf(providerId) == address(0)) { + try PROVIDER_CONTRACT.ownerOf(providerId) returns (address) {} + catch { revert ProviderDoesNotExist(); } - swarmId = computeSwarmId(fleetUuid, providerId, filter); + swarmId = computeSwarmId(fleetUuid, filter, fingerprintSize, tagType); if (swarms[swarmId].filterLength != 0) { revert SwarmAlreadyExists(); @@ -187,31 +192,6 @@ contract SwarmRegistryUniversal is ReentrancyGuard { emit SwarmStatusChanged(swarmId, SwarmStatus.REJECTED); } - /// @notice Replaces the XOR filter. Resets status to REGISTERED. Caller must own the fleet UUID. - /// @param swarmId The swarm to update. - /// @param newFilterData Replacement filter blob. - function updateSwarmFilter(uint256 swarmId, bytes calldata newFilterData) external nonReentrant { - Swarm storage s = swarms[swarmId]; - if (s.filterLength == 0) { - revert SwarmNotFound(); - } - if (FLEET_CONTRACT.uuidOwner(s.fleetUuid) != msg.sender) { - revert NotUuidOwner(); - } - if (newFilterData.length == 0) { - revert InvalidFilterSize(); - } - if (newFilterData.length > MAX_FILTER_SIZE) { - revert FilterTooLarge(); - } - - s.filterLength = uint32(newFilterData.length); - s.status = SwarmStatus.REGISTERED; - filterData[swarmId] = newFilterData; - - emit SwarmFilterUpdated(swarmId, msg.sender, uint32(newFilterData.length)); - } - /// @notice Reassigns the service provider. Resets status to REGISTERED. Caller must own the fleet UUID. /// @param swarmId The swarm to update. /// @param newProviderId New provider token ID. @@ -223,7 +203,8 @@ contract SwarmRegistryUniversal is ReentrancyGuard { if (FLEET_CONTRACT.uuidOwner(s.fleetUuid) != msg.sender) { revert NotUuidOwner(); } - if (PROVIDER_CONTRACT.ownerOf(newProviderId) == address(0)) { + try PROVIDER_CONTRACT.ownerOf(newProviderId) returns (address) {} + catch { revert ProviderDoesNotExist(); } diff --git a/test/SwarmRegistryL1.t.sol b/test/SwarmRegistryL1.t.sol index e2ba3d20..812fe03b 100644 --- a/test/SwarmRegistryL1.t.sol +++ b/test/SwarmRegistryL1.t.sol @@ -33,7 +33,6 @@ contract SwarmRegistryL1Test is Test { event SwarmRegistered(uint256 indexed swarmId, bytes16 indexed fleetUuid, uint256 indexed providerId, address owner); event SwarmStatusChanged(uint256 indexed swarmId, SwarmRegistryL1.SwarmStatus status); - event SwarmFilterUpdated(uint256 indexed swarmId, address indexed owner, uint32 filterSize); event SwarmProviderUpdated(uint256 indexed swarmId, uint256 indexed oldProvider, uint256 indexed newProvider); event SwarmDeleted(uint256 indexed swarmId, bytes16 indexed fleetUuid, address indexed owner); event SwarmPurged(uint256 indexed swarmId, bytes16 indexed fleetUuid, address indexed purgedBy); @@ -141,8 +140,8 @@ contract SwarmRegistryL1Test is Test { fleetOwner, fleetId, providerId, new bytes(100), 16, SwarmRegistryL1.TagType.IBEACON_INCLUDES_MAC ); - // Swarm ID is deterministic hash of (fleetUuid, providerId, filter) - uint256 expectedId = swarmRegistry.computeSwarmId(_getFleetUuid(fleetId), providerId, new bytes(100)); + // Swarm ID is deterministic hash of (fleetUuid, filter, fingerprintSize, tagType) + uint256 expectedId = swarmRegistry.computeSwarmId(_getFleetUuid(fleetId), new bytes(100), 16, SwarmRegistryL1.TagType.IBEACON_INCLUDES_MAC); assertEq(swarmId, expectedId); } @@ -176,7 +175,7 @@ contract SwarmRegistryL1Test is Test { bytes memory filter = new bytes(32); - uint256 expectedId = swarmRegistry.computeSwarmId(_getFleetUuid(fleetId), providerId, filter); + uint256 expectedId = swarmRegistry.computeSwarmId(_getFleetUuid(fleetId), filter, 8, SwarmRegistryL1.TagType.GENERIC); uint256 swarmId = _registerSwarm(fleetOwner, fleetId, providerId, filter, 8, SwarmRegistryL1.TagType.GENERIC); assertEq(swarmId, expectedId); @@ -198,7 +197,7 @@ contract SwarmRegistryL1Test is Test { uint256 providerId = _registerProvider(providerOwner, "url1"); bytes memory filter = new bytes(50); - uint256 expectedId = swarmRegistry.computeSwarmId(_getFleetUuid(fleetId), providerId, filter); + uint256 expectedId = swarmRegistry.computeSwarmId(_getFleetUuid(fleetId), filter, 16, SwarmRegistryL1.TagType.GENERIC); vm.expectEmit(true, true, true, true); emit SwarmRegistered(expectedId, _getFleetUuid(fleetId), providerId, fleetOwner); @@ -211,10 +210,16 @@ contract SwarmRegistryL1Test is Test { uint256 providerId1 = _registerProvider(providerOwner, "url1"); uint256 providerId2 = _registerProvider(providerOwner, "url2"); + // Use different filters to create distinct swarms + bytes memory filter1 = new bytes(50); + filter1[0] = 0x01; + bytes memory filter2 = new bytes(50); + filter2[0] = 0x02; + uint256 swarmId1 = - _registerSwarm(fleetOwner, fleetId, providerId1, new bytes(50), 8, SwarmRegistryL1.TagType.GENERIC); + _registerSwarm(fleetOwner, fleetId, providerId1, filter1, 8, SwarmRegistryL1.TagType.GENERIC); uint256 swarmId2 = - _registerSwarm(fleetOwner, fleetId, providerId2, new bytes(50), 8, SwarmRegistryL1.TagType.GENERIC); + _registerSwarm(fleetOwner, fleetId, providerId2, filter2, 8, SwarmRegistryL1.TagType.GENERIC); assertEq(swarmRegistry.uuidSwarms(_getFleetUuid(fleetId), 0), swarmId1); assertEq(swarmRegistry.uuidSwarms(_getFleetUuid(fleetId), 1), swarmId2); @@ -273,8 +278,7 @@ contract SwarmRegistryL1Test is Test { uint256 nonExistentProvider = 12345; vm.prank(fleetOwner); - // ERC721.ownerOf reverts for non-existent tokens before our ProviderDoesNotExist check - vm.expectRevert(); + vm.expectRevert(SwarmRegistryL1.ProviderDoesNotExist.selector); swarmRegistry.registerSwarm(_getFleetUuid(fleetId), nonExistentProvider, new bytes(32), 8, SwarmRegistryL1.TagType.GENERIC); } @@ -565,89 +569,6 @@ contract SwarmRegistryL1Test is Test { swarmRegistry.registerSwarm(_getFleetUuid(fleetId), providerId, new bytes(32), fpSize, SwarmRegistryL1.TagType.GENERIC); } - // ============================== - // updateSwarmFilter - // ============================== - - function test_updateSwarmFilter_updatesFilterAndResetsStatus() public { - uint256 fleetId = _registerFleet(fleetOwner, "f1"); - uint256 providerId = _registerProvider(providerOwner, "url1"); - uint256 swarmId = - _registerSwarm(fleetOwner, fleetId, providerId, new bytes(50), 8, SwarmRegistryL1.TagType.GENERIC); - - // Provider accepts - vm.prank(providerOwner); - swarmRegistry.acceptSwarm(swarmId); - - // Fleet owner updates filter - bytes memory newFilter = new bytes(100); - vm.expectEmit(true, true, true, true); - emit SwarmFilterUpdated(swarmId, fleetOwner, 100); - - vm.prank(fleetOwner); - swarmRegistry.updateSwarmFilter(swarmId, newFilter); - - // Status should be reset to REGISTERED - (,,,,, SwarmRegistryL1.SwarmStatus status) = swarmRegistry.swarms(swarmId); - assertEq(uint8(status), uint8(SwarmRegistryL1.SwarmStatus.REGISTERED)); - } - - function test_updateSwarmFilter_changesFilterPointer() public { - uint256 fleetId = _registerFleet(fleetOwner, "f1"); - uint256 providerId = _registerProvider(providerOwner, "url1"); - uint256 swarmId = - _registerSwarm(fleetOwner, fleetId, providerId, new bytes(50), 8, SwarmRegistryL1.TagType.GENERIC); - - (,, address oldPointer,,,) = swarmRegistry.swarms(swarmId); - - bytes memory newFilter = new bytes(100); - vm.prank(fleetOwner); - swarmRegistry.updateSwarmFilter(swarmId, newFilter); - - (,, address newPointer,,,) = swarmRegistry.swarms(swarmId); - assertTrue(newPointer != oldPointer); - assertTrue(newPointer != address(0)); - } - - function test_RevertIf_updateSwarmFilter_swarmNotFound() public { - vm.prank(fleetOwner); - vm.expectRevert(SwarmRegistryL1.SwarmNotFound.selector); - swarmRegistry.updateSwarmFilter(999, new bytes(50)); - } - - function test_RevertIf_updateSwarmFilter_notFleetOwner() public { - uint256 fleetId = _registerFleet(fleetOwner, "f1"); - uint256 providerId = _registerProvider(providerOwner, "url1"); - uint256 swarmId = - _registerSwarm(fleetOwner, fleetId, providerId, new bytes(50), 8, SwarmRegistryL1.TagType.GENERIC); - - vm.prank(caller); - vm.expectRevert(SwarmRegistryL1.NotUuidOwner.selector); - swarmRegistry.updateSwarmFilter(swarmId, new bytes(100)); - } - - function test_RevertIf_updateSwarmFilter_emptyFilter() public { - uint256 fleetId = _registerFleet(fleetOwner, "f1"); - uint256 providerId = _registerProvider(providerOwner, "url1"); - uint256 swarmId = - _registerSwarm(fleetOwner, fleetId, providerId, new bytes(50), 8, SwarmRegistryL1.TagType.GENERIC); - - vm.prank(fleetOwner); - vm.expectRevert(SwarmRegistryL1.InvalidFilterSize.selector); - swarmRegistry.updateSwarmFilter(swarmId, new bytes(0)); - } - - function test_RevertIf_updateSwarmFilter_filterTooLarge() public { - uint256 fleetId = _registerFleet(fleetOwner, "f1"); - uint256 providerId = _registerProvider(providerOwner, "url1"); - uint256 swarmId = - _registerSwarm(fleetOwner, fleetId, providerId, new bytes(50), 8, SwarmRegistryL1.TagType.GENERIC); - - vm.prank(fleetOwner); - vm.expectRevert(SwarmRegistryL1.InvalidFilterSize.selector); - swarmRegistry.updateSwarmFilter(swarmId, new bytes(24577)); - } - // ============================== // updateSwarmProvider // ============================== @@ -706,8 +627,7 @@ contract SwarmRegistryL1Test is Test { _registerSwarm(fleetOwner, fleetId, providerId, new bytes(50), 8, SwarmRegistryL1.TagType.GENERIC); vm.prank(fleetOwner); - // ERC721 reverts before our custom error is reached - vm.expectRevert(); + vm.expectRevert(SwarmRegistryL1.ProviderDoesNotExist.selector); swarmRegistry.updateSwarmProvider(swarmId, 99999); } @@ -737,10 +657,16 @@ contract SwarmRegistryL1Test is Test { uint256 providerId1 = _registerProvider(providerOwner, "url1"); uint256 providerId2 = _registerProvider(providerOwner, "url2"); + // Use different filters to create distinct swarms + bytes memory filter1 = new bytes(50); + filter1[0] = 0x01; + bytes memory filter2 = new bytes(50); + filter2[0] = 0x02; + uint256 swarm1 = - _registerSwarm(fleetOwner, fleetId, providerId1, new bytes(50), 8, SwarmRegistryL1.TagType.GENERIC); + _registerSwarm(fleetOwner, fleetId, providerId1, filter1, 8, SwarmRegistryL1.TagType.GENERIC); uint256 swarm2 = - _registerSwarm(fleetOwner, fleetId, providerId2, new bytes(50), 8, SwarmRegistryL1.TagType.GENERIC); + _registerSwarm(fleetOwner, fleetId, providerId2, filter2, 8, SwarmRegistryL1.TagType.GENERIC); // Delete first swarm vm.prank(fleetOwner); @@ -758,12 +684,20 @@ contract SwarmRegistryL1Test is Test { uint256 providerId2 = _registerProvider(providerOwner, "url2"); uint256 providerId3 = _registerProvider(providerOwner, "url3"); + // Use different filters to create distinct swarms + bytes memory filter1 = new bytes(50); + filter1[0] = 0x01; + bytes memory filter2 = new bytes(50); + filter2[0] = 0x02; + bytes memory filter3 = new bytes(50); + filter3[0] = 0x03; + uint256 swarm1 = - _registerSwarm(fleetOwner, fleetId, providerId1, new bytes(50), 8, SwarmRegistryL1.TagType.GENERIC); + _registerSwarm(fleetOwner, fleetId, providerId1, filter1, 8, SwarmRegistryL1.TagType.GENERIC); uint256 swarm2 = - _registerSwarm(fleetOwner, fleetId, providerId2, new bytes(50), 8, SwarmRegistryL1.TagType.GENERIC); + _registerSwarm(fleetOwner, fleetId, providerId2, filter2, 8, SwarmRegistryL1.TagType.GENERIC); uint256 swarm3 = - _registerSwarm(fleetOwner, fleetId, providerId3, new bytes(50), 8, SwarmRegistryL1.TagType.GENERIC); + _registerSwarm(fleetOwner, fleetId, providerId3, filter3, 8, SwarmRegistryL1.TagType.GENERIC); // Delete middle swarm vm.prank(fleetOwner); @@ -793,15 +727,16 @@ contract SwarmRegistryL1Test is Test { swarmRegistry.deleteSwarm(swarmId); } - function test_deleteSwarm_afterUpdate() public { + function test_deleteSwarm_afterProviderUpdate() public { uint256 fleetId = _registerFleet(fleetOwner, "f1"); - uint256 providerId = _registerProvider(providerOwner, "url1"); + uint256 providerId1 = _registerProvider(providerOwner, "url1"); + uint256 providerId2 = _registerProvider(providerOwner, "url2"); uint256 swarmId = - _registerSwarm(fleetOwner, fleetId, providerId, new bytes(50), 8, SwarmRegistryL1.TagType.GENERIC); + _registerSwarm(fleetOwner, fleetId, providerId1, new bytes(50), 8, SwarmRegistryL1.TagType.GENERIC); - // Update then delete + // Update provider then delete vm.prank(fleetOwner); - swarmRegistry.updateSwarmFilter(swarmId, new bytes(100)); + swarmRegistry.updateSwarmProvider(swarmId, providerId2); vm.prank(fleetOwner); swarmRegistry.deleteSwarm(swarmId); @@ -816,9 +751,17 @@ contract SwarmRegistryL1Test is Test { uint256 p2 = _registerProvider(providerOwner, "url2"); uint256 p3 = _registerProvider(providerOwner, "url3"); - uint256 s1 = _registerSwarm(fleetOwner, fleetId, p1, new bytes(50), 8, SwarmRegistryL1.TagType.GENERIC); - uint256 s2 = _registerSwarm(fleetOwner, fleetId, p2, new bytes(50), 8, SwarmRegistryL1.TagType.GENERIC); - uint256 s3 = _registerSwarm(fleetOwner, fleetId, p3, new bytes(50), 8, SwarmRegistryL1.TagType.GENERIC); + // Use different filters to create distinct swarms + bytes memory filter1 = new bytes(50); + filter1[0] = 0x01; + bytes memory filter2 = new bytes(50); + filter2[0] = 0x02; + bytes memory filter3 = new bytes(50); + filter3[0] = 0x03; + + uint256 s1 = _registerSwarm(fleetOwner, fleetId, p1, filter1, 8, SwarmRegistryL1.TagType.GENERIC); + uint256 s2 = _registerSwarm(fleetOwner, fleetId, p2, filter2, 8, SwarmRegistryL1.TagType.GENERIC); + uint256 s3 = _registerSwarm(fleetOwner, fleetId, p3, filter3, 8, SwarmRegistryL1.TagType.GENERIC); // Verify initial indices assertEq(swarmRegistry.swarmIndexInUuid(s1), 0); @@ -970,8 +913,14 @@ contract SwarmRegistryL1Test is Test { uint256 p1 = _registerProvider(providerOwner, "url1"); uint256 p2 = _registerProvider(providerOwner, "url2"); - uint256 s1 = _registerSwarm(fleetOwner, fleetId, p1, new bytes(50), 8, SwarmRegistryL1.TagType.GENERIC); - uint256 s2 = _registerSwarm(fleetOwner, fleetId, p2, new bytes(50), 8, SwarmRegistryL1.TagType.GENERIC); + // Use different filters to create distinct swarms + bytes memory filter1 = new bytes(50); + filter1[0] = 0x01; + bytes memory filter2 = new bytes(50); + filter2[0] = 0x02; + + uint256 s1 = _registerSwarm(fleetOwner, fleetId, p1, filter1, 8, SwarmRegistryL1.TagType.GENERIC); + uint256 s2 = _registerSwarm(fleetOwner, fleetId, p2, filter2, 8, SwarmRegistryL1.TagType.GENERIC); // Burn provider of s1 vm.prank(providerOwner); diff --git a/test/SwarmRegistryUniversal.t.sol b/test/SwarmRegistryUniversal.t.sol index 860fd536..558f3798 100644 --- a/test/SwarmRegistryUniversal.t.sol +++ b/test/SwarmRegistryUniversal.t.sol @@ -35,7 +35,6 @@ contract SwarmRegistryUniversalTest is Test { uint256 indexed swarmId, bytes16 indexed fleetUuid, uint256 indexed providerId, address owner, uint32 filterSize ); event SwarmStatusChanged(uint256 indexed swarmId, SwarmRegistryUniversal.SwarmStatus status); - event SwarmFilterUpdated(uint256 indexed swarmId, address indexed owner, uint32 newFilterSize); event SwarmProviderUpdated(uint256 indexed swarmId, uint256 indexed oldProviderId, uint256 indexed newProviderId); event SwarmDeleted(uint256 indexed swarmId, bytes16 indexed fleetUuid, address indexed owner); event SwarmPurged(uint256 indexed swarmId, bytes16 indexed fleetUuid, address indexed purgedBy); @@ -142,8 +141,8 @@ contract SwarmRegistryUniversalTest is Test { fleetOwner, fleetId, providerId, new bytes(100), 16, SwarmRegistryUniversal.TagType.IBEACON_INCLUDES_MAC ); - // Swarm ID is deterministic hash of (fleetUuid, providerId, filter) - uint256 expectedId = swarmRegistry.computeSwarmId(_getFleetUuid(fleetId), providerId, new bytes(100)); + // Swarm ID is deterministic hash of (fleetUuid, filter, fingerprintSize, tagType) + uint256 expectedId = swarmRegistry.computeSwarmId(_getFleetUuid(fleetId), new bytes(100), 16, SwarmRegistryUniversal.TagType.IBEACON_INCLUDES_MAC); assertEq(swarmId, expectedId); } @@ -197,7 +196,7 @@ contract SwarmRegistryUniversalTest is Test { bytes memory filter = new bytes(32); - uint256 expectedId = swarmRegistry.computeSwarmId(_getFleetUuid(fleetId), providerId, filter); + uint256 expectedId = swarmRegistry.computeSwarmId(_getFleetUuid(fleetId), filter, 8, SwarmRegistryUniversal.TagType.GENERIC); uint256 swarmId = _registerSwarm(fleetOwner, fleetId, providerId, filter, 8, SwarmRegistryUniversal.TagType.GENERIC); @@ -220,7 +219,7 @@ contract SwarmRegistryUniversalTest is Test { uint256 providerId = _registerProvider(providerOwner, "url1"); bytes memory filter = new bytes(50); - uint256 expectedId = swarmRegistry.computeSwarmId(_getFleetUuid(fleetId), providerId, filter); + uint256 expectedId = swarmRegistry.computeSwarmId(_getFleetUuid(fleetId), filter, 16, SwarmRegistryUniversal.TagType.GENERIC); vm.expectEmit(true, true, true, true); emit SwarmRegistered(expectedId, _getFleetUuid(fleetId), providerId, fleetOwner, 50); @@ -233,10 +232,16 @@ contract SwarmRegistryUniversalTest is Test { uint256 providerId1 = _registerProvider(providerOwner, "url1"); uint256 providerId2 = _registerProvider(providerOwner, "url2"); + // Use different filters to create distinct swarms + bytes memory filter1 = new bytes(50); + filter1[0] = 0x01; + bytes memory filter2 = new bytes(50); + filter2[0] = 0x02; + uint256 s1 = - _registerSwarm(fleetOwner, fleetId, providerId1, new bytes(50), 8, SwarmRegistryUniversal.TagType.GENERIC); + _registerSwarm(fleetOwner, fleetId, providerId1, filter1, 8, SwarmRegistryUniversal.TagType.GENERIC); uint256 s2 = - _registerSwarm(fleetOwner, fleetId, providerId2, new bytes(50), 8, SwarmRegistryUniversal.TagType.GENERIC); + _registerSwarm(fleetOwner, fleetId, providerId2, filter2, 8, SwarmRegistryUniversal.TagType.GENERIC); assertEq(swarmRegistry.uuidSwarms(_getFleetUuid(fleetId), 0), s1); assertEq(swarmRegistry.uuidSwarms(_getFleetUuid(fleetId), 1), s2); @@ -288,8 +293,7 @@ contract SwarmRegistryUniversalTest is Test { uint256 nonExistentProvider = 12345; vm.prank(fleetOwner); - // ERC721.ownerOf reverts for non-existent tokens before our ProviderDoesNotExist check - vm.expectRevert(); + vm.expectRevert(SwarmRegistryUniversal.ProviderDoesNotExist.selector); swarmRegistry.registerSwarm(_getFleetUuid(fleetId), nonExistentProvider, new bytes(32), 8, SwarmRegistryUniversal.TagType.GENERIC); } @@ -543,6 +547,18 @@ contract SwarmRegistryUniversalTest is Test { swarmRegistry.checkMembership(swarmId, keccak256("test2")); } + function test_checkMembership_tinyFilter_returnsFalse() public { + uint256 fleetId = _registerFleet(fleetOwner, "f1"); + uint256 providerId = _registerProvider(providerOwner, "u1"); + + // 1-byte filter with 16-bit fingerprint: m = (1*8)/16 = 0, returns false immediately + bytes memory filter = new bytes(1); + uint256 swarmId = _registerSwarm(fleetOwner, fleetId, providerId, filter, 16, SwarmRegistryUniversal.TagType.GENERIC); + + // Should return false (not revert) because m == 0 + assertFalse(swarmRegistry.checkMembership(swarmId, keccak256("test")), "m=0 should return false"); + } + // ============================== // getFilterData // ============================== @@ -647,94 +663,6 @@ contract SwarmRegistryUniversalTest is Test { assertEq(storedLen, uint32(size)); } - // ============================== - // updateSwarmFilter - // ============================== - - function test_updateSwarmFilter_updatesFilterAndResetsStatus() public { - uint256 fleetId = _registerFleet(fleetOwner, "f1"); - uint256 providerId = _registerProvider(providerOwner, "url1"); - uint256 swarmId = - _registerSwarm(fleetOwner, fleetId, providerId, new bytes(50), 8, SwarmRegistryUniversal.TagType.GENERIC); - - // Provider accepts - vm.prank(providerOwner); - swarmRegistry.acceptSwarm(swarmId); - - // Fleet owner updates filter - bytes memory newFilter = new bytes(100); - for (uint256 i = 0; i < 100; i++) { - newFilter[i] = bytes1(uint8(i % 256)); - } - - vm.expectEmit(true, true, true, true); - emit SwarmFilterUpdated(swarmId, fleetOwner, 100); - - vm.prank(fleetOwner); - swarmRegistry.updateSwarmFilter(swarmId, newFilter); - - // Status should be reset to REGISTERED - (,, uint32 filterLength,,, SwarmRegistryUniversal.SwarmStatus status) = swarmRegistry.swarms(swarmId); - assertEq(uint8(status), uint8(SwarmRegistryUniversal.SwarmStatus.REGISTERED)); - assertEq(filterLength, 100); - } - - function test_updateSwarmFilter_changesFilterLength() public { - uint256 fleetId = _registerFleet(fleetOwner, "f1"); - uint256 providerId = _registerProvider(providerOwner, "url1"); - uint256 swarmId = - _registerSwarm(fleetOwner, fleetId, providerId, new bytes(50), 8, SwarmRegistryUniversal.TagType.GENERIC); - - (,, uint32 oldLen,,,) = swarmRegistry.swarms(swarmId); - assertEq(oldLen, 50); - - bytes memory newFilter = new bytes(100); - vm.prank(fleetOwner); - swarmRegistry.updateSwarmFilter(swarmId, newFilter); - - (,, uint32 newLen,,,) = swarmRegistry.swarms(swarmId); - assertEq(newLen, 100); - } - - function test_RevertIf_updateSwarmFilter_swarmNotFound() public { - vm.prank(fleetOwner); - vm.expectRevert(SwarmRegistryUniversal.SwarmNotFound.selector); - swarmRegistry.updateSwarmFilter(999, new bytes(50)); - } - - function test_RevertIf_updateSwarmFilter_notFleetOwner() public { - uint256 fleetId = _registerFleet(fleetOwner, "f1"); - uint256 providerId = _registerProvider(providerOwner, "url1"); - uint256 swarmId = - _registerSwarm(fleetOwner, fleetId, providerId, new bytes(50), 8, SwarmRegistryUniversal.TagType.GENERIC); - - vm.prank(caller); - vm.expectRevert(SwarmRegistryUniversal.NotUuidOwner.selector); - swarmRegistry.updateSwarmFilter(swarmId, new bytes(100)); - } - - function test_RevertIf_updateSwarmFilter_emptyFilter() public { - uint256 fleetId = _registerFleet(fleetOwner, "f1"); - uint256 providerId = _registerProvider(providerOwner, "url1"); - uint256 swarmId = - _registerSwarm(fleetOwner, fleetId, providerId, new bytes(50), 8, SwarmRegistryUniversal.TagType.GENERIC); - - vm.prank(fleetOwner); - vm.expectRevert(SwarmRegistryUniversal.InvalidFilterSize.selector); - swarmRegistry.updateSwarmFilter(swarmId, new bytes(0)); - } - - function test_RevertIf_updateSwarmFilter_filterTooLarge() public { - uint256 fleetId = _registerFleet(fleetOwner, "f1"); - uint256 providerId = _registerProvider(providerOwner, "url1"); - uint256 swarmId = - _registerSwarm(fleetOwner, fleetId, providerId, new bytes(50), 8, SwarmRegistryUniversal.TagType.GENERIC); - - vm.prank(fleetOwner); - vm.expectRevert(SwarmRegistryUniversal.FilterTooLarge.selector); - swarmRegistry.updateSwarmFilter(swarmId, new bytes(24577)); - } - // ============================== // updateSwarmProvider // ============================== @@ -793,8 +721,7 @@ contract SwarmRegistryUniversalTest is Test { _registerSwarm(fleetOwner, fleetId, providerId, new bytes(50), 8, SwarmRegistryUniversal.TagType.GENERIC); vm.prank(fleetOwner); - // ERC721 reverts before our custom error is reached - vm.expectRevert(); + vm.expectRevert(SwarmRegistryUniversal.ProviderDoesNotExist.selector); swarmRegistry.updateSwarmProvider(swarmId, 99999); } @@ -825,10 +752,16 @@ contract SwarmRegistryUniversalTest is Test { uint256 providerId1 = _registerProvider(providerOwner, "url1"); uint256 providerId2 = _registerProvider(providerOwner, "url2"); + // Use different filters to create distinct swarms + bytes memory filter1 = new bytes(50); + filter1[0] = 0x01; + bytes memory filter2 = new bytes(50); + filter2[0] = 0x02; + uint256 swarm1 = - _registerSwarm(fleetOwner, fleetId, providerId1, new bytes(50), 8, SwarmRegistryUniversal.TagType.GENERIC); + _registerSwarm(fleetOwner, fleetId, providerId1, filter1, 8, SwarmRegistryUniversal.TagType.GENERIC); uint256 swarm2 = - _registerSwarm(fleetOwner, fleetId, providerId2, new bytes(50), 8, SwarmRegistryUniversal.TagType.GENERIC); + _registerSwarm(fleetOwner, fleetId, providerId2, filter2, 8, SwarmRegistryUniversal.TagType.GENERIC); // Delete first swarm vm.prank(fleetOwner); @@ -846,12 +779,20 @@ contract SwarmRegistryUniversalTest is Test { uint256 providerId2 = _registerProvider(providerOwner, "url2"); uint256 providerId3 = _registerProvider(providerOwner, "url3"); + // Use different filters to create distinct swarms + bytes memory filter1 = new bytes(50); + filter1[0] = 0x01; + bytes memory filter2 = new bytes(50); + filter2[0] = 0x02; + bytes memory filter3 = new bytes(50); + filter3[0] = 0x03; + uint256 swarm1 = - _registerSwarm(fleetOwner, fleetId, providerId1, new bytes(50), 8, SwarmRegistryUniversal.TagType.GENERIC); + _registerSwarm(fleetOwner, fleetId, providerId1, filter1, 8, SwarmRegistryUniversal.TagType.GENERIC); uint256 swarm2 = - _registerSwarm(fleetOwner, fleetId, providerId2, new bytes(50), 8, SwarmRegistryUniversal.TagType.GENERIC); + _registerSwarm(fleetOwner, fleetId, providerId2, filter2, 8, SwarmRegistryUniversal.TagType.GENERIC); uint256 swarm3 = - _registerSwarm(fleetOwner, fleetId, providerId3, new bytes(50), 8, SwarmRegistryUniversal.TagType.GENERIC); + _registerSwarm(fleetOwner, fleetId, providerId3, filter3, 8, SwarmRegistryUniversal.TagType.GENERIC); // Delete middle swarm vm.prank(fleetOwner); @@ -902,15 +843,16 @@ contract SwarmRegistryUniversalTest is Test { swarmRegistry.deleteSwarm(swarmId); } - function test_deleteSwarm_afterUpdate() public { + function test_deleteSwarm_afterProviderUpdate() public { uint256 fleetId = _registerFleet(fleetOwner, "f1"); - uint256 providerId = _registerProvider(providerOwner, "url1"); + uint256 providerId1 = _registerProvider(providerOwner, "url1"); + uint256 providerId2 = _registerProvider(providerOwner, "url2"); uint256 swarmId = - _registerSwarm(fleetOwner, fleetId, providerId, new bytes(50), 8, SwarmRegistryUniversal.TagType.GENERIC); + _registerSwarm(fleetOwner, fleetId, providerId1, new bytes(50), 8, SwarmRegistryUniversal.TagType.GENERIC); - // Update then delete + // Update provider then delete vm.prank(fleetOwner); - swarmRegistry.updateSwarmFilter(swarmId, new bytes(100)); + swarmRegistry.updateSwarmProvider(swarmId, providerId2); vm.prank(fleetOwner); swarmRegistry.deleteSwarm(swarmId); @@ -925,9 +867,17 @@ contract SwarmRegistryUniversalTest is Test { uint256 p2 = _registerProvider(providerOwner, "url2"); uint256 p3 = _registerProvider(providerOwner, "url3"); - uint256 s1 = _registerSwarm(fleetOwner, fleetId, p1, new bytes(50), 8, SwarmRegistryUniversal.TagType.GENERIC); - uint256 s2 = _registerSwarm(fleetOwner, fleetId, p2, new bytes(50), 8, SwarmRegistryUniversal.TagType.GENERIC); - uint256 s3 = _registerSwarm(fleetOwner, fleetId, p3, new bytes(50), 8, SwarmRegistryUniversal.TagType.GENERIC); + // Use different filters to create distinct swarms + bytes memory filter1 = new bytes(50); + filter1[0] = 0x01; + bytes memory filter2 = new bytes(50); + filter2[0] = 0x02; + bytes memory filter3 = new bytes(50); + filter3[0] = 0x03; + + uint256 s1 = _registerSwarm(fleetOwner, fleetId, p1, filter1, 8, SwarmRegistryUniversal.TagType.GENERIC); + uint256 s2 = _registerSwarm(fleetOwner, fleetId, p2, filter2, 8, SwarmRegistryUniversal.TagType.GENERIC); + uint256 s3 = _registerSwarm(fleetOwner, fleetId, p3, filter3, 8, SwarmRegistryUniversal.TagType.GENERIC); // Verify initial indices assertEq(swarmRegistry.swarmIndexInUuid(s1), 0); @@ -1076,8 +1026,14 @@ contract SwarmRegistryUniversalTest is Test { uint256 p1 = _registerProvider(providerOwner, "url1"); uint256 p2 = _registerProvider(providerOwner, "url2"); - uint256 s1 = _registerSwarm(fleetOwner, fleetId, p1, new bytes(50), 8, SwarmRegistryUniversal.TagType.GENERIC); - uint256 s2 = _registerSwarm(fleetOwner, fleetId, p2, new bytes(50), 8, SwarmRegistryUniversal.TagType.GENERIC); + // Use different filters to create distinct swarms + bytes memory filter1 = new bytes(50); + filter1[0] = 0x01; + bytes memory filter2 = new bytes(50); + filter2[0] = 0x02; + + uint256 s1 = _registerSwarm(fleetOwner, fleetId, p1, filter1, 8, SwarmRegistryUniversal.TagType.GENERIC); + uint256 s2 = _registerSwarm(fleetOwner, fleetId, p2, filter2, 8, SwarmRegistryUniversal.TagType.GENERIC); // Burn provider of s1 vm.prank(providerOwner); From 2c662a71dadc01c77b7b4a8bb3c4f2f9d667c7e6 Mon Sep 17 00:00:00 2001 From: Alex Sedighi Date: Tue, 3 Mar 2026 12:14:19 +1300 Subject: [PATCH 05/15] docs(swarms): update documentation to match actual code logic - Fix Swarm ID derivation: uses keccak256(fleetUuid, filter, fpSize, tagType), not keccak256(fleetUuid, providerId, filter). ProviderId is mutable. - Remove non-existent updateSwarmFilter function (filter is immutable as part of identity) - Remove non-existent unregisterToOwned() and releaseUuid() - everything uses burn() - Remove non-existent registerFleetLocalWithOperator - use claimUuid(uuid, operator) instead - Fix TIER_CAPACITY: 10 members per tier (was incorrectly 4) - Fix operator permissions: can set operator for owned UUIDs - Fix burn flow: registered tokens burned by operator, owned-only by owner - Fix tier management: only operator can promote/demote registered tokens - Update struct field order in code examples to match actual contracts --- src/swarms/doc/README.md | 12 ++++---- src/swarms/doc/assistant-guide.md | 42 +++++++++++++++----------- src/swarms/doc/data-model.md | 7 ++--- src/swarms/doc/discovery.md | 3 +- src/swarms/doc/fleet-registration.md | 26 +++++++++------- src/swarms/doc/lifecycle.md | 45 +++++++++++++++------------- src/swarms/doc/maintenance.md | 21 ++++++------- src/swarms/doc/swarm-operations.md | 15 +++++----- 8 files changed, 92 insertions(+), 79 deletions(-) diff --git a/src/swarms/doc/README.md b/src/swarms/doc/README.md index 1b56c3dc..ed40a8a0 100644 --- a/src/swarms/doc/README.md +++ b/src/swarms/doc/README.md @@ -40,12 +40,12 @@ graph TB ## Core Components -| Contract | Role | Identity | Token | -| :------------------------- | :----------------------------- | :----------------------------------------- | :---- | -| **FleetIdentity** | Fleet registry (ERC-721) | `(regionKey << 128) \| uuid` | SFID | -| **ServiceProvider** | Backend URL registry (ERC-721) | `keccak256(url)` | SSV | -| **SwarmRegistryL1** | Tag group registry (L1) | `keccak256(fleetUuid, providerId, filter)` | — | -| **SwarmRegistryUniversal** | Tag group registry (ZkSync+) | `keccak256(fleetUuid, providerId, filter)` | — | +| Contract | Role | Identity | Token | +| :------------------------- | :----------------------------- | :------------------------------------------------- | :---- | +| **FleetIdentity** | Fleet registry (ERC-721) | `(regionKey << 128) \| uuid` | SFID | +| **ServiceProvider** | Backend URL registry (ERC-721) | `keccak256(url)` | SSV | +| **SwarmRegistryL1** | Tag group registry (L1) | `keccak256(fleetUuid, filter, fpSize, tagType)` | — | +| **SwarmRegistryUniversal** | Tag group registry (ZkSync+) | `keccak256(fleetUuid, filter, fpSize, tagType)` | — | All contracts are **permissionless**—access control via NFT ownership. FleetIdentity requires ERC-20 bond (anti-spam). diff --git a/src/swarms/doc/assistant-guide.md b/src/swarms/doc/assistant-guide.md index 070602d6..4699a618 100644 --- a/src/swarms/doc/assistant-guide.md +++ b/src/swarms/doc/assistant-guide.md @@ -48,13 +48,13 @@ This allows the same UUID to be registered in multiple regions, each with a dist | Parameter | Value | | :------------------ | :--------------------------------------------------------- | -| **Tier Capacity** | 4 members per tier | +| **Tier Capacity** | 10 members per tier | | **Max Tiers** | 24 per region | | **Local Bond** | `BASE_BOND * 2^tier` | | **Country Bond** | `BASE_BOND * COUNTRY_BOND_MULTIPLIER * 2^tier` (16× local) | | **Max Bundle Size** | 20 UUIDs | -Country fleets pay 16× more but appear in all admin-area bundles within their country. This economic difference provides locals a significant advantage: a local can reach tier 3 for the same cost a country player pays for tier 0. +Country fleets pay 16× more but appear in all admin-area bundles within their country. This economic difference provides locals a significant advantage: a local can reach tier 4 for the same cost a country player pays for tier 0. ### UUID Ownership Model @@ -114,13 +114,15 @@ uint256 tokenId = fleetIdentity.registerFleetLocal(uuid, 840, 5, tier); #### B3. Claim-First Flow (Reserve UUID, Register Later) ```solidity -// 1. Claim UUID ownership (costs BASE_BOND) +// 1. Claim UUID ownership (costs BASE_BOND), optionally designate operator NODL.approve(fleetIdentityAddress, BASE_BOND); -uint256 ownedTokenId = fleetIdentity.claimUuid(uuid); +uint256 ownedTokenId = fleetIdentity.claimUuid(uuid, operatorAddress); // Returns tokenId = uint128(uuid) (regionKey = 0) +// If operatorAddress is address(0), caller becomes the operator -// 2. Later: Register from owned state (burns owned token, mints regional token) -// Only pays incremental bond (tier bond - BASE_BOND already paid) +// 2. Later: Operator registers from owned state (burns owned token, mints regional token to owner) +// Operator pays tier bond only (BASE_BOND already paid by owner via claimUuid) +NODL.approve(fleetIdentityAddress, tierBond); // as operator uint256 tokenId = fleetIdentity.registerFleetLocal(uuid, 840, 5, targetTier); ``` @@ -143,25 +145,28 @@ fleetIdentity.reassignTier(tokenId, targetTier); UUID owners can delegate tier management to an operator wallet: ```solidity -// Set operator at registration time (owner pays BASE_BOND, operator pays tier excess) -fleetIdentity.registerFleetLocalWithOperator(uuid, 840, 5, tier, operatorAddress); +// Set operator at claim time (owner pays BASE_BOND, operator manages tiers later) +uint256 ownedTokenId = fleetIdentity.claimUuid(uuid, operatorAddress); -// Or set operator after registration (transfers tier bonds atomically) +// Or set/change operator after registration (transfers tier bonds atomically) fleetIdentity.setOperator(uuid, operatorAddress); +// Pulls total tier bonds from new operator, refunds old operator // Check current operator (returns owner if none set) address manager = fleetIdentity.operatorOf(uuid); // Clear operator (reverts to owner-managed) fleetIdentity.setOperator(uuid, address(0)); +// Pulls tier bonds from owner, refunds old operator ``` **Key Points:** -- Operator handles `promote()`, `reassignTier()`, and `burn()` calls for registered tokens -- Owner retains `setOperator()` control and `burn()` rights for owned-only tokens -- Tier excess bonds transfer between operators when changing -- Cannot set operator for owned-only UUIDs (must be registered) +- Operator handles `promote()`, `reassignTier()`, and registration calls for owned UUIDs +- Operator can burn registered tokens (refunds tier bond to operator) +- Owner retains `setOperator()` control and can burn owned-only tokens +- `setOperator` transfers all tier bonds atomically (O(1) via `uuidTotalTierBonds`) +- Can set operator for both owned-only and registered UUIDs ### E. Burn Fleet Token @@ -213,11 +218,12 @@ Only the owner of the provider NFT (`providerId`) can accept or reject. ### H. Swarm Updates -The fleet owner can modify a swarm at any time. Both operations reset status to `REGISTERED`, requiring fresh provider approval: +The fleet owner can change the service provider. This resets status to `REGISTERED`, requiring fresh provider approval: -- **Replace the XOR filter**: `swarmRegistry.updateSwarmFilter(swarmId, newFilterData)` - **Change service provider**: `swarmRegistry.updateSwarmProvider(swarmId, newProviderId)` +**Note:** The XOR filter is immutable and part of swarm identity. To change the filter, delete the swarm and create a new one. + ### I. Swarm Deletion The fleet owner can permanently remove a swarm: @@ -277,10 +283,10 @@ To verify membership on-chain, the contract uses **3-hash XOR logic**. Swarm IDs are **deterministic** — derived from the swarm's core identity: ``` -swarmId = uint256(keccak256(abi.encode(fleetUuid, providerId, filterData))) +swarmId = uint256(keccak256(abi.encode(fleetUuid, filterData, fingerprintSize, tagType))) ``` -This means the same (UUID, provider, filter) triple always produces the same ID, and duplicate registrations revert with `SwarmAlreadyExists()`. The `computeSwarmId` function is `public pure`, so it can be called off-chain at zero cost via `eth_call`. +Swarm identity is based on fleet, filter, fingerprintSize, and tagType. ProviderId is mutable and not part of identity. The same (UUID, filter, fpSize, tagType) tuple always produces the same ID, and duplicate registrations revert with `SwarmAlreadyExists()`. The `computeSwarmId` function is `public pure`, so it can be called off-chain at zero cost via `eth_call`. --- @@ -375,7 +381,7 @@ for (uint256 i = 0; ; i++) { ```solidity // Construct tagHash based on swarm's tagType (bytes16 fleetUuid, uint256 providerId, uint32 filterLen, uint8 fpSize, - SwarmStatus status, TagType tagType) = swarmRegistry.swarms(swarmId); + TagType tagType, SwarmStatus status) = swarmRegistry.swarms(swarmId); // Build tagId per schema (see Section 3) bytes memory tagId; diff --git a/src/swarms/doc/data-model.md b/src/swarms/doc/data-model.md index 5fd583b8..2e448af2 100644 --- a/src/swarms/doc/data-model.md +++ b/src/swarms/doc/data-model.md @@ -52,11 +52,10 @@ classDiagram +mapping uuidSwarms : bytes16 → uint256[] +mapping swarmIndexInUuid : uint256 → uint256 -- - +computeSwarmId(fleetUuid, providerId, filter) → swarmId + +computeSwarmId(fleetUuid, filter, fpSize, tagType) → swarmId +registerSwarm(fleetUuid, providerId, filter, fpSize, tagType) → swarmId +acceptSwarm(swarmId) +rejectSwarm(swarmId) - +updateSwarmFilter(swarmId, newFilter) +updateSwarmProvider(swarmId, newProviderId) +deleteSwarm(swarmId) +isSwarmValid(swarmId) → fleetValid, providerValid @@ -133,10 +132,10 @@ uint32 adminRegion = fleetIdentity.makeAdminRegion(countryCode, adminCode); Deterministic and collision-free: ```solidity -swarmId = uint256(keccak256(abi.encode(fleetUuid, providerId, filterData))) +swarmId = uint256(keccak256(abi.encode(fleetUuid, filterData, fingerprintSize, tagType))) ``` -Duplicate registration reverts with `SwarmAlreadyExists()`. +Swarm identity is based on fleet, filter, fingerprintSize, and tagType. ProviderId is mutable and not part of identity. Duplicate registration reverts with `SwarmAlreadyExists()`. ## XOR Filter Membership diff --git a/src/swarms/doc/discovery.md b/src/swarms/doc/discovery.md index a71d0ef4..95e5e8a3 100644 --- a/src/swarms/doc/discovery.md +++ b/src/swarms/doc/discovery.md @@ -144,7 +144,8 @@ function discoverService( } catch { break; } // 3. Get swarm data - (,uint256 providerId,,,SwarmStatus status, TagType tagType) = + (bytes16 storedUuid, uint256 providerId, uint32 filterLen, uint8 fpSize, + SwarmRegistryUniversal.TagType tagType, SwarmRegistryUniversal.SwarmStatus status) = swarmRegistry.swarms(swarmId); if (status != SwarmStatus.ACCEPTED) continue; diff --git a/src/swarms/doc/fleet-registration.md b/src/swarms/doc/fleet-registration.md index b6536883..dcb82e7a 100644 --- a/src/swarms/doc/fleet-registration.md +++ b/src/swarms/doc/fleet-registration.md @@ -159,36 +159,40 @@ Country fleets pay 16× but appear in **all** admin-area bundles. Locals have co ### Promote +Only the operator (or owner if no operator set) can promote: + ```solidity -// Approve additional bond +// Approve additional bond (as operator) fleetIdentity.promote(tokenId); // Moves to currentTier + 1 ``` ### Reassign +Only the operator (or owner if no operator set) can reassign: + ```solidity // Move to any tier fleetIdentity.reassignTier(tokenId, targetTier); -// Promotion: pulls difference -// Demotion: refunds difference +// Promotion: pulls difference from operator +// Demotion: refunds difference to operator ``` ```mermaid sequenceDiagram - actor FO as Fleet Owner + actor OP as Operator participant FI as FleetIdentity participant TOKEN as BOND_TOKEN alt Promote - FO->>TOKEN: approve(additionalBond) - FO->>+FI: reassignTier(tokenId, higherTier) - FI->>TOKEN: transferFrom(owner, this, diff) - FI-->>-FO: FleetPromoted + OP->>TOKEN: approve(additionalBond) + OP->>+FI: reassignTier(tokenId, higherTier) + FI->>TOKEN: transferFrom(operator, this, diff) + FI-->>-OP: FleetPromoted else Demote - FO->>+FI: reassignTier(tokenId, lowerTier) - FI->>TOKEN: transfer(owner, refund) - FI-->>-FO: FleetDemoted + OP->>+FI: reassignTier(tokenId, lowerTier) + FI->>TOKEN: transfer(operator, refund) + FI-->>-OP: FleetDemoted end ``` diff --git a/src/swarms/doc/lifecycle.md b/src/swarms/doc/lifecycle.md index 6772934d..e7cc2a53 100644 --- a/src/swarms/doc/lifecycle.md +++ b/src/swarms/doc/lifecycle.md @@ -10,15 +10,17 @@ stateDiagram-v2 None --> Local : registerFleetLocal() None --> Country : registerFleetCountry() - Owned --> Local : registerFleetLocal() - Owned --> Country : registerFleetCountry() - Owned --> [*] : releaseUuid() / burn() + Owned --> Local : registerFleetLocal() [operator] + Owned --> Country : registerFleetCountry() [operator] + Owned --> [*] : burn() [owner] - Local --> Owned : unregisterToOwned() - Local --> [*] : burn() all tokens + Local --> Owned : burn() [operator, last token] + Local --> Local : burn() [operator, not last] + Local --> [*] : burn() [owner, after owned-only] - Country --> Owned : unregisterToOwned() - Country --> [*] : burn() all tokens + Country --> Owned : burn() [operator, last token] + Country --> Country : burn() [operator, not last] + Country --> [*] : burn() [owner, after owned-only] note right of Owned : regionKey = 0 note right of Local : regionKey ≥ 1024 @@ -27,16 +29,16 @@ stateDiagram-v2 ### State Transitions -| From | To | Function | Bond Effect | -| :------------ | :------ | :------------------------- | :------------------------------------------------------------- | -| None | Owned | `claimUuid()` | Pull BASE_BOND from owner | -| None | Local | `registerFleetLocal()` | Pull BASE_BOND + tierBond from caller (becomes owner+operator) | -| None | Country | `registerFleetCountry()` | Pull BASE_BOND + tierBond from caller (becomes owner+operator) | -| Owned | Local | `registerFleetLocal()` | Pull tierBond from operator (only operator can call) | -| Owned | Country | `registerFleetCountry()` | Pull tierBond from operator (only operator can call) | -| Local/Country | Owned | `unregisterToOwned()` | Refund tierBond to operator | -| Owned | None | `releaseUuid()` / `burn()` | Refund BASE_BOND to owner | -| Local/Country | None | `burn()` | Refund tierBond to operator; BASE_BOND to owner on last burn | +| From | To | Function | Who Calls | Bond Effect | +| :------------ | :------ | :------------------------- | :-------- | :------------------------------------------------------------- | +| None | Owned | `claimUuid()` | Anyone | Pull BASE_BOND from caller (becomes owner) | +| None | Local | `registerFleetLocal()` | Anyone | Pull BASE_BOND + tierBond from caller (becomes owner+operator) | +| None | Country | `registerFleetCountry()` | Anyone | Pull BASE_BOND + tierBond from caller (becomes owner+operator) | +| Owned | Local | `registerFleetLocal()` | Operator | Pull tierBond from operator | +| Owned | Country | `registerFleetCountry()` | Operator | Pull tierBond from operator | +| Local/Country | Owned | `burn()` | Operator | Refund tierBond to operator (last token mints owned-only) | +| Owned | None | `burn()` | Owner | Refund BASE_BOND to owner | +| Local/Country | - | `burn()` | Operator | Refund tierBond to operator (not last token, stays registered) | ## Swarm Status States @@ -91,16 +93,17 @@ sequenceDiagram FI->>TOKEN: transferFrom(newOperator, this, totalTierBonds) FI->>TOKEN: transfer(oldOperator, totalTierBonds) - Note over FI: Unregister to Owned + Note over FI: Burn registered token (operator) FI->>TOKEN: transfer(operator, tierBond) - Note over FI: Burn (non-last token) + Note over FI: Burn last registered token (operator) + Note over FI: Mints owned-only token to owner FI->>TOKEN: transfer(operator, tierBond) - Note over FI: Burn (last token) - FI->>TOKEN: transfer(operator, tierBond) + Note over FI: Burn owned-only token (owner) FI->>TOKEN: transfer(owner, BASE_BOND) ``` +``` ## Orphan Lifecycle diff --git a/src/swarms/doc/maintenance.md b/src/swarms/doc/maintenance.md index bad2231d..4ef7588e 100644 --- a/src/swarms/doc/maintenance.md +++ b/src/swarms/doc/maintenance.md @@ -124,21 +124,21 @@ const [uuids, count] = await fleetIdentity.buildCountryOnlyBundle(countryCode); ```mermaid sequenceDiagram - actor FO as Fleet Owner + actor OP as Operator participant FI as FleetIdentity participant TOKEN as BOND_TOKEN - FO->>+FI: tierBond(currentTier, isCountry) - FI-->>-FO: currentBond - FO->>+FI: tierBond(targetTier, isCountry) - FI-->>-FO: targetBond + OP->>+FI: tierBond(currentTier, isCountry) + FI-->>-OP: currentBond + OP->>+FI: tierBond(targetTier, isCountry) + FI-->>-OP: targetBond - Note over FO: additionalBond = targetBond - currentBond + Note over OP: additionalBond = targetBond - currentBond - FO->>TOKEN: approve(FleetIdentity, additionalBond) - FO->>+FI: reassignTier(tokenId, targetTier) - FI->>TOKEN: transferFrom(...) - FI-->>-FO: FleetPromoted + OP->>TOKEN: approve(FleetIdentity, additionalBond) + OP->>+FI: reassignTier(tokenId, targetTier) + FI->>TOKEN: transferFrom(operator, ...) + FI-->>-OP: FleetPromoted ``` ### Quick Promote @@ -146,6 +146,7 @@ sequenceDiagram ```solidity fleetIdentity.promote(tokenId); // Moves to currentTier + 1 +// Only operator (or owner if no operator set) can call ``` ### Handle TierFull diff --git a/src/swarms/doc/swarm-operations.md b/src/swarms/doc/swarm-operations.md index 853352b8..6dbffe7a 100644 --- a/src/swarms/doc/swarm-operations.md +++ b/src/swarms/doc/swarm-operations.md @@ -42,10 +42,10 @@ sequenceDiagram Deterministic derivation: ```solidity -swarmId = uint256(keccak256(abi.encode(fleetUuid, providerId, filter))) +swarmId = uint256(keccak256(abi.encode(fleetUuid, filter, fingerprintSize, tagType))) ``` -Duplicate registration reverts with `SwarmAlreadyExists()`. +Swarm identity is based on fleet, filter, fingerprintSize, and tagType. ProviderId is mutable and not part of identity. Duplicate registration reverts with `SwarmAlreadyExists()`. ## XOR Filter Construction @@ -114,13 +114,10 @@ Only `ACCEPTED` swarms pass `checkMembership()`. ## Updates -Both operations reset status to `REGISTERED`: +The fleet owner can change the service provider. This resets status to `REGISTERED`: ```solidity -// Replace filter -swarmRegistry.updateSwarmFilter(swarmId, newFilterData); - -// Change provider +// Change provider (requires re-approval) swarmRegistry.updateSwarmProvider(swarmId, newProviderId); ``` @@ -130,12 +127,14 @@ sequenceDiagram participant SR as SwarmRegistry participant FI as FleetIdentity - FO->>+SR: updateSwarmFilter(swarmId, newFilter) + FO->>+SR: updateSwarmProvider(swarmId, newProviderId) SR->>FI: uuidOwner(fleetUuid) Note over SR: status → REGISTERED SR-->>-FO: ✓ (requires re-approval) ``` +**Note:** The XOR filter is immutable and part of swarm identity. To change the filter, delete the swarm and create a new one. + ## Deletion ```solidity From 6947340cf2b669daee09a5c5edef3e7bfdab826c Mon Sep 17 00:00:00 2001 From: Alex Sedighi Date: Tue, 3 Mar 2026 16:21:05 +1300 Subject: [PATCH 06/15] doc(swarms): minor fix --- lcov.info | 3301 ----------------------------------- src/swarms/doc/README.md | 15 +- src/swarms/doc/lifecycle.md | 25 +- 3 files changed, 21 insertions(+), 3320 deletions(-) delete mode 100644 lcov.info diff --git a/lcov.info b/lcov.info deleted file mode 100644 index e533fc28..00000000 --- a/lcov.info +++ /dev/null @@ -1,3301 +0,0 @@ -TN: -SF:script/CheckBridge.s.sol -DA:13,0 -FN:13,CheckBridge.setUp -FNDA:0,CheckBridge.setUp -DA:14,0 -DA:15,0 -DA:18,0 -FN:18,CheckBridge.run -FNDA:0,CheckBridge.run -DA:19,0 -DA:20,0 -DA:22,0 -DA:23,0 -DA:25,0 -BRDA:25,0,0,- -BRDA:25,0,1,- -DA:26,0 -DA:28,0 -DA:30,0 -BRDA:30,1,0,- -BRDA:30,1,1,- -DA:31,0 -DA:32,0 -BRDA:32,2,0,- -BRDA:32,2,1,- -DA:33,0 -DA:35,0 -DA:38,0 -FNF:2 -FNH:0 -LF:17 -LH:0 -BRF:6 -BRH:0 -end_of_record -TN: -SF:script/ContentSignWhitelist.s.sol -DA:13,0 -FN:13,ContentSignWhitelist.setUp -FNDA:0,ContentSignWhitelist.setUp -DA:14,0 -DA:15,0 -DA:18,0 -FN:18,ContentSignWhitelist.run -FNDA:0,ContentSignWhitelist.run -DA:19,0 -DA:21,0 -BRDA:21,0,0,- -BRDA:21,0,1,- -DA:22,0 -DA:24,0 -DA:26,0 -DA:27,0 -DA:28,0 -FNF:2 -FNH:0 -LF:11 -LH:0 -BRF:2 -BRH:0 -end_of_record -TN: -SF:script/DeployClick.s.sol -DA:14,0 -FN:14,DeployClick.setUp -FNDA:0,DeployClick.setUp -DA:15,0 -DA:16,0 -DA:18,0 -DA:19,0 -DA:22,0 -FN:22,DeployClick.run -FNDA:0,DeployClick.run -DA:23,0 -DA:25,0 -DA:26,0 -DA:28,0 -DA:29,0 -DA:32,0 -DA:35,0 -DA:37,0 -DA:39,0 -DA:40,0 -DA:41,0 -FNF:2 -FNH:0 -LF:17 -LH:0 -BRF:0 -BRH:0 -end_of_record -TN: -SF:script/DeployContentSignEnterprise.s.sol -DA:15,0 -FN:15,DeployContentSignEnterprise.setUp -FNDA:0,DeployContentSignEnterprise.setUp -DA:16,0 -DA:17,0 -DA:18,0 -DA:21,0 -FN:21,DeployContentSignEnterprise.run -FNDA:0,DeployContentSignEnterprise.run -DA:22,0 -DA:24,0 -DA:26,0 -DA:28,0 -DA:29,0 -DA:30,0 -DA:31,0 -FNF:2 -FNH:0 -LF:12 -LH:0 -BRF:0 -BRH:0 -end_of_record -TN: -SF:script/DeployL1Bridge.s.sol -DA:22,0 -FN:22,DeployL1Bridge.setUp -FNDA:0,DeployL1Bridge.setUp -DA:23,0 -DA:24,0 -DA:25,0 -DA:26,0 -DA:28,0 -DA:29,0 -DA:30,0 -DA:31,0 -DA:34,0 -FN:34,DeployL1Bridge.run -FNDA:0,DeployL1Bridge.run -DA:35,0 -DA:37,0 -DA:39,0 -DA:40,0 -DA:41,0 -DA:43,0 -DA:45,0 -DA:46,0 -FNF:2 -FNH:0 -LF:18 -LH:0 -BRF:0 -BRH:0 -end_of_record -TN: -SF:script/DeployL1Ens.s.sol -DA:18,0 -FN:18,DeployL1Ens.run -FNDA:0,DeployL1Ens.run -DA:19,0 -DA:20,0 -DA:22,0 -DA:24,0 -BRDA:24,0,0,- -BRDA:24,0,1,- -DA:25,0 -DA:26,0 -BRDA:26,1,0,- -BRDA:26,1,1,- -DA:27,0 -DA:28,0 -DA:29,0 -DA:30,0 -DA:32,0 -DA:35,0 -DA:36,0 -DA:39,0 -DA:40,0 -DA:42,0 -DA:45,0 -DA:47,0 -BRDA:47,2,0,- -DA:48,0 -DA:49,0 -DA:55,0 -DA:56,0 -DA:59,0 -DA:60,0 -DA:62,0 -DA:63,0 -DA:65,0 -DA:66,0 -DA:68,0 -FNF:1 -FNH:0 -LF:30 -LH:0 -BRF:5 -BRH:0 -end_of_record -TN: -SF:script/DeployL1Nodl.s.sol -DA:17,0 -FN:17,DeployL1Nodl.setUp -FNDA:0,DeployL1Nodl.setUp -DA:18,0 -DA:19,0 -DA:21,0 -DA:22,0 -DA:25,0 -FN:25,DeployL1Nodl.run -FNDA:0,DeployL1Nodl.run -DA:26,0 -DA:28,0 -DA:30,0 -DA:32,0 -FNF:2 -FNH:0 -LF:10 -LH:0 -BRF:0 -BRH:0 -end_of_record -TN: -SF:script/DeployL2Bridge.s.sol -DA:19,0 -FN:19,DeployL2Bridge.setUp -FNDA:0,DeployL2Bridge.setUp -DA:20,0 -DA:21,0 -DA:23,0 -DA:24,0 -DA:27,0 -FN:27,DeployL2Bridge.run -FNDA:0,DeployL2Bridge.run -DA:28,0 -DA:30,0 -DA:32,0 -DA:33,0 -DA:34,0 -DA:36,0 -DA:38,0 -DA:39,0 -FNF:2 -FNH:0 -LF:14 -LH:0 -BRF:0 -BRH:0 -end_of_record -TN: -SF:script/DeployL2Nodl.s.sol -DA:17,0 -FN:17,DeployL2Nodl.setUp -FNDA:0,DeployL2Nodl.setUp -DA:18,0 -DA:19,0 -DA:21,0 -DA:22,0 -DA:25,0 -FN:25,DeployL2Nodl.run -FNDA:0,DeployL2Nodl.run -DA:26,0 -DA:28,0 -DA:29,0 -DA:30,0 -DA:32,0 -DA:34,0 -FNF:2 -FNH:0 -LF:12 -LH:0 -BRF:0 -BRH:0 -end_of_record -TN: -SF:script/DeployMigrationNFT.s.sol -DA:18,0 -FN:18,DeployMigrationNFT.setUp -FNDA:0,DeployMigrationNFT.setUp -DA:19,0 -DA:20,0 -DA:22,0 -DA:23,0 -DA:24,0 -DA:25,0 -DA:27,0 -DA:28,0 -DA:32,0 -FN:32,DeployMigrationNFT.run -FNDA:0,DeployMigrationNFT.run -DA:33,0 -DA:35,0 -DA:37,0 -DA:39,0 -FNF:2 -FNH:0 -LF:14 -LH:0 -BRF:0 -BRH:0 -end_of_record -TN: -SF:script/DeployNodlMigration.sol -DA:14,0 -FN:14,DeployNodlMigration.setUp -FNDA:0,DeployNodlMigration.setUp -DA:15,0 -DA:16,0 -DA:18,0 -DA:19,0 -DA:20,0 -DA:23,0 -FN:23,DeployNodlMigration.run -FNDA:0,DeployNodlMigration.run -DA:24,0 -DA:26,0 -DA:27,0 -DA:29,0 -DA:30,0 -DA:32,0 -DA:34,0 -DA:36,0 -DA:37,0 -FNF:2 -FNH:0 -LF:16 -LH:0 -BRF:0 -BRH:0 -end_of_record -TN: -SF:script/DeployRewards.sol -DA:18,0 -FN:18,DeployRewards.setUp -FNDA:0,DeployRewards.setUp -DA:19,0 -DA:20,0 -DA:21,0 -DA:22,0 -DA:23,0 -DA:24,0 -DA:27,0 -FN:27,DeployRewards.run -FNDA:0,DeployRewards.run -DA:28,0 -DA:29,0 -BRDA:29,0,0,- -DA:30,0 -DA:31,0 -DA:32,0 -DA:35,0 -DA:36,0 -DA:39,0 -DA:40,0 -DA:42,0 -DA:44,0 -FNF:2 -FNH:0 -LF:19 -LH:0 -BRF:1 -BRH:0 -end_of_record -TN: -SF:script/RewardsSig.s.sol -DA:15,0 -FN:15,RewardsSig.setUp -FNDA:0,RewardsSig.setUp -DA:16,0 -DA:17,0 -DA:18,0 -DA:19,0 -DA:22,0 -FN:22,RewardsSig.run -FNDA:0,RewardsSig.run -DA:23,0 -DA:25,0 -DA:26,0 -DA:27,0 -DA:29,0 -DA:31,0 -DA:33,0 -DA:34,0 -DA:36,0 -DA:37,0 -DA:39,0 -DA:40,0 -DA:42,0 -DA:43,0 -FNF:2 -FNH:0 -LF:20 -LH:0 -BRF:0 -BRH:0 -end_of_record -TN: -SF:src/Grants.sol -DA:56,0 -FN:56,Grants.constructor -FNDA:0,Grants.constructor -DA:57,0 -BRDA:57,0,0,- -DA:58,0 -DA:61,0 -DA:62,0 -DA:63,0 -DA:75,0 -FN:75,Grants.addVestingSchedule -FNDA:0,Grants.addVestingSchedule -DA:83,0 -DA:85,0 -DA:87,0 -DA:88,0 -BRDA:88,1,0,- -DA:89,0 -DA:90,0 -DA:92,0 -DA:94,0 -DA:96,0 -DA:106,0 -FN:106,Grants.validateVestingSchedule -FNDA:0,Grants.validateVestingSchedule -DA:110,0 -DA:111,0 -DA:112,0 -DA:113,0 -DA:114,0 -DA:123,0 -FN:123,Grants.claim -FNDA:0,Grants.claim -DA:124,0 -DA:125,0 -DA:127,0 -DA:129,0 -DA:130,0 -DA:131,0 -DA:132,0 -DA:133,0 -DA:134,0 -BRDA:134,2,0,- -DA:135,0 -DA:136,0 -DA:137,0 -DA:138,0 -DA:139,0 -DA:140,0 -DA:141,0 -DA:142,0 -BRDA:142,3,0,- -DA:143,0 -DA:144,0 -DA:145,0 -DA:148,0 -DA:152,0 -BRDA:152,4,0,- -BRDA:152,4,1,- -DA:153,0 -DA:154,0 -DA:156,0 -DA:167,0 -FN:167,Grants.renounce -FNDA:0,Grants.renounce -DA:168,0 -DA:170,0 -DA:171,0 -DA:172,0 -DA:173,0 -DA:174,0 -BRDA:174,5,0,- -DA:175,0 -DA:176,0 -DA:181,0 -BRDA:181,6,0,- -BRDA:181,6,1,- -DA:182,0 -DA:184,0 -DA:195,0 -FN:195,Grants.cancelVestingSchedules -FNDA:0,Grants.cancelVestingSchedules -DA:196,0 -DA:197,0 -DA:198,0 -DA:200,0 -DA:201,0 -DA:202,0 -DA:203,0 -DA:204,0 -DA:205,0 -DA:206,0 -BRDA:206,7,0,- -DA:207,0 -DA:208,0 -DA:209,0 -DA:210,0 -DA:211,0 -DA:212,0 -DA:213,0 -DA:214,0 -DA:215,0 -DA:216,0 -DA:217,0 -DA:219,0 -DA:223,0 -BRDA:223,8,0,- -DA:224,0 -DA:227,0 -BRDA:227,9,0,- -DA:228,0 -DA:231,0 -BRDA:231,10,0,- -DA:232,0 -DA:235,0 -DA:243,0 -FN:243,Grants.getGrantsCount -FNDA:0,Grants.getGrantsCount -DA:244,0 -DA:245,0 -DA:246,0 -DA:248,0 -DA:253,0 -FN:253,Grants._mustBeNonZero -FNDA:0,Grants._mustBeNonZero -DA:254,0 -BRDA:254,11,0,- -DA:255,0 -DA:259,0 -FN:259,Grants._mustBeNonZeroAddress -FNDA:0,Grants._mustBeNonZeroAddress -DA:260,0 -BRDA:260,12,0,- -DA:261,0 -DA:265,0 -FN:265,Grants._mustNotBeSelf -FNDA:0,Grants._mustNotBeSelf -DA:266,0 -BRDA:266,13,0,- -DA:267,0 -DA:271,0 -FN:271,Grants._mustBeEqualOrExceedMinAmount -FNDA:0,Grants._mustBeEqualOrExceedMinAmount -DA:272,0 -BRDA:272,14,0,- -DA:273,0 -DA:277,0 -FN:277,Grants._sanitizePageRange -FNDA:0,Grants._sanitizePageRange -DA:278,0 -DA:279,0 -BRDA:279,15,0,- -DA:280,0 -DA:282,0 -BRDA:282,16,0,- -DA:283,0 -DA:285,0 -FNF:12 -FNH:0 -LF:114 -LH:0 -BRF:19 -BRH:0 -end_of_record -TN: -SF:src/L1Nodl.sol -DA:18,0 -FN:18,L1Nodl.constructor -FNDA:0,L1Nodl.constructor -DA:19,0 -BRDA:19,0,0,- -DA:20,0 -DA:22,0 -DA:23,0 -DA:26,0 -FN:26,L1Nodl.mint -FNDA:0,L1Nodl.mint -DA:27,0 -DA:30,0 -FN:30,L1Nodl.clock -FNDA:0,L1Nodl.clock -DA:31,0 -DA:35,0 -FN:35,L1Nodl.CLOCK_MODE -FNDA:0,L1Nodl.CLOCK_MODE -DA:36,0 -DA:39,0 -FN:39,L1Nodl.nonces -FNDA:0,L1Nodl.nonces -DA:40,0 -DA:43,0 -FN:43,L1Nodl._update -FNDA:0,L1Nodl._update -DA:44,0 -FNF:6 -FNH:0 -LF:15 -LH:0 -BRF:1 -BRH:0 -end_of_record -TN: -SF:src/NODL.sol -DA:12,0 -FN:12,NODL.constructor -FNDA:0,NODL.constructor -DA:13,0 -DA:14,0 -DA:17,0 -FN:17,NODL.mint -FNDA:0,NODL.mint -DA:18,0 -DA:20,0 -FNF:2 -FNH:0 -LF:6 -LH:0 -BRF:0 -BRH:0 -end_of_record -TN: -SF:src/Payment.sol -DA:40,0 -FN:40,Payment.constructor -FNDA:0,Payment.constructor -DA:43,0 -DA:44,0 -DA:58,0 -FN:58,Payment.pay -FNDA:0,Payment.pay -DA:59,0 -DA:60,0 -DA:62,0 -BRDA:62,0,0,- -DA:63,0 -DA:66,0 -DA:67,0 -DA:69,0 -DA:70,0 -DA:83,0 -FN:83,Payment.withdraw -FNDA:0,Payment.withdraw -DA:84,0 -FNF:3 -FNH:0 -LF:14 -LH:0 -BRF:1 -BRH:0 -end_of_record -TN: -SF:src/QuotaControl.sol -DA:84,0 -FN:84,QuotaControl.constructor -FNDA:0,QuotaControl.constructor -DA:85,0 -DA:86,0 -DA:88,0 -DA:89,0 -DA:90,0 -DA:97,0 -FN:97,QuotaControl.setQuota -FNDA:0,QuotaControl.setQuota -DA:98,0 -DA:99,0 -DA:109,0 -FN:109,QuotaControl.setPeriod -FNDA:0,QuotaControl.setPeriod -DA:110,0 -DA:111,0 -DA:112,0 -DA:121,0 -FN:121,QuotaControl._checkedResetClaimed -FNDA:0,QuotaControl._checkedResetClaimed -DA:122,0 -BRDA:122,0,0,- -DA:123,0 -DA:126,0 -DA:127,0 -DA:140,0 -FN:140,QuotaControl._checkedUpdateClaimed -FNDA:0,QuotaControl._checkedUpdateClaimed -DA:141,0 -DA:142,0 -BRDA:142,1,0,- -DA:143,0 -DA:145,0 -DA:156,0 -FN:156,QuotaControl._mustBeWithinPeriodRange -FNDA:0,QuotaControl._mustBeWithinPeriodRange -DA:157,0 -BRDA:157,2,0,- -DA:158,0 -DA:160,0 -BRDA:160,3,0,- -DA:161,0 -FNF:6 -FNH:0 -LF:28 -LH:0 -BRF:4 -BRH:0 -end_of_record -TN: -SF:src/Rewards.sol -DA:137,0 -FN:137,Rewards.constructor -FNDA:0,Rewards.constructor -DA:145,0 -DA:147,0 -DA:148,0 -DA:149,0 -DA:157,0 -FN:157,Rewards.mintReward -FNDA:0,Rewards.mintReward -DA:158,0 -DA:159,0 -DA:161,0 -DA:162,0 -DA:165,0 -DA:166,0 -DA:168,0 -DA:176,0 -FN:176,Rewards.mintBatchReward -FNDA:0,Rewards.mintBatchReward -DA:177,0 -DA:178,0 -DA:180,0 -DA:182,0 -DA:184,0 -DA:186,0 -DA:187,0 -DA:189,0 -DA:192,0 -DA:194,0 -DA:196,0 -DA:197,0 -DA:199,0 -DA:201,0 -DA:208,0 -FN:208,Rewards.setBatchSubmitterRewardBasisPoints -FNDA:0,Rewards.setBatchSubmitterRewardBasisPoints -DA:209,0 -DA:210,0 -DA:211,0 -DA:213,0 -DA:220,0 -FN:220,Rewards._mustBeLessThanBasisPointsDivisor -FNDA:0,Rewards._mustBeLessThanBasisPointsDivisor -DA:221,0 -BRDA:221,0,0,- -DA:222,0 -DA:231,0 -FN:231,Rewards._mustBeExpectedSequence -FNDA:0,Rewards._mustBeExpectedSequence -DA:232,0 -BRDA:232,1,0,- -DA:233,0 -DA:241,0 -FN:241,Rewards._mustBeExpectedBatchSequence -FNDA:0,Rewards._mustBeExpectedBatchSequence -DA:242,0 -BRDA:242,2,0,- -DA:243,0 -DA:251,0 -FN:251,Rewards._mustBeValidBatchStructure -FNDA:0,Rewards._mustBeValidBatchStructure -DA:252,0 -BRDA:252,3,0,- -DA:253,0 -DA:263,0 -FN:263,Rewards._mustBeFromAuthorizedOracle -FNDA:0,Rewards._mustBeFromAuthorizedOracle -DA:264,0 -BRDA:264,4,0,- -DA:265,0 -DA:274,0 -FN:274,Rewards._batchSum -FNDA:0,Rewards._batchSum -DA:275,0 -DA:276,0 -DA:277,0 -DA:279,0 -DA:287,0 -FN:287,Rewards.digestReward -FNDA:0,Rewards.digestReward -DA:288,0 -DA:289,0 -DA:297,0 -FN:297,Rewards.digestBatchReward -FNDA:0,Rewards.digestBatchReward -DA:298,0 -DA:299,0 -DA:300,0 -DA:301,0 -DA:308,0 -FN:308,Rewards.latestBatchDetails -FNDA:0,Rewards.latestBatchDetails -DA:309,0 -FNF:13 -FNH:0 -LF:63 -LH:0 -BRF:5 -BRH:0 -end_of_record -TN: -SF:src/bridge/BridgeBase.sol -DA:70,0 -FN:70,BridgeBase.constructor -FNDA:0,BridgeBase.constructor -DA:71,0 -DA:72,0 -DA:73,0 -DA:75,0 -DA:76,0 -DA:79,0 -DA:80,0 -DA:81,0 -DA:90,0 -FN:90,BridgeBase._createVote -FNDA:0,BridgeBase._createVote -DA:91,0 -DA:93,0 -DA:99,0 -FN:99,BridgeBase._recordVote -FNDA:0,BridgeBase._recordVote -DA:100,0 -DA:102,0 -DA:104,0 -DA:108,0 -FN:108,BridgeBase._processVote -FNDA:0,BridgeBase._processVote -DA:109,0 -DA:110,0 -DA:111,0 -DA:116,0 -FN:116,BridgeBase._execute -FNDA:0,BridgeBase._execute -DA:117,0 -DA:118,0 -DA:119,0 -DA:121,0 -DA:164,0 -FN:164,BridgeBase._mustNotHaveExecutedYet -FNDA:0,BridgeBase._mustNotHaveExecutedYet -DA:165,0 -BRDA:165,0,0,- -DA:166,0 -DA:170,0 -FN:170,BridgeBase._mustBePastSafetyDelay -FNDA:0,BridgeBase._mustBePastSafetyDelay -DA:171,0 -BRDA:171,1,0,- -DA:172,0 -DA:176,0 -FN:176,BridgeBase._mustHaveEnoughVotes -FNDA:0,BridgeBase._mustHaveEnoughVotes -DA:177,0 -BRDA:177,2,0,- -DA:178,0 -DA:182,0 -FN:182,BridgeBase._mustHaveEnoughOracles -FNDA:0,BridgeBase._mustHaveEnoughOracles -DA:183,0 -BRDA:183,3,0,- -DA:184,0 -DA:188,0 -FN:188,BridgeBase._mustBeAnOracle -FNDA:0,BridgeBase._mustBeAnOracle -DA:189,0 -BRDA:189,4,0,- -DA:190,0 -DA:194,0 -FN:194,BridgeBase._mustNotExceedMaxOracles -FNDA:0,BridgeBase._mustNotExceedMaxOracles -DA:195,0 -BRDA:195,5,0,- -DA:196,0 -DA:200,0 -FN:200,BridgeBase._mustNotBeZeroMinVotes -FNDA:0,BridgeBase._mustNotBeZeroMinVotes -DA:201,0 -BRDA:201,6,0,- -DA:202,0 -DA:206,0 -FN:206,BridgeBase._mustNotHaveVotedYet -FNDA:0,BridgeBase._mustNotHaveVotedYet -DA:207,0 -BRDA:207,7,0,- -DA:208,0 -FNF:13 -FNH:0 -LF:49 -LH:0 -BRF:8 -BRH:0 -end_of_record -TN: -SF:src/bridge/GrantsMigration.sol -DA:57,0 -FN:57,GrantsMigration.constructor -FNDA:0,GrantsMigration.constructor -DA:60,0 -DA:70,0 -FN:70,GrantsMigration.bridge -FNDA:0,GrantsMigration.bridge -DA:73,0 -DA:74,0 -DA:76,0 -BRDA:76,0,0,- -BRDA:76,0,1,- -DA:77,0 -DA:78,0 -DA:80,0 -DA:88,0 -FN:88,GrantsMigration.grant -FNDA:0,GrantsMigration.grant -DA:89,0 -DA:90,0 -DA:91,0 -DA:92,0 -DA:93,0 -DA:94,0 -DA:103,0 -DA:108,0 -FN:108,GrantsMigration._mustNotBeChangingParameters -FNDA:0,GrantsMigration._mustNotBeChangingParameters -DA:114,0 -DA:116,0 -BRDA:116,1,0,- -DA:117,0 -DA:120,0 -DA:121,0 -BRDA:121,2,0,- -DA:122,0 -DA:125,0 -DA:127,0 -DA:128,0 -DA:129,0 -DA:130,0 -DA:131,0 -DA:132,0 -BRDA:132,3,0,- -DA:133,0 -DA:138,0 -FN:138,GrantsMigration._createProposal -FNDA:0,GrantsMigration._createProposal -DA:145,0 -BRDA:145,4,0,- -DA:146,0 -DA:148,0 -BRDA:148,5,0,- -DA:149,0 -DA:151,0 -DA:152,0 -DA:153,0 -DA:156,0 -DA:158,0 -BRDA:158,6,0,- -DA:159,0 -DA:161,0 -DA:162,0 -DA:165,0 -FN:165,GrantsMigration._proposalExists -FNDA:0,GrantsMigration._proposalExists -DA:166,0 -DA:169,0 -FN:169,GrantsMigration._flagAsExecuted -FNDA:0,GrantsMigration._flagAsExecuted -DA:170,0 -DA:173,0 -FN:173,GrantsMigration._incTotalVotes -FNDA:0,GrantsMigration._incTotalVotes -DA:174,0 -DA:177,0 -FN:177,GrantsMigration._updateLastVote -FNDA:0,GrantsMigration._updateLastVote -DA:178,0 -DA:181,0 -FN:181,GrantsMigration._totalVotes -FNDA:0,GrantsMigration._totalVotes -DA:182,0 -DA:185,0 -FN:185,GrantsMigration._lastVote -FNDA:0,GrantsMigration._lastVote -DA:186,0 -DA:189,0 -FN:189,GrantsMigration._executed -FNDA:0,GrantsMigration._executed -DA:190,0 -FNF:12 -FNH:0 -LF:59 -LH:0 -BRF:8 -BRH:0 -end_of_record -TN: -SF:src/bridge/L1Bridge.sol -DA:80,0 -FN:80,L1Bridge.constructor -FNDA:0,L1Bridge.constructor -DA:81,0 -BRDA:81,0,0,- -DA:82,0 -DA:84,0 -DA:85,0 -DA:86,0 -DA:94,0 -FN:94,L1Bridge.pause -FNDA:0,L1Bridge.pause -DA:95,0 -DA:99,0 -FN:99,L1Bridge.unpause -FNDA:0,L1Bridge.unpause -DA:100,0 -DA:115,0 -FN:115,L1Bridge.quoteL2BaseCost -FNDA:0,L1Bridge.quoteL2BaseCost -DA:120,0 -DA:130,0 -FN:130,L1Bridge.quoteL2BaseCostAtGasPrice -FNDA:0,L1Bridge.quoteL2BaseCostAtGasPrice -DA:135,0 -DA:152,0 -FN:152,L1Bridge.deposit -FNDA:0,L1Bridge.deposit -DA:159,0 -BRDA:159,1,0,- -DA:160,0 -DA:162,0 -BRDA:162,2,0,- -DA:163,0 -DA:166,0 -DA:168,0 -DA:169,0 -DA:171,0 -DA:175,0 -DA:177,0 -DA:183,0 -FN:183,L1Bridge.deposit -FNDA:0,L1Bridge.deposit -DA:189,0 -DA:202,0 -FN:202,L1Bridge.claimFailedDeposit -FNDA:0,L1Bridge.claimFailedDeposit -DA:210,0 -DA:211,0 -BRDA:211,3,0,- -DA:212,0 -DA:214,0 -DA:217,0 -BRDA:217,4,0,- -DA:218,0 -DA:220,0 -DA:221,0 -DA:222,0 -DA:234,0 -FN:234,L1Bridge.finalizeWithdrawal -FNDA:0,L1Bridge.finalizeWithdrawal -DA:241,0 -BRDA:241,5,0,- -DA:242,0 -DA:245,0 -DA:246,0 -DA:249,0 -DA:255,0 -BRDA:255,6,0,- -DA:256,0 -DA:259,0 -DA:261,0 -DA:262,0 -DA:276,0 -FN:276,L1Bridge._parseL2WithdrawalMessage -FNDA:0,L1Bridge._parseL2WithdrawalMessage -DA:282,0 -BRDA:282,7,0,- -DA:283,0 -DA:287,0 -DA:288,0 -BRDA:288,8,0,- -DA:289,0 -DA:291,0 -DA:292,0 -FNF:10 -FNH:0 -LF:56 -LH:0 -BRF:9 -BRH:0 -end_of_record -TN: -SF:src/bridge/L2Bridge.sol -DA:49,0 -FN:49,L2Bridge.onlyL1Bridge -FNDA:0,L2Bridge.onlyL1Bridge -DA:50,0 -BRDA:50,0,0,- -DA:51,0 -DA:65,0 -FN:65,L2Bridge.constructor -FNDA:0,L2Bridge.constructor -DA:66,0 -BRDA:66,1,0,- -DA:67,0 -DA:69,0 -DA:70,0 -DA:78,0 -FN:78,L2Bridge.pause -FNDA:0,L2Bridge.pause -DA:79,0 -DA:83,0 -FN:83,L2Bridge.unpause -FNDA:0,L2Bridge.unpause -DA:84,0 -DA:90,0 -FN:90,L2Bridge.initialize -FNDA:0,L2Bridge.initialize -DA:91,0 -BRDA:91,2,0,- -DA:92,0 -DA:94,0 -BRDA:94,3,0,- -DA:95,0 -DA:97,0 -DA:103,0 -FN:103,L2Bridge.finalizeDeposit -FNDA:0,L2Bridge.finalizeDeposit -DA:109,0 -BRDA:109,4,0,- -DA:110,0 -DA:112,0 -BRDA:112,5,0,- -DA:113,0 -DA:116,0 -DA:118,0 -DA:124,0 -FN:124,L2Bridge.withdraw -FNDA:0,L2Bridge.withdraw -DA:125,0 -BRDA:125,6,0,- -DA:126,0 -DA:128,0 -BRDA:128,7,0,- -DA:129,0 -DA:132,0 -DA:135,0 -DA:136,0 -DA:138,0 -FNF:7 -FNH:0 -LF:34 -LH:0 -BRF:8 -BRH:0 -end_of_record -TN: -SF:src/bridge/MigrationNFT.sol -DA:69,0 -FN:69,MigrationNFT.constructor -FNDA:0,MigrationNFT.constructor -DA:75,0 -BRDA:75,0,0,- -DA:76,0 -DA:78,0 -BRDA:78,1,0,- -DA:79,0 -DA:81,0 -BRDA:81,2,0,- -DA:82,0 -DA:85,0 -DA:86,0 -BRDA:86,3,0,- -DA:87,0 -DA:91,0 -DA:92,0 -DA:93,0 -DA:94,0 -DA:101,0 -FN:101,MigrationNFT.tokenURI -FNDA:0,MigrationNFT.tokenURI -DA:102,0 -DA:104,0 -DA:105,0 -DA:112,0 -FN:112,MigrationNFT.safeMint -FNDA:0,MigrationNFT.safeMint -DA:113,0 -DA:115,0 -DA:117,0 -DA:118,0 -DA:119,0 -DA:121,0 -DA:123,0 -DA:124,0 -BRDA:124,4,0,- -DA:125,0 -DA:128,0 -DA:129,0 -DA:130,0 -DA:131,0 -DA:132,0 -DA:136,0 -FN:136,MigrationNFT._computeLevelUps -FNDA:0,MigrationNFT._computeLevelUps -DA:141,0 -DA:142,0 -DA:147,0 -DA:148,0 -DA:149,0 -BRDA:149,5,0,- -DA:150,0 -DA:151,0 -DA:155,0 -BRDA:155,6,0,- -DA:156,0 -DA:160,0 -FN:160,MigrationNFT._mustNotHaveBeenClaimed -FNDA:0,MigrationNFT._mustNotHaveBeenClaimed -DA:161,0 -BRDA:161,7,0,- -DA:162,0 -DA:166,0 -FN:166,MigrationNFT._mustBeAnExistingProposal -FNDA:0,MigrationNFT._mustBeAnExistingProposal -DA:168,0 -BRDA:168,8,0,- -DA:169,0 -DA:173,0 -FN:173,MigrationNFT._mustBeExecuted -FNDA:0,MigrationNFT._mustBeExecuted -DA:174,0 -BRDA:174,9,0,- -DA:175,0 -DA:179,0 -FN:179,MigrationNFT._mustAlreadyBeHolderOrEnoughHoldersRemaining -FNDA:0,MigrationNFT._mustAlreadyBeHolderOrEnoughHoldersRemaining -DA:180,0 -DA:181,0 -BRDA:181,10,0,- -DA:182,0 -DA:186,0 -FN:186,MigrationNFT._update -FNDA:0,MigrationNFT._update -DA:187,0 -DA:188,0 -BRDA:188,11,0,- -DA:190,0 -DA:193,0 -FNF:9 -FNH:0 -LF:61 -LH:0 -BRF:12 -BRH:0 -end_of_record -TN: -SF:src/bridge/NODLMigration.sol -DA:35,0 -FN:35,NODLMigration.bridge -FNDA:0,NODLMigration.bridge -DA:36,0 -DA:37,0 -DA:39,0 -BRDA:39,0,0,- -BRDA:39,0,1,- -DA:40,0 -DA:41,0 -DA:43,0 -DA:50,0 -FN:50,NODLMigration.withdraw -FNDA:0,NODLMigration.withdraw -DA:51,0 -DA:52,0 -DA:55,0 -FN:55,NODLMigration._mustNotBeChangingParameters -FNDA:0,NODLMigration._mustNotBeChangingParameters -DA:56,0 -BRDA:56,1,0,- -DA:57,0 -DA:61,0 -FN:61,NODLMigration._proposalExists -FNDA:0,NODLMigration._proposalExists -DA:62,0 -DA:65,0 -FN:65,NODLMigration._createVote -FNDA:0,NODLMigration._createVote -DA:66,0 -DA:67,0 -DA:68,0 -DA:71,0 -FN:71,NODLMigration._withdraw -FNDA:0,NODLMigration._withdraw -DA:72,0 -DA:73,0 -DA:76,0 -FN:76,NODLMigration._flagAsExecuted -FNDA:0,NODLMigration._flagAsExecuted -DA:77,0 -DA:80,0 -FN:80,NODLMigration._incTotalVotes -FNDA:0,NODLMigration._incTotalVotes -DA:81,0 -DA:84,0 -FN:84,NODLMigration._updateLastVote -FNDA:0,NODLMigration._updateLastVote -DA:85,0 -DA:88,0 -FN:88,NODLMigration._totalVotes -FNDA:0,NODLMigration._totalVotes -DA:89,0 -DA:92,0 -FN:92,NODLMigration._lastVote -FNDA:0,NODLMigration._lastVote -DA:93,0 -DA:96,0 -FN:96,NODLMigration._executed -FNDA:0,NODLMigration._executed -DA:97,0 -FNF:12 -FNH:0 -LF:34 -LH:0 -BRF:3 -BRH:0 -end_of_record -TN: -SF:src/contentsign/BaseContentSign.sol -DA:17,0 -FN:17,BaseContentSign.safeMint -FNDA:0,BaseContentSign.safeMint -DA:18,0 -DA:20,0 -DA:21,0 -DA:22,0 -DA:25,0 -FN:25,BaseContentSign.tokenURI -FNDA:0,BaseContentSign.tokenURI -DA:26,0 -DA:29,0 -FN:29,BaseContentSign.supportsInterface -FNDA:0,BaseContentSign.supportsInterface -DA:36,0 -DA:39,0 -FN:39,BaseContentSign._mustBeWhitelisted -FNDA:0,BaseContentSign._mustBeWhitelisted -DA:40,0 -BRDA:40,0,0,- -DA:41,0 -FNF:4 -FNH:0 -LF:12 -LH:0 -BRF:1 -BRH:0 -end_of_record -TN: -SF:src/contentsign/ClickBounty.sol -DA:158,0 -FN:158,ClickBounty.constructor -FNDA:0,ClickBounty.constructor -DA:159,0 -DA:160,0 -DA:162,0 -DA:163,0 -DA:164,0 -DA:181,0 -FN:181,ClickBounty.payEntryFee -FNDA:0,ClickBounty.payEntryFee -DA:182,0 -DA:184,0 -BRDA:184,0,0,- -DA:185,0 -DA:188,0 -DA:189,0 -BRDA:189,1,0,- -DA:190,0 -DA:193,0 -DA:194,0 -DA:209,0 -FN:209,ClickBounty.setEntryFee -FNDA:0,ClickBounty.setEntryFee -DA:210,0 -DA:211,0 -DA:212,0 -DA:224,0 -FN:224,ClickBounty.withdraw -FNDA:0,ClickBounty.withdraw -DA:225,0 -DA:226,0 -DA:254,0 -FN:254,ClickBounty.awardBounty -FNDA:0,ClickBounty.awardBounty -DA:255,0 -DA:257,0 -BRDA:257,2,0,- -DA:258,0 -DA:260,0 -DA:262,0 -BRDA:262,3,0,- -DA:263,0 -DA:265,0 -BRDA:265,4,0,- -DA:266,0 -DA:269,0 -DA:272,0 -DA:275,0 -DA:277,0 -DA:280,0 -DA:294,0 -FN:294,ClickBounty.getLeaderboard -FNDA:0,ClickBounty.getLeaderboard -DA:295,0 -DA:296,0 -DA:297,0 -DA:298,0 -DA:317,0 -FN:317,ClickBounty._updateLeaderboard -FNDA:0,ClickBounty._updateLeaderboard -DA:319,0 -BRDA:319,5,0,- -DA:320,0 -DA:321,0 -DA:322,0 -DA:326,0 -DA:327,0 -DA:328,0 -DA:329,0 -DA:330,0 -BRDA:330,6,0,- -DA:331,0 -DA:332,0 -DA:337,0 -BRDA:337,7,0,- -DA:338,0 -DA:339,0 -FNF:7 -FNH:0 -LF:56 -LH:0 -BRF:8 -BRH:0 -end_of_record -TN: -SF:src/contentsign/ClickContentSign.sol -DA:12,0 -FN:12,ClickContentSign.constructor -FNDA:0,ClickContentSign.constructor -DA:13,0 -DA:16,0 -FN:16,ClickContentSign._userIsWhitelisted -FNDA:0,ClickContentSign._userIsWhitelisted -DA:17,0 -FNF:2 -FNH:0 -LF:4 -LH:0 -BRF:0 -BRH:0 -end_of_record -TN: -SF:src/contentsign/EnterpriseContentSign.sol -DA:13,0 -FN:13,EnterpriseContentSign.constructor -FNDA:0,EnterpriseContentSign.constructor -DA:14,0 -DA:17,0 -FN:17,EnterpriseContentSign.supportsInterface -FNDA:0,EnterpriseContentSign.supportsInterface -DA:23,0 -DA:26,0 -FN:26,EnterpriseContentSign._userIsWhitelisted -FNDA:0,EnterpriseContentSign._userIsWhitelisted -DA:27,0 -FNF:3 -FNH:0 -LF:6 -LH:0 -BRF:0 -BRH:0 -end_of_record -TN: -SF:src/contentsign/PaymentMiddleware.sol -DA:21,0 -FN:21,PaymentMiddleware.constructor -FNDA:0,PaymentMiddleware.constructor -DA:24,0 -DA:25,0 -DA:26,0 -DA:27,0 -DA:30,0 -FN:30,PaymentMiddleware.safeMint -FNDA:0,PaymentMiddleware.safeMint -DA:32,0 -BRDA:32,0,0,- -DA:33,0 -DA:37,0 -DA:40,0 -DA:43,0 -FN:43,PaymentMiddleware.withdraw -FNDA:0,PaymentMiddleware.withdraw -DA:44,0 -DA:46,0 -DA:47,0 -DA:50,0 -FN:50,PaymentMiddleware.setFeeAmount -FNDA:0,PaymentMiddleware.setFeeAmount -DA:51,0 -DA:53,0 -DA:56,0 -FN:56,PaymentMiddleware.setTarget -FNDA:0,PaymentMiddleware.setTarget -DA:57,0 -DA:59,0 -DA:62,0 -FN:62,PaymentMiddleware.setWhitelist -FNDA:0,PaymentMiddleware.setWhitelist -DA:63,0 -DA:65,0 -DA:68,0 -FN:68,PaymentMiddleware.setFeeToken -FNDA:0,PaymentMiddleware.setFeeToken -DA:69,0 -DA:71,0 -FNF:7 -FNH:0 -LF:26 -LH:0 -BRF:1 -BRH:0 -end_of_record -TN: -SF:src/nameservice/ClickNameService.sol -DA:62,0 -FN:62,ClickNameService.constructor -FNDA:0,ClickNameService.constructor -DA:63,0 -DA:64,0 -DA:68,0 -FN:68,ClickNameService.resolve -FNDA:0,ClickNameService.resolve -DA:69,0 -DA:70,0 -DA:71,0 -BRDA:71,0,0,- -DA:72,0 -DA:74,0 -DA:80,0 -FN:80,ClickNameService.batchRegister -FNDA:0,ClickNameService.batchRegister -DA:81,0 -BRDA:81,1,0,- -DA:82,0 -DA:85,0 -DA:86,0 -DA:94,0 -FN:94,ClickNameService.setDefaultExpiry -FNDA:0,ClickNameService.setDefaultExpiry -DA:95,0 -DA:99,0 -FN:99,ClickNameService.register -FNDA:0,ClickNameService.register -DA:100,0 -DA:104,0 -FN:104,ClickNameService.registerWithExpiry -FNDA:0,ClickNameService.registerWithExpiry -DA:105,0 -BRDA:105,2,0,- -DA:106,0 -DA:109,0 -DA:110,0 -DA:111,0 -DA:112,0 -DA:115,0 -FN:115,ClickNameService._register -FNDA:0,ClickNameService._register -DA:116,0 -BRDA:116,3,0,- -DA:117,0 -DA:119,0 -BRDA:119,4,0,- -DA:120,0 -DA:123,0 -DA:124,0 -DA:125,0 -BRDA:125,5,0,- -BRDA:125,5,1,- -DA:126,0 -DA:128,0 -BRDA:128,6,0,- -DA:129,0 -DA:131,0 -DA:134,0 -DA:140,0 -FN:140,ClickNameService.supportsInterface -FNDA:0,ClickNameService.supportsInterface -DA:141,0 -DA:142,0 -DA:149,0 -FN:149,ClickNameService.burn -FNDA:0,ClickNameService.burn -DA:150,0 -DA:151,0 -DA:158,0 -FN:158,ClickNameService.removeExpired -FNDA:0,ClickNameService.removeExpired -DA:159,0 -DA:160,0 -BRDA:160,7,0,- -DA:161,0 -DA:163,0 -DA:164,0 -DA:171,0 -FN:171,ClickNameService.extendExpiry -FNDA:0,ClickNameService.extendExpiry -DA:172,0 -BRDA:172,8,0,- -DA:173,0 -DA:175,0 -BRDA:175,9,0,- -DA:176,0 -DA:179,0 -BRDA:179,10,0,- -BRDA:179,10,1,- -DA:180,0 -DA:182,0 -DA:187,0 -FN:187,ClickNameService._isAlphanumeric -FNDA:0,ClickNameService._isAlphanumeric -DA:188,0 -DA:189,0 -DA:190,0 -DA:191,0 -DA:193,0 -DA:197,0 -FN:197,ClickNameService._isAuthorized -FNDA:0,ClickNameService._isAuthorized -DA:198,0 -FNF:13 -FNH:0 -LF:66 -LH:0 -BRF:13 -BRH:0 -end_of_record -TN: -SF:src/nameservice/NameService.sol -DA:75,0 -FN:75,NameService.constructor -FNDA:0,NameService.constructor -DA:78,0 -DA:79,0 -DA:83,0 -FN:83,NameService.resolve -FNDA:0,NameService.resolve -DA:84,0 -DA:85,0 -DA:86,0 -BRDA:86,0,0,- -DA:87,0 -DA:89,0 -DA:95,0 -FN:95,NameService.batchRegister -FNDA:0,NameService.batchRegister -DA:96,0 -BRDA:96,1,0,- -DA:97,0 -DA:100,0 -DA:101,0 -DA:109,0 -FN:109,NameService.setDefaultExpiry -FNDA:0,NameService.setDefaultExpiry -DA:110,0 -DA:114,0 -FN:114,NameService.register -FNDA:0,NameService.register -DA:115,0 -DA:119,0 -FN:119,NameService.registerWithExpiry -FNDA:0,NameService.registerWithExpiry -DA:120,0 -BRDA:120,2,0,- -DA:121,0 -DA:124,0 -DA:125,0 -DA:126,0 -DA:127,0 -DA:130,0 -FN:130,NameService._register -FNDA:0,NameService._register -DA:131,0 -BRDA:131,3,0,- -DA:132,0 -DA:134,0 -BRDA:134,4,0,- -DA:135,0 -DA:138,0 -DA:139,0 -DA:140,0 -BRDA:140,5,0,- -BRDA:140,5,1,- -DA:141,0 -DA:143,0 -BRDA:143,6,0,- -DA:144,0 -DA:146,0 -DA:149,0 -DA:155,0 -FN:155,NameService.supportsInterface -FNDA:0,NameService.supportsInterface -DA:156,0 -DA:157,0 -DA:164,0 -FN:164,NameService.burn -FNDA:0,NameService.burn -DA:165,0 -DA:167,0 -DA:168,0 -DA:169,0 -DA:171,0 -DA:173,0 -DA:174,0 -DA:181,0 -FN:181,NameService.removeExpired -FNDA:0,NameService.removeExpired -DA:182,0 -DA:183,0 -BRDA:183,7,0,- -DA:184,0 -DA:186,0 -DA:187,0 -DA:194,0 -FN:194,NameService.extendExpiry -FNDA:0,NameService.extendExpiry -DA:195,0 -BRDA:195,8,0,- -DA:196,0 -DA:198,0 -BRDA:198,9,0,- -DA:199,0 -DA:202,0 -BRDA:202,10,0,- -BRDA:202,10,1,- -DA:203,0 -DA:205,0 -DA:210,0 -FN:210,NameService._isAlphanumeric -FNDA:0,NameService._isAlphanumeric -DA:211,0 -DA:212,0 -DA:213,0 -DA:214,0 -DA:216,0 -DA:220,0 -FN:220,NameService._isAuthorized -FNDA:0,NameService._isAuthorized -DA:221,0 -DA:228,0 -FN:228,NameService.setTextRecord -FNDA:0,NameService.setTextRecord -DA:229,0 -DA:231,0 -DA:232,0 -DA:233,0 -DA:235,0 -BRDA:235,12,0,- -DA:236,0 -DA:238,0 -BRDA:238,13,0,- -DA:239,0 -DA:241,0 -DA:242,0 -DA:249,0 -FN:249,NameService.getTextRecord -FNDA:0,NameService.getTextRecord -DA:250,0 -DA:251,0 -BRDA:251,14,0,- -DA:252,0 -DA:254,0 -FNF:15 -FNH:0 -LF:87 -LH:0 -BRF:16 -BRH:0 -end_of_record -TN: -SF:src/nameservice/PaymasterTest.sol -DA:23,0 -FN:23,PaymasterTest.register -FNDA:0,PaymasterTest.register -DA:24,0 -BRDA:24,0,0,- -DA:25,0 -DA:27,0 -BRDA:27,1,0,- -DA:28,0 -DA:31,0 -DA:32,0 -DA:33,0 -BRDA:33,2,0,- -BRDA:33,2,1,- -DA:34,0 -DA:36,0 -DA:39,0 -DA:43,0 -FN:43,PaymasterTest._isAlphanumeric -FNDA:0,PaymasterTest._isAlphanumeric -DA:44,0 -DA:45,0 -DA:46,0 -DA:47,0 -DA:49,0 -FNF:2 -FNH:0 -LF:17 -LH:0 -BRF:4 -BRH:0 -end_of_record -TN: -SF:src/nameservice/UniversalResolver.sol -DA:53,0 -FN:53,UniversalResolver.constructor -FNDA:0,UniversalResolver.constructor -DA:56,0 -DA:57,0 -DA:58,0 -DA:59,0 -DA:62,0 -DA:63,0 -DA:66,0 -FN:66,UniversalResolver.setUrl -FNDA:0,UniversalResolver.setUrl -DA:67,0 -DA:81,0 -FN:81,UniversalResolver._parseDnsDomain -FNDA:0,UniversalResolver._parseDnsDomain -DA:86,0 -DA:88,0 -DA:89,0 -DA:93,0 -DA:95,0 -DA:96,0 -DA:100,0 -DA:102,0 -DA:103,0 -DA:105,0 -DA:110,0 -FN:110,UniversalResolver.getStorageKey -FNDA:0,UniversalResolver.getStorageKey -DA:111,0 -DA:112,0 -DA:119,0 -FN:119,UniversalResolver.getTextRecordStorageKey -FNDA:0,UniversalResolver.getTextRecordStorageKey -DA:120,0 -DA:121,0 -DA:122,0 -DA:130,0 -FN:130,UniversalResolver.resolve -FNDA:0,UniversalResolver.resolve -DA:131,0 -DA:133,0 -BRDA:133,2,0,- -DA:134,0 -DA:137,0 -DA:138,0 -DA:140,0 -BRDA:140,3,0,- -BRDA:140,3,1,- -DA:141,0 -DA:142,0 -DA:143,0 -BRDA:143,4,0,- -BRDA:143,4,1,- -DA:144,0 -DA:145,0 -BRDA:145,5,0,- -DA:146,0 -DA:147,0 -BRDA:147,6,0,- -DA:148,0 -DA:152,0 -DA:155,0 -DA:156,0 -DA:158,0 -DA:159,0 -DA:161,0 -DA:167,0 -FN:167,UniversalResolver.resolveWithProof -FNDA:0,UniversalResolver.resolveWithProof -DA:168,0 -DA:169,0 -DA:172,0 -DA:174,0 -DA:176,0 -DA:178,0 -BRDA:178,7,0,- -DA:179,0 -DA:182,0 -BRDA:182,8,0,- -BRDA:182,8,1,- -DA:183,0 -DA:184,0 -BRDA:184,9,0,- -BRDA:184,9,1,- -DA:185,0 -DA:187,0 -DA:194,0 -FN:194,UniversalResolver.supportsInterface -FNDA:0,UniversalResolver.supportsInterface -DA:195,0 -DA:196,0 -FNF:8 -FNH:0 -LF:64 -LH:0 -BRF:12 -BRH:0 -end_of_record -TN: -SF:src/paymasters/BasePaymaster.sol -DA:33,0 -FN:33,BasePaymaster.constructor -FNDA:0,BasePaymaster.constructor -DA:34,0 -DA:35,0 -DA:38,0 -FN:38,BasePaymaster.validateAndPayForPaymasterTransaction -FNDA:0,BasePaymaster.validateAndPayForPaymasterTransaction -DA:43,0 -DA:46,0 -DA:48,0 -BRDA:48,0,0,- -DA:49,0 -DA:52,0 -DA:56,0 -DA:57,0 -DA:58,0 -DA:60,0 -BRDA:60,1,0,- -BRDA:60,1,1,- -DA:61,0 -DA:62,0 -BRDA:62,2,0,- -BRDA:62,2,1,- -DA:63,0 -DA:64,0 -DA:66,0 -DA:68,0 -DA:72,0 -DA:73,0 -BRDA:73,3,0,- -DA:74,0 -DA:77,0 -DA:80,0 -FN:80,BasePaymaster.postTransaction -FNDA:0,BasePaymaster.postTransaction -DA:88,0 -DA:93,0 -FN:93,BasePaymaster.withdraw -FNDA:0,BasePaymaster.withdraw -DA:94,0 -DA:96,0 -DA:97,0 -BRDA:97,4,0,- -DA:99,0 -DA:104,0 -FN:104,BasePaymaster._mustBeBootloader -FNDA:0,BasePaymaster._mustBeBootloader -DA:105,0 -BRDA:105,5,0,- -DA:106,0 -FNF:5 -FNH:0 -LF:33 -LH:0 -BRF:8 -BRH:0 -end_of_record -TN: -SF:src/paymasters/WhitelistPaymaster.sol -DA:22,0 -FN:22,WhitelistPaymaster.constructor -FNDA:0,WhitelistPaymaster.constructor -DA:23,0 -DA:26,0 -FN:26,WhitelistPaymaster.addWhitelistedContracts -FNDA:0,WhitelistPaymaster.addWhitelistedContracts -DA:27,0 -DA:29,0 -DA:30,0 -DA:33,0 -DA:36,0 -FN:36,WhitelistPaymaster.removeWhitelistedContracts -FNDA:0,WhitelistPaymaster.removeWhitelistedContracts -DA:37,0 -DA:39,0 -DA:40,0 -DA:43,0 -DA:46,0 -FN:46,WhitelistPaymaster.addWhitelistedUsers -FNDA:0,WhitelistPaymaster.addWhitelistedUsers -DA:47,0 -DA:49,0 -DA:50,0 -DA:53,0 -DA:56,0 -FN:56,WhitelistPaymaster.removeWhitelistedUsers -FNDA:0,WhitelistPaymaster.removeWhitelistedUsers -DA:57,0 -DA:59,0 -DA:60,0 -DA:63,0 -DA:66,0 -FN:66,WhitelistPaymaster._validateAndPayGeneralFlow -FNDA:0,WhitelistPaymaster._validateAndPayGeneralFlow -DA:67,0 -BRDA:67,0,0,- -DA:68,0 -DA:71,0 -BRDA:71,1,0,- -DA:72,0 -DA:76,0 -FN:76,WhitelistPaymaster._validateAndPayApprovalBasedFlow -FNDA:0,WhitelistPaymaster._validateAndPayApprovalBasedFlow -DA:81,0 -FNF:7 -FNH:0 -LF:29 -LH:0 -BRF:2 -BRH:0 -end_of_record -TN: -SF:src/swarms/FleetIdentity.sol -DA:227,128 -FN:227,FleetIdentity.constructor -FNDA:128,FleetIdentity.constructor -DA:228,128 -DA:229,128 -DA:243,0 -FN:243,FleetIdentity.registerFleetCountry -FNDA:0,FleetIdentity.registerFleetCountry -DA:248,0 -BRDA:248,0,0,- -DA:249,0 -BRDA:249,1,0,- -DA:250,0 -DA:251,0 -DA:252,0 -DA:267,1389 -FN:267,FleetIdentity.registerFleetLocal -FNDA:1389,FleetIdentity.registerFleetLocal -DA:272,1389 -BRDA:272,2,0,- -DA:273,1389 -BRDA:273,3,0,- -DA:274,1389 -BRDA:274,4,0,- -DA:275,1389 -DA:276,1389 -DA:277,1389 -DA:286,0 -FN:286,FleetIdentity.promote -FNDA:0,FleetIdentity.promote -DA:287,0 -DA:294,0 -FN:294,FleetIdentity.reassignTier -FNDA:0,FleetIdentity.reassignTier -DA:295,0 -DA:296,0 -BRDA:296,5,0,- -DA:297,0 -BRDA:297,6,0,- -BRDA:297,6,1,- -DA:298,0 -DA:300,0 -DA:316,0 -FN:316,FleetIdentity.setOperator -FNDA:0,FleetIdentity.setOperator -DA:318,0 -BRDA:318,7,0,- -DA:321,0 -BRDA:321,8,0,- -DA:322,0 -DA:325,0 -DA:328,0 -DA:329,0 -DA:332,0 -DA:335,0 -DA:338,0 -BRDA:338,9,0,- -DA:340,0 -DA:342,0 -DA:345,0 -DA:367,20 -FN:367,FleetIdentity.burn -FNDA:20,FleetIdentity.burn -DA:368,20 -DA:370,20 -DA:371,20 -DA:372,20 -DA:373,20 -DA:374,20 -DA:376,20 -BRDA:376,10,0,10 -BRDA:376,10,1,- -DA:378,10 -BRDA:378,11,0,- -DA:380,10 -DA:381,10 -DA:382,10 -DA:384,10 -DA:387,10 -BRDA:387,12,0,- -DA:388,0 -DA:391,10 -DA:392,10 -DA:395,10 -DA:397,10 -DA:398,10 -DA:400,10 -BRDA:400,13,0,10 -BRDA:400,13,1,- -DA:402,10 -DA:403,10 -DA:404,10 -DA:408,0 -DA:412,10 -DA:414,10 -DA:427,0 -FN:427,FleetIdentity.claimUuid -FNDA:0,FleetIdentity.claimUuid -DA:428,0 -BRDA:428,14,0,- -DA:429,0 -BRDA:429,15,0,- -DA:432,0 -DA:433,0 -DA:434,0 -DA:436,0 -DA:439,0 -DA:440,0 -DA:442,0 -DA:444,0 -DA:454,1399 -FN:454,FleetIdentity.tierBond -FNDA:1399,FleetIdentity.tierBond -DA:455,1399 -DA:456,1399 -DA:462,0 -FN:462,FleetIdentity.localInclusionHint -FNDA:0,FleetIdentity.localInclusionHint -DA:467,0 -BRDA:467,16,0,- -DA:468,0 -BRDA:468,17,0,- -DA:469,0 -DA:470,0 -DA:478,0 -FN:478,FleetIdentity.countryInclusionHint -FNDA:0,FleetIdentity.countryInclusionHint -DA:479,0 -BRDA:479,18,0,- -DA:482,0 -DA:485,0 -DA:486,0 -DA:487,0 -DA:488,0 -DA:489,0 -DA:490,0 -BRDA:490,19,0,- -DA:492,0 -DA:496,0 -FN:496,FleetIdentity.highestActiveTier -FNDA:0,FleetIdentity.highestActiveTier -DA:497,0 -DA:498,0 -DA:499,0 -DA:503,0 -FN:503,FleetIdentity.tierMemberCount -FNDA:0,FleetIdentity.tierMemberCount -DA:504,0 -DA:508,0 -FN:508,FleetIdentity.getTierMembers -FNDA:0,FleetIdentity.getTierMembers -DA:509,0 -DA:513,0 -FN:513,FleetIdentity.getTierUuids -FNDA:0,FleetIdentity.getTierUuids -DA:514,0 -DA:515,0 -DA:516,0 -DA:517,0 -DA:522,20 -FN:522,FleetIdentity.tokenUuid -FNDA:20,FleetIdentity.tokenUuid -DA:523,20 -DA:527,1439 -FN:527,FleetIdentity.tokenRegion -FNDA:1439,FleetIdentity.tokenRegion -DA:528,1439 -DA:532,1389 -FN:532,FleetIdentity.computeTokenId -FNDA:1389,FleetIdentity.computeTokenId -DA:533,1389 -DA:537,0 -FN:537,FleetIdentity.bonds -FNDA:0,FleetIdentity.bonds -DA:538,0 -DA:539,0 -DA:540,0 -DA:541,0 -DA:545,0 -FN:545,FleetIdentity.isOwnedOnly -FNDA:0,FleetIdentity.isOwnedOnly -DA:546,0 -DA:554,20 -FN:554,FleetIdentity.operatorOf -FNDA:20,FleetIdentity.operatorOf -DA:555,20 -DA:556,20 -BRDA:556,23,0,20 -DA:557,20 -DA:582,0 -FN:582,FleetIdentity.buildHighestBondedUuidBundle -FNDA:0,FleetIdentity.buildHighestBondedUuidBundle -DA:587,0 -BRDA:587,24,0,- -DA:588,0 -BRDA:588,25,0,- -DA:590,0 -DA:591,0 -DA:593,0 -DA:610,0 -FN:610,FleetIdentity.buildCountryOnlyBundle -FNDA:0,FleetIdentity.buildCountryOnlyBundle -DA:615,0 -BRDA:615,26,0,- -DA:617,0 -DA:619,0 -DA:621,0 -DA:633,0 -FN:633,FleetIdentity._buildHighestBondedUuidBundle -FNDA:0,FleetIdentity._buildHighestBondedUuidBundle -DA:638,0 -DA:640,0 -DA:643,0 -DA:644,0 -DA:647,0 -DA:650,0 -DA:655,0 -DA:662,0 -FN:662,FleetIdentity._appendTierUuids -FNDA:0,FleetIdentity._appendTierUuids -DA:668,0 -DA:669,0 -DA:670,0 -DA:671,0 -DA:673,0 -DA:674,0 -DA:675,0 -DA:677,0 -DA:685,0 -FN:685,FleetIdentity.getActiveCountries -FNDA:0,FleetIdentity.getActiveCountries -DA:686,0 -DA:691,0 -FN:691,FleetIdentity.getActiveAdminAreas -FNDA:0,FleetIdentity.getActiveAdminAreas -DA:693,0 -DA:694,0 -DA:695,0 -DA:696,0 -DA:700,0 -DA:701,0 -DA:702,0 -DA:703,0 -DA:704,0 -DA:705,0 -DA:706,0 -DA:709,0 -DA:714,0 -FN:714,FleetIdentity.getCountryAdminAreas -FNDA:0,FleetIdentity.getCountryAdminAreas -DA:715,0 -DA:720,1389 -FN:720,FleetIdentity.makeAdminRegion -FNDA:1389,FleetIdentity.makeAdminRegion -DA:721,1389 -DA:731,1393 -FN:731,FleetIdentity._countryFromRegion -FNDA:1393,FleetIdentity._countryFromRegion -DA:732,1393 -DA:736,0 -FN:736,FleetIdentity._adminFromRegion -FNDA:0,FleetIdentity._adminFromRegion -DA:737,0 -DA:742,2798 -FN:742,FleetIdentity._isCountryRegion -FNDA:2798,FleetIdentity._isCountryRegion -DA:743,2798 -DA:749,1389 -FN:749,FleetIdentity._pullBond -FNDA:1389,FleetIdentity._pullBond -DA:750,1389 -BRDA:750,27,0,1389 -DA:751,1389 -DA:756,20 -FN:756,FleetIdentity._refundBond -FNDA:20,FleetIdentity._refundBond -DA:757,20 -BRDA:757,28,0,20 -DA:758,20 -DA:765,10 -FN:765,FleetIdentity._clearUuidOwnership -FNDA:10,FleetIdentity._clearUuidOwnership -DA:766,10 -DA:767,10 -DA:768,10 -DA:769,10 -DA:770,10 -DA:775,0 -FN:775,FleetIdentity._decrementUuidCount -FNDA:0,FleetIdentity._decrementUuidCount -DA:776,0 -DA:777,0 -BRDA:777,29,0,- -BRDA:777,29,1,- -DA:778,0 -DA:780,0 -DA:788,10 -FN:788,FleetIdentity._cleanupFleetFromTier -FNDA:10,FleetIdentity._cleanupFleetFromTier -DA:789,10 -DA:790,10 -DA:791,10 -DA:792,10 -DA:793,10 -DA:800,1389 -FN:800,FleetIdentity._mintFleetToken -FNDA:1389,FleetIdentity._mintFleetToken -DA:801,1389 -DA:802,1389 -DA:803,1389 -DA:804,1389 -DA:805,1389 -DA:810,0 -FN:810,FleetIdentity._mintFleetTokenTo -FNDA:0,FleetIdentity._mintFleetTokenTo -DA:811,0 -DA:812,0 -DA:813,0 -DA:814,0 -DA:815,0 -DA:824,1389 -FN:824,FleetIdentity._register -FNDA:1389,FleetIdentity._register -DA:825,1389 -DA:826,1389 -DA:827,1389 -DA:828,0 -DA:830,1389 -BRDA:830,30,0,- -BRDA:830,30,1,- -DA:832,0 -DA:833,0 -BRDA:833,31,0,- -DA:834,0 -DA:836,0 -DA:837,0 -DA:838,0 -DA:840,0 -DA:843,0 -DA:845,0 -DA:846,1389 -BRDA:846,32,0,1389 -BRDA:846,32,1,- -DA:848,1389 -DA:849,1389 -DA:850,1389 -DA:851,1389 -DA:854,1389 -DA:857,1389 -DA:859,1389 -DA:862,0 -DA:863,0 -BRDA:863,33,0,- -DA:864,0 -BRDA:864,34,0,- -DA:865,0 -DA:867,0 -DA:868,0 -DA:870,0 -DA:873,0 -DA:875,0 -DA:880,0 -FN:880,FleetIdentity._promote -FNDA:0,FleetIdentity._promote -DA:881,0 -DA:882,0 -DA:883,0 -BRDA:883,35,0,- -DA:885,0 -DA:886,0 -DA:887,0 -BRDA:887,36,0,- -DA:888,0 -BRDA:888,37,0,- -DA:889,0 -BRDA:889,38,0,- -DA:891,0 -DA:892,0 -DA:893,0 -DA:894,0 -DA:897,0 -DA:898,0 -DA:899,0 -DA:900,0 -DA:903,0 -DA:905,0 -DA:909,0 -FN:909,FleetIdentity._demote -FNDA:0,FleetIdentity._demote -DA:910,0 -DA:911,0 -DA:912,0 -BRDA:912,39,0,- -DA:914,0 -DA:915,0 -DA:916,0 -BRDA:916,40,0,- -DA:917,0 -BRDA:917,41,0,- -DA:919,0 -DA:920,0 -DA:921,0 -DA:922,0 -DA:925,0 -DA:926,0 -DA:927,0 -DA:928,0 -DA:929,0 -DA:932,0 -DA:934,0 -DA:938,1389 -FN:938,FleetIdentity._validateExplicitTier -FNDA:1389,FleetIdentity._validateExplicitTier -DA:939,1389 -BRDA:939,42,0,- -DA:940,1389 -BRDA:940,43,0,- -DA:946,0 -FN:946,FleetIdentity._findMaxTierIndex -FNDA:0,FleetIdentity._findMaxTierIndex -DA:951,0 -DA:952,0 -DA:954,0 -DA:955,0 -BRDA:955,44,0,- -DA:956,0 -DA:976,0 -FN:976,FleetIdentity._findCheapestInclusionTier -FNDA:0,FleetIdentity._findCheapestInclusionTier -DA:981,0 -DA:982,0 -DA:983,0 -DA:985,0 -DA:990,0 -DA:991,0 -DA:992,0 -DA:994,0 -BRDA:994,45,0,- -DA:995,0 -DA:1000,0 -DA:1001,0 -DA:1002,0 -DA:1003,0 -DA:1007,0 -BRDA:1007,46,0,- -DA:1008,0 -DA:1011,0 -DA:1016,1389 -FN:1016,FleetIdentity._addToTier -FNDA:1389,FleetIdentity._addToTier -DA:1017,1389 -DA:1018,1389 -DA:1021,1389 -BRDA:1021,47,0,1383 -DA:1022,1383 -DA:1027,10 -FN:1027,FleetIdentity._removeFromTier -FNDA:10,FleetIdentity._removeFromTier -DA:1028,10 -DA:1029,10 -DA:1030,10 -DA:1032,10 -BRDA:1032,48,0,- -DA:1033,0 -DA:1034,0 -DA:1035,0 -DA:1037,10 -DA:1041,10 -FN:1041,FleetIdentity._trimTierCount -FNDA:10,FleetIdentity._trimTierCount -DA:1042,10 -DA:1043,20 -DA:1044,10 -DA:1046,10 -DA:1052,1389 -FN:1052,FleetIdentity._addToRegionIndex -FNDA:1389,FleetIdentity._addToRegionIndex -DA:1053,1389 -BRDA:1053,49,0,- -BRDA:1053,49,1,1383 -DA:1055,0 -DA:1056,0 -BRDA:1056,50,0,- -DA:1057,0 -DA:1058,0 -DA:1062,1389 -BRDA:1062,51,0,1383 -DA:1063,1383 -DA:1065,1383 -BRDA:1065,52,0,1383 -DA:1066,1383 -DA:1067,1383 -DA:1069,1383 -DA:1070,1383 -DA:1076,10 -FN:1076,FleetIdentity._removeFromRegionIndex -FNDA:10,FleetIdentity._removeFromRegionIndex -DA:1077,10 -DA:1079,10 -BRDA:1079,54,0,- -BRDA:1079,54,1,- -DA:1080,0 -DA:1081,0 -DA:1082,0 -BRDA:1082,55,0,- -DA:1084,0 -DA:1086,0 -DA:1087,0 -DA:1088,0 -BRDA:1088,57,0,- -DA:1089,0 -DA:1090,0 -DA:1091,0 -DA:1093,0 -DA:1094,0 -DA:1098,10 -DA:1099,10 -BRDA:1099,58,0,10 -DA:1100,10 -DA:1101,10 -DA:1102,10 -DA:1103,10 -DA:1104,10 -BRDA:1104,59,0,- -DA:1105,0 -DA:1106,0 -DA:1107,0 -DA:1109,10 -DA:1110,10 -DA:1113,10 -BRDA:1113,60,0,10 -DA:1114,10 -DA:1115,10 -BRDA:1115,61,0,10 -DA:1116,10 -DA:1117,10 -DA:1118,10 -BRDA:1118,62,0,- -DA:1119,0 -DA:1120,0 -DA:1121,0 -DA:1123,10 -DA:1124,10 -DA:1135,1419 -FN:1135,FleetIdentity._update -FNDA:1419,FleetIdentity._update -DA:1136,1419 -DA:1140,1419 -DA:1141,1419 -BRDA:1141,63,0,- -DA:1142,0 -DA:1145,1419 -DA:1148,0 -FN:1148,FleetIdentity._increaseBalance -FNDA:0,FleetIdentity._increaseBalance -DA:1149,0 -DA:1152,0 -FN:1152,FleetIdentity.supportsInterface -FNDA:0,FleetIdentity.supportsInterface -DA:1153,0 -FNF:53 -FNH:24 -LF:387 -LH:144 -BRF:67 -BRH:13 -end_of_record -TN: -SF:src/swarms/ServiceProvider.sol -DA:27,1407 -FN:27,ServiceProvider.registerProvider -FNDA:1407,ServiceProvider.registerProvider -DA:28,1407 -BRDA:28,0,0,- -DA:29,0 -DA:32,1407 -DA:34,1407 -DA:36,1407 -DA:38,1407 -DA:43,15 -FN:43,ServiceProvider.burn -FNDA:15,ServiceProvider.burn -DA:44,15 -BRDA:44,1,0,- -DA:45,0 -DA:48,15 -DA:50,15 -DA:52,15 -FNF:2 -FNH:2 -LF:13 -LH:11 -BRF:2 -BRH:0 -end_of_record -TN: -SF:src/swarms/SwarmRegistryL1.sol -DA:83,313 -FN:83,SwarmRegistryL1.computeSwarmId -FNDA:313,SwarmRegistryL1.computeSwarmId -DA:88,313 -DA:91,63 -FN:91,SwarmRegistryL1.constructor -FNDA:63,SwarmRegistryL1.constructor -DA:92,63 -BRDA:92,0,0,3 -DA:93,3 -DA:95,60 -DA:96,60 -DA:106,573 -FN:106,SwarmRegistryL1.registerSwarm -FNDA:573,SwarmRegistryL1.registerSwarm -DA:113,573 -BRDA:113,1,0,1 -DA:114,1 -DA:116,572 -BRDA:116,2,0,258 -DA:117,258 -DA:119,314 -BRDA:119,3,0,2 -DA:120,2 -DA:124,312 -BRDA:124,4,0,1 -DA:125,1 -DA:127,311 -BRDA:127,5,0,- -DA:128,0 -DA:131,310 -DA:133,310 -BRDA:133,6,0,1 -DA:134,1 -DA:137,309 -DA:138,309 -DA:139,309 -DA:140,309 -DA:141,309 -DA:142,309 -DA:144,309 -DA:145,309 -DA:147,309 -DA:149,309 -DA:154,7 -FN:154,SwarmRegistryL1.acceptSwarm -FNDA:7,SwarmRegistryL1.acceptSwarm -DA:155,7 -DA:156,7 -BRDA:156,7,0,1 -DA:158,6 -DA:159,6 -BRDA:159,8,0,2 -DA:161,4 -BRDA:161,9,0,1 -DA:162,1 -DA:164,3 -DA:165,3 -DA:170,4 -FN:170,SwarmRegistryL1.rejectSwarm -FNDA:4,SwarmRegistryL1.rejectSwarm -DA:171,4 -DA:172,4 -BRDA:172,10,0,- -DA:174,4 -DA:175,4 -BRDA:175,11,0,1 -DA:177,3 -BRDA:177,12,0,1 -DA:178,1 -DA:180,2 -DA:181,2 -DA:187,5 -FN:187,SwarmRegistryL1.updateSwarmProvider -FNDA:5,SwarmRegistryL1.updateSwarmProvider -DA:188,5 -DA:189,5 -BRDA:189,13,0,1 -DA:190,1 -DA:192,4 -BRDA:192,14,0,1 -DA:193,1 -DA:195,3 -BRDA:195,15,0,- -DA:196,0 -DA:199,2 -DA:201,2 -DA:203,2 -DA:205,2 -DA:210,7 -FN:210,SwarmRegistryL1.deleteSwarm -FNDA:7,SwarmRegistryL1.deleteSwarm -DA:211,7 -DA:212,7 -BRDA:212,16,0,1 -DA:213,1 -DA:215,6 -BRDA:215,17,0,1 -DA:216,1 -DA:219,5 -DA:221,5 -DA:223,5 -DA:225,5 -DA:232,26 -FN:232,SwarmRegistryL1.isSwarmValid -FNDA:26,SwarmRegistryL1.isSwarmValid -DA:233,26 -DA:234,26 -BRDA:234,18,0,1 -DA:237,25 -DA:239,25 -BRDA:239,19,0,25 -DA:240,18 -DA:241,7 -BRDA:241,19,1,7 -DA:242,7 -DA:248,6 -FN:248,SwarmRegistryL1.purgeOrphanedSwarm -FNDA:6,SwarmRegistryL1.purgeOrphanedSwarm -DA:249,6 -DA:250,6 -BRDA:250,20,0,1 -DA:252,5 -DA:253,5 -BRDA:253,21,0,1 -DA:255,4 -DA:257,4 -DA:259,4 -DA:261,4 -DA:268,7 -FN:268,SwarmRegistryL1.checkMembership -FNDA:7,SwarmRegistryL1.checkMembership -DA:269,7 -DA:270,7 -BRDA:270,22,0,1 -DA:271,1 -DA:275,6 -DA:276,6 -BRDA:276,23,0,1 -DA:278,5 -DA:279,5 -DA:281,0 -DA:285,5 -BRDA:285,24,0,5 -DA:287,5 -DA:292,5 -DA:293,5 -DA:295,0 -DA:297,4 -DA:298,4 -DA:299,4 -DA:301,4 -DA:302,4 -DA:304,4 -DA:305,4 -DA:306,4 -DA:308,4 -DA:314,9 -FN:314,SwarmRegistryL1._removeFromUuidSwarms -FNDA:9,SwarmRegistryL1._removeFromUuidSwarms -DA:315,9 -DA:316,9 -DA:317,9 -DA:319,9 -DA:320,9 -DA:321,9 -DA:322,9 -DA:331,12 -FN:331,SwarmRegistryL1._readFingerprint -FNDA:12,SwarmRegistryL1._readFingerprint -DA:332,12 -DA:333,12 -DA:334,12 -DA:337,12 -DA:340,12 -DA:341,12 -DA:342,24 -DA:344,24 -DA:348,12 -DA:349,12 -DA:350,12 -DA:352,12 -FNF:12 -FNH:12 -LF:132 -LH:128 -BRF:26 -BRH:23 -end_of_record -TN: -SF:src/swarms/SwarmRegistryUniversal.sol -DA:89,576 -FN:89,SwarmRegistryUniversal.computeSwarmId -FNDA:576,SwarmRegistryUniversal.computeSwarmId -DA:94,576 -DA:97,71 -FN:97,SwarmRegistryUniversal.constructor -FNDA:71,SwarmRegistryUniversal.constructor -DA:98,71 -BRDA:98,0,0,3 -DA:99,3 -DA:101,68 -DA:102,68 -DA:112,836 -FN:112,SwarmRegistryUniversal.registerSwarm -FNDA:836,SwarmRegistryUniversal.registerSwarm -DA:119,836 -BRDA:119,1,0,1 -DA:120,1 -DA:122,835 -BRDA:122,2,0,258 -DA:123,258 -DA:125,577 -BRDA:125,3,0,1 -DA:126,1 -DA:128,576 -BRDA:128,4,0,1 -DA:129,1 -DA:133,575 -BRDA:133,5,0,1 -DA:134,1 -DA:136,574 -BRDA:136,6,0,- -DA:137,0 -DA:140,573 -DA:142,573 -BRDA:142,7,0,1 -DA:143,1 -DA:146,572 -DA:147,572 -DA:148,572 -DA:149,572 -DA:150,572 -DA:151,572 -DA:152,572 -DA:154,572 -DA:156,572 -DA:157,572 -DA:159,572 -DA:164,9 -FN:164,SwarmRegistryUniversal.acceptSwarm -FNDA:9,SwarmRegistryUniversal.acceptSwarm -DA:165,9 -DA:166,9 -BRDA:166,8,0,1 -DA:168,8 -DA:169,8 -BRDA:169,9,0,2 -DA:171,6 -BRDA:171,10,0,2 -DA:172,2 -DA:174,4 -DA:175,4 -DA:180,5 -FN:180,SwarmRegistryUniversal.rejectSwarm -FNDA:5,SwarmRegistryUniversal.rejectSwarm -DA:181,5 -DA:182,5 -BRDA:182,11,0,- -DA:184,5 -DA:185,5 -BRDA:185,12,0,1 -DA:187,4 -BRDA:187,13,0,1 -DA:188,1 -DA:190,3 -DA:191,3 -DA:197,5 -FN:197,SwarmRegistryUniversal.updateSwarmProvider -FNDA:5,SwarmRegistryUniversal.updateSwarmProvider -DA:198,5 -DA:199,5 -BRDA:199,14,0,1 -DA:200,1 -DA:202,4 -BRDA:202,15,0,1 -DA:203,1 -DA:205,3 -BRDA:205,16,0,- -DA:206,0 -DA:209,2 -DA:212,2 -DA:213,2 -DA:215,2 -DA:220,8 -FN:220,SwarmRegistryUniversal.deleteSwarm -FNDA:8,SwarmRegistryUniversal.deleteSwarm -DA:221,8 -DA:222,8 -BRDA:222,17,0,1 -DA:223,1 -DA:225,7 -BRDA:225,18,0,1 -DA:226,1 -DA:229,6 -DA:231,6 -DA:233,6 -DA:234,6 -DA:236,6 -DA:243,29 -FN:243,SwarmRegistryUniversal.isSwarmValid -FNDA:29,SwarmRegistryUniversal.isSwarmValid -DA:244,29 -DA:245,29 -BRDA:245,19,0,1 -DA:248,28 -DA:250,28 -BRDA:250,20,0,28 -DA:251,20 -DA:252,8 -BRDA:252,20,1,8 -DA:253,8 -DA:259,7 -FN:259,SwarmRegistryUniversal.purgeOrphanedSwarm -FNDA:7,SwarmRegistryUniversal.purgeOrphanedSwarm -DA:260,7 -DA:261,7 -BRDA:261,21,0,1 -DA:263,6 -DA:264,6 -BRDA:264,22,0,1 -DA:266,5 -DA:268,5 -DA:270,5 -DA:271,5 -DA:273,5 -DA:280,6 -FN:280,SwarmRegistryUniversal.checkMembership -FNDA:6,SwarmRegistryUniversal.checkMembership -DA:281,6 -DA:282,6 -BRDA:282,23,0,1 -DA:283,1 -DA:287,5 -DA:288,5 -BRDA:288,24,0,1 -DA:290,4 -DA:291,4 -DA:294,4 -DA:295,4 -DA:298,4 -DA:299,4 -DA:300,4 -DA:302,4 -DA:303,4 -DA:306,4 -DA:307,4 -DA:308,4 -DA:310,4 -DA:316,3 -FN:316,SwarmRegistryUniversal.getFilterData -FNDA:3,SwarmRegistryUniversal.getFilterData -DA:317,3 -BRDA:317,26,0,1 -DA:318,1 -DA:320,2 -DA:326,11 -FN:326,SwarmRegistryUniversal._removeFromUuidSwarms -FNDA:11,SwarmRegistryUniversal._removeFromUuidSwarms -DA:327,11 -DA:328,11 -DA:329,11 -DA:331,11 -DA:332,11 -DA:333,11 -DA:334,11 -DA:343,12 -FN:343,SwarmRegistryUniversal._readFingerprint -FNDA:12,SwarmRegistryUniversal._readFingerprint -DA:344,12 -DA:345,12 -DA:346,12 -DA:349,12 -DA:350,12 -DA:351,24 -DA:353,24 -DA:358,12 -DA:359,12 -DA:360,12 -DA:362,12 -FNF:13 -FNH:13 -LF:136 -LH:134 -BRF:27 -BRH:24 -end_of_record -TN: -SF:test/FleetIdentity.t.sol -DA:12,0 -FN:12,MockERC20.mint -FNDA:0,MockERC20.mint -DA:13,0 -DA:23,0 -FN:23,BadERC20.mint -FNDA:0,BadERC20.mint -DA:24,0 -DA:27,0 -FN:27,BadERC20.setFail -FNDA:0,BadERC20.setFail -DA:28,0 -DA:31,0 -FN:31,BadERC20.transfer -FNDA:0,BadERC20.transfer -DA:33,0 -DA:36,0 -FN:36,BadERC20.transferFrom -FNDA:0,BadERC20.transferFrom -DA:38,0 -FNF:5 -FNH:0 -LF:10 -LH:0 -BRF:0 -BRH:0 -end_of_record -TN: -SF:test/FleetIdentityFairness.t.sol -DA:12,0 -FN:12,MockERC20Fairness.mint -FNDA:0,MockERC20Fairness.mint -DA:13,0 -FNF:1 -FNH:0 -LF:2 -LH:0 -BRF:0 -BRH:0 -end_of_record -TN: -SF:test/Grants.t.sol -DA:9,0 -FN:9,MockToken.constructor -FNDA:0,MockToken.constructor -DA:10,0 -FNF:1 -FNH:0 -LF:2 -LH:0 -BRF:0 -BRH:0 -end_of_record -TN: -SF:test/Payment.t.sol -DA:12,0 -FN:12,MockToken.constructor -FNDA:0,MockToken.constructor -DA:13,0 -FNF:1 -FNH:0 -LF:2 -LH:0 -BRF:0 -BRH:0 -end_of_record -TN: -SF:test/QuotaControl.t.sol -DA:14,0 -FN:14,TestableQuotaControl.exposeCheckedResetClaimed -FNDA:0,TestableQuotaControl.exposeCheckedResetClaimed -DA:15,0 -DA:18,0 -FN:18,TestableQuotaControl.exposeCheckedUpdateClaimed -FNDA:0,TestableQuotaControl.exposeCheckedUpdateClaimed -DA:19,0 -FNF:2 -FNH:0 -LF:4 -LH:0 -BRF:0 -BRH:0 -end_of_record -TN: -SF:test/SwarmRegistryL1.t.sol -DA:13,60 -FN:13,MockBondTokenL1.mint -FNDA:60,MockBondTokenL1.mint -DA:14,60 -FNF:1 -FNH:1 -LF:2 -LH:2 -BRF:0 -BRH:0 -end_of_record -TN: -SF:test/SwarmRegistryUniversal.t.sol -DA:13,68 -FN:13,MockBondTokenUniv.mint -FNDA:68,MockBondTokenUniv.mint -DA:14,68 -FNF:1 -FNH:1 -LF:2 -LH:2 -BRF:0 -BRH:0 -end_of_record -TN: -SF:test/__helpers__/AccessControlUtils.sol -DA:9,0 -FN:9,AccessControlUtils.expectRevert_AccessControlUnauthorizedAccount -FNDA:0,AccessControlUtils.expectRevert_AccessControlUnauthorizedAccount -DA:10,0 -FNF:1 -FNH:0 -LF:2 -LH:0 -BRF:0 -BRH:0 -end_of_record -TN: -SF:test/bridge/L1Bridge.t.sol -DA:31,0 -FN:31,MockMailbox.setL1ToL2Failed -FNDA:0,MockMailbox.setL1ToL2Failed -DA:32,0 -DA:35,0 -FN:35,MockMailbox.setInclusion -FNDA:0,MockMailbox.setInclusion -DA:36,0 -DA:39,0 -FN:39,MockMailbox.setBaseCostReturn -FNDA:0,MockMailbox.setBaseCostReturn -DA:40,0 -DA:43,0 -FN:43,MockMailbox.expectBaseCostParams -FNDA:0,MockMailbox.expectBaseCostParams -DA:44,0 -DA:45,0 -DA:46,0 -DA:50,0 -FN:50,MockMailbox.requestL2Transaction -FNDA:0,MockMailbox.requestL2Transaction -DA:59,0 -DA:60,0 -DA:63,0 -DA:66,0 -FN:66,MockMailbox.proveL1ToL2TransactionStatus -FNDA:0,MockMailbox.proveL1ToL2TransactionStatus -DA:74,0 -BRDA:74,0,0,- -DA:75,0 -DA:77,0 -DA:80,0 -FN:80,MockMailbox.proveL2MessageInclusion -FNDA:0,MockMailbox.proveL2MessageInclusion -DA:86,0 -DA:89,0 -FN:89,MockMailbox.l2TransactionBaseCost -FNDA:0,MockMailbox.l2TransactionBaseCost -DA:95,0 -BRDA:95,1,0,- -BRDA:95,1,1,- -DA:96,0 -BRDA:96,2,0,- -BRDA:96,2,1,- -DA:97,0 -BRDA:97,3,0,- -BRDA:97,3,1,- -DA:98,0 -FNF:8 -FNH:0 -LF:25 -LH:0 -BRF:7 -BRH:0 -end_of_record -TN: -SF:test/bridge/MigrationNFT.t.sol -DA:12,0 -FN:12,MigrationNFTTestUtils.bridgeTokens -FNDA:0,MigrationNFTTestUtils.bridgeTokens -DA:20,0 -DA:21,0 -DA:22,0 -FNF:1 -FNH:0 -LF:4 -LH:0 -BRF:0 -BRH:0 -end_of_record -TN: -SF:test/contentsign/BaseContentSign.t.sol -DA:13,0 -FN:13,MockContentSign.setWhitelisted -FNDA:0,MockContentSign.setWhitelisted -DA:14,0 -DA:17,0 -FN:17,MockContentSign._userIsWhitelisted -FNDA:0,MockContentSign._userIsWhitelisted -DA:18,0 -FNF:2 -FNH:0 -LF:4 -LH:0 -BRF:0 -BRH:0 -end_of_record -TN: -SF:test/contentsign/ClickBounty.t.sol -DA:13,0 -FN:13,MockERC20.constructor -FNDA:0,MockERC20.constructor -DA:14,0 -DA:21,0 -FN:21,MockContentSign._userIsWhitelisted -FNDA:0,MockContentSign._userIsWhitelisted -DA:22,0 -FNF:2 -FNH:0 -LF:4 -LH:0 -BRF:0 -BRH:0 -end_of_record -TN: -SF:test/contentsign/PaymentMiddleware.t.sol -DA:16,0 -FN:16,MockToken.mint -FNDA:0,MockToken.mint -DA:17,0 -DA:24,0 -FN:24,MockWhitelist.mint -FNDA:0,MockWhitelist.mint -DA:25,0 -DA:32,0 -FN:32,MockContentSign._userIsWhitelisted -FNDA:0,MockContentSign._userIsWhitelisted -DA:33,0 -FNF:3 -FNH:0 -LF:6 -LH:0 -BRF:0 -BRH:0 -end_of_record -TN: -SF:test/paymasters/BasePaymaster.t.sol -DA:13,0 -FN:13,MockPaymaster._validateAndPayGeneralFlow -FNDA:0,MockPaymaster._validateAndPayGeneralFlow -DA:15,0 -DA:18,0 -FN:18,MockPaymaster._validateAndPayApprovalBasedFlow -FNDA:0,MockPaymaster._validateAndPayApprovalBasedFlow -DA:23,0 -FNF:2 -FNH:0 -LF:4 -LH:0 -BRF:0 -BRH:0 -end_of_record -TN: -SF:test/paymasters/WhitelistPaymaster.t.sol -DA:14,0 -FN:14,MockWhitelistPaymaster.mock_validateAndPayGeneralFlow -FNDA:0,MockWhitelistPaymaster.mock_validateAndPayGeneralFlow -DA:15,0 -DA:18,0 -FN:18,MockWhitelistPaymaster.mock_validateAndPayApprovalBasedFlow -FNDA:0,MockWhitelistPaymaster.mock_validateAndPayApprovalBasedFlow -DA:26,0 -FNF:2 -FNH:0 -LF:4 -LH:0 -BRF:0 -BRH:0 -end_of_record diff --git a/src/swarms/doc/README.md b/src/swarms/doc/README.md index ed40a8a0..5e73b805 100644 --- a/src/swarms/doc/README.md +++ b/src/swarms/doc/README.md @@ -25,7 +25,8 @@ graph TB FO -- "registerSwarm / update / delete" --> REG PRV -- "registerProvider(url)" --> SP PRV -- "acceptSwarm / rejectSwarm" --> REG - ANY -- "buildHighestBondedUuidBundle /
checkMembership / purge" --> REG + ANY -- "checkMembership / purge" --> REG + ANY -- "buildHighestBondedUuidBundle" --> FI REG -. "uuidOwner(fleetUuid)" .-> FI REG -. "ownerOf(providerId)" .-> SP @@ -40,12 +41,12 @@ graph TB ## Core Components -| Contract | Role | Identity | Token | -| :------------------------- | :----------------------------- | :------------------------------------------------- | :---- | -| **FleetIdentity** | Fleet registry (ERC-721) | `(regionKey << 128) \| uuid` | SFID | -| **ServiceProvider** | Backend URL registry (ERC-721) | `keccak256(url)` | SSV | -| **SwarmRegistryL1** | Tag group registry (L1) | `keccak256(fleetUuid, filter, fpSize, tagType)` | — | -| **SwarmRegistryUniversal** | Tag group registry (ZkSync+) | `keccak256(fleetUuid, filter, fpSize, tagType)` | — | +| Contract | Role | Identity | Token | +| :------------------------- | :----------------------------- | :---------------------------------------------- | :---- | +| **FleetIdentity** | Fleet registry (ERC-721) | `(regionKey << 128) \| uuid` | SFID | +| **ServiceProvider** | Backend URL registry (ERC-721) | `keccak256(url)` | SSV | +| **SwarmRegistryL1** | Tag group registry (L1) | `keccak256(fleetUuid, filter, fpSize, tagType)` | — | +| **SwarmRegistryUniversal** | Tag group registry (ZkSync+) | `keccak256(fleetUuid, filter, fpSize, tagType)` | — | All contracts are **permissionless**—access control via NFT ownership. FleetIdentity requires ERC-20 bond (anti-spam). diff --git a/src/swarms/doc/lifecycle.md b/src/swarms/doc/lifecycle.md index e7cc2a53..db45896d 100644 --- a/src/swarms/doc/lifecycle.md +++ b/src/swarms/doc/lifecycle.md @@ -29,16 +29,16 @@ stateDiagram-v2 ### State Transitions -| From | To | Function | Who Calls | Bond Effect | -| :------------ | :------ | :------------------------- | :-------- | :------------------------------------------------------------- | -| None | Owned | `claimUuid()` | Anyone | Pull BASE_BOND from caller (becomes owner) | -| None | Local | `registerFleetLocal()` | Anyone | Pull BASE_BOND + tierBond from caller (becomes owner+operator) | -| None | Country | `registerFleetCountry()` | Anyone | Pull BASE_BOND + tierBond from caller (becomes owner+operator) | -| Owned | Local | `registerFleetLocal()` | Operator | Pull tierBond from operator | -| Owned | Country | `registerFleetCountry()` | Operator | Pull tierBond from operator | -| Local/Country | Owned | `burn()` | Operator | Refund tierBond to operator (last token mints owned-only) | -| Owned | None | `burn()` | Owner | Refund BASE_BOND to owner | -| Local/Country | - | `burn()` | Operator | Refund tierBond to operator (not last token, stays registered) | +| From | To | Function | Who Calls | Bond Effect | +| :------------ | :------ | :----------------------- | :-------- | :------------------------------------------------------------- | +| None | Owned | `claimUuid()` | Anyone | Pull BASE_BOND from caller (becomes owner) | +| None | Local | `registerFleetLocal()` | Anyone | Pull BASE_BOND + tierBond from caller (becomes owner+operator) | +| None | Country | `registerFleetCountry()` | Anyone | Pull BASE_BOND + tierBond from caller (becomes owner+operator) | +| Owned | Local | `registerFleetLocal()` | Operator | Pull tierBond from operator | +| Owned | Country | `registerFleetCountry()` | Operator | Pull tierBond from operator | +| Local/Country | Owned | `burn()` | Operator | Refund tierBond to operator (last token mints owned-only) | +| Owned | None | `burn()` | Owner | Refund BASE_BOND to owner | +| Local/Country | - | `burn()` | Operator | Refund tierBond to operator (not last token, stays registered) | ## Swarm Status States @@ -103,7 +103,8 @@ sequenceDiagram Note over FI: Burn owned-only token (owner) FI->>TOKEN: transfer(owner, BASE_BOND) ``` -``` + +```` ## Orphan Lifecycle @@ -115,7 +116,7 @@ flowchart TD ORPHAN --> CHECK[isSwarmValid returns false] CHECK --> PURGE[Anyone: purgeOrphanedSwarm] PURGE --> DELETED[Swarm Deleted + Gas Refund] -``` +```` ### Orphan Guards From 37f0d097e2aef784aa9846b7704f61726c34ea7c Mon Sep 17 00:00:00 2001 From: Alex Sedighi Date: Fri, 6 Mar 2026 10:53:32 +1300 Subject: [PATCH 07/15] feat(swarms): implement UUPS upgradeable pattern for swarm contracts - Convert FleetIdentity, ServiceProvider, SwarmRegistryL1, SwarmRegistryUniversal to UUPS upgradeable pattern - Implement ERC1967Proxy for transparent proxy pattern with storage isolation - Add Ownable2Step for safer ownership transfers - Preserve all public APIs and storage layouts Deployment: - Add DeploySwarmUpgradeable.s.sol for initial deployment - Add UpgradeSwarm.s.sol helper script for contract upgrades - Support both SwarmRegistryL1 (L1 SSTORE2) and SwarmRegistryUniversal (L2) deployments Testing: - Migrate all existing tests to upgradeable versions - 12 ServiceProvider tests - 216 FleetIdentity tests - 13 FleetIdentityFairness tests - 69 SwarmRegistryUniversal tests - 60 SwarmRegistryL1 tests - Add test coverage: 95.74% FleetIdentity, 94.41% SwarmRegistryL1, 95.92% SwarmRegistryUniversal, 85% ServiceProvider - Add comprehensive upgrade demo (test/upgrade-demo/) showing - Convert FleetIdentity, ServiceProvider, SwarmRegistryL1, SwarmRegistryUniversal to UUPS upgradeable pattern - Impleost- Implement ERC1967Proxy for transparent proxy pattern with storage isolation - Add Ownable2Step for safer ost- Add Ownable2Step for safer ownership transfers - Preserve all public APIs .m- Preserve all public APIs and storage layouts t Deployment: - Add DeploySwarmUpgradeable.s.sn f- Add Depll - Add UpgradeSwarm.s.sol helper script for contract upgr r- Support both SwarmRegistryL1 (L1 SSTORE2) and SwarmRegistEI Testing: - Migrate all existing tests to upgradeable versions - 12 ServiceProvider Rem- Migran- - 12 ServiceProvider tests - 216 FleetIdentity erviceProvider.sol, etc.) - All - 13 FleetIdentityFairneea - 69 SwarmRegistryUniversal tes --- .gitmodules | 3 + anvil-zksync.log | 0 foundry.lock | 18 +- foundry.toml | 2 + lib/openzeppelin-contracts-upgradeable | 1 + remappings.txt | 3 +- script/DeploySwarmUpgradeable.s.sol | 120 +++ script/UpgradeSwarm.s.sol | 202 +++++ ...ntity.sol => FleetIdentityUpgradeable.sol} | 592 +++++-------- src/swarms/ServiceProvider.sol | 54 -- src/swarms/ServiceProviderUpgradeable.sol | 123 +++ src/swarms/SwarmRegistryL1.sol | 356 -------- src/swarms/SwarmRegistryL1Upgradeable.sol | 420 +++++++++ ... => SwarmRegistryUniversalUpgradeable.sol} | 221 +++-- src/swarms/doc/README.md | 19 +- src/swarms/doc/upgradeable-contracts.md | 806 ++++++++++++++++++ test/FleetIdentity.t.sol | 119 ++- test/FleetIdentityFairness.t.sol | 47 +- test/ServiceProvider.t.sol | 24 +- test/SwarmRegistryL1.t.sol | 303 ++++--- test/SwarmRegistryUniversal.t.sol | 327 +++---- test/__helpers__/MockERC20.sol | 22 + test/upgrade-demo/README.md | 189 ++++ test/upgrade-demo/TestUpgradeOnAnvil.s.sol | 248 ++++++ test/upgradeable/UpgradeableContracts.t.sol | 440 ++++++++++ 25 files changed, 3423 insertions(+), 1236 deletions(-) create mode 100644 anvil-zksync.log create mode 160000 lib/openzeppelin-contracts-upgradeable create mode 100644 script/DeploySwarmUpgradeable.s.sol create mode 100644 script/UpgradeSwarm.s.sol rename src/swarms/{FleetIdentity.sol => FleetIdentityUpgradeable.sol} (63%) delete mode 100644 src/swarms/ServiceProvider.sol create mode 100644 src/swarms/ServiceProviderUpgradeable.sol delete mode 100644 src/swarms/SwarmRegistryL1.sol create mode 100644 src/swarms/SwarmRegistryL1Upgradeable.sol rename src/swarms/{SwarmRegistryUniversal.sol => SwarmRegistryUniversalUpgradeable.sol} (53%) create mode 100644 src/swarms/doc/upgradeable-contracts.md create mode 100644 test/__helpers__/MockERC20.sol create mode 100644 test/upgrade-demo/README.md create mode 100644 test/upgrade-demo/TestUpgradeOnAnvil.s.sol create mode 100644 test/upgradeable/UpgradeableContracts.t.sol diff --git a/.gitmodules b/.gitmodules index c6c1a45d..9717df7c 100644 --- a/.gitmodules +++ b/.gitmodules @@ -13,3 +13,6 @@ [submodule "lib/solady"] path = lib/solady url = https://github.com/vectorized/solady +[submodule "lib/openzeppelin-contracts-upgradeable"] + path = lib/openzeppelin-contracts-upgradeable + url = https://github.com/OpenZeppelin/openzeppelin-contracts-upgradeable diff --git a/anvil-zksync.log b/anvil-zksync.log new file mode 100644 index 00000000..e69de29b diff --git a/foundry.lock b/foundry.lock index 7a3effd8..599c1c01 100644 --- a/foundry.lock +++ b/foundry.lock @@ -1,20 +1,26 @@ { + "lib/forge-std": { + "rev": "1eea5bae12ae557d589f9f0f0edae2faa47cb262" + }, "lib/zksync-storage-proofs": { "rev": "4b20401ce44c1ec966a29d893694f65db885304b" }, - "lib/openzeppelin-contracts": { - "rev": "e4f70216d759d8e6a64144a9e1f7bbeed78e7079" - }, "lib/solady": { "tag": { "name": "v0.1.26", "rev": "acd959aa4bd04720d640bf4e6a5c71037510cc4b" } }, - "lib/forge-std": { - "rev": "1eea5bae12ae557d589f9f0f0edae2faa47cb262" - }, "lib/era-contracts": { "rev": "84d5e3716f645909e8144c7d50af9dd6dd9ded62" + }, + "lib/openzeppelin-contracts-upgradeable": { + "tag": { + "name": "v5.6.1", + "rev": "7bf4727aacdbfaa0f36cbd664654d0c9e1dc52bf" + } + }, + "lib/openzeppelin-contracts": { + "rev": "e4f70216d759d8e6a64144a9e1f7bbeed78e7079" } } \ No newline at end of file diff --git a/foundry.toml b/foundry.toml index 44161471..41b53dc8 100644 --- a/foundry.toml +++ b/foundry.toml @@ -8,6 +8,8 @@ solc = "0.8.26" # necessary as some of the zksync contracts are big via_ir = true +optimizer = true +optimizer_runs = 200 [lint] # Exclude ERC20 transfer warning - false positive for ERC721.transferFrom in tests diff --git a/lib/openzeppelin-contracts-upgradeable b/lib/openzeppelin-contracts-upgradeable new file mode 160000 index 00000000..7bf4727a --- /dev/null +++ b/lib/openzeppelin-contracts-upgradeable @@ -0,0 +1 @@ +Subproject commit 7bf4727aacdbfaa0f36cbd664654d0c9e1dc52bf diff --git a/remappings.txt b/remappings.txt index 53468b38..5d942fdf 100644 --- a/remappings.txt +++ b/remappings.txt @@ -1,2 +1,3 @@ -@openzeppelin=lib/openzeppelin-contracts/ +@openzeppelin/contracts/=lib/openzeppelin-contracts/contracts/ +@openzeppelin/contracts-upgradeable/=lib/openzeppelin-contracts-upgradeable/contracts/ solady/=lib/solady/src/ \ No newline at end of file diff --git a/script/DeploySwarmUpgradeable.s.sol b/script/DeploySwarmUpgradeable.s.sol new file mode 100644 index 00000000..558f67fa --- /dev/null +++ b/script/DeploySwarmUpgradeable.s.sol @@ -0,0 +1,120 @@ +// SPDX-License-Identifier: BSD-3-Clause-Clear + +pragma solidity ^0.8.24; + +import {Script, console} from "forge-std/Script.sol"; +import {ERC1967Proxy} from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol"; + +import {ServiceProviderUpgradeable} from "../src/swarms/ServiceProviderUpgradeable.sol"; +import {FleetIdentityUpgradeable} from "../src/swarms/FleetIdentityUpgradeable.sol"; +import {SwarmRegistryUniversalUpgradeable} from "../src/swarms/SwarmRegistryUniversalUpgradeable.sol"; +import {SwarmRegistryL1Upgradeable} from "../src/swarms/SwarmRegistryL1Upgradeable.sol"; + +/** + * @title DeploySwarmUpgradeable + * @notice Deployment script for the upgradeable swarm contracts. + * + * @dev Deploy order matters due to dependencies: + * 1. ServiceProviderUpgradeable (no dependencies) + * 2. FleetIdentityUpgradeable (depends on bond token) + * 3. SwarmRegistryUniversalUpgradeable or SwarmRegistryL1Upgradeable (depends on 1 & 2) + * + * Usage: + * # Dry run (simulation) + * forge script script/DeploySwarmUpgradeable.s.sol --rpc-url $RPC_URL + * + * # Deploy with broadcast + * forge script script/DeploySwarmUpgradeable.s.sol --rpc-url $RPC_URL --broadcast --verify + * + * # For L1 deployment (with SwarmRegistryL1): + * DEPLOY_L1_REGISTRY=true forge script script/DeploySwarmUpgradeable.s.sol --rpc-url $RPC_URL --broadcast + * + * Environment Variables: + * - DEPLOYER_PRIVATE_KEY: Private key for deployment + * - BOND_TOKEN: Address of the ERC20 bond token + * - BASE_BOND: Base bond amount in wei + * - OWNER: Owner address for upgrade authorization (defaults to deployer) + * - DEPLOY_L1_REGISTRY: Set to "true" to deploy SwarmRegistryL1 instead of Universal + */ +contract DeploySwarmUpgradeable is Script { + // Deployment artifacts + address public serviceProviderProxy; + address public serviceProviderImpl; + address public fleetIdentityProxy; + address public fleetIdentityImpl; + address public swarmRegistryProxy; + address public swarmRegistryImpl; + + function run() external { + // Load environment variables + uint256 deployerPrivateKey = vm.envUint("DEPLOYER_PRIVATE_KEY"); + address bondToken = vm.envAddress("BOND_TOKEN"); + uint256 baseBond = vm.envUint("BASE_BOND"); + address owner = vm.envOr("OWNER", vm.addr(deployerPrivateKey)); + bool deployL1Registry = vm.envOr("DEPLOY_L1_REGISTRY", false); + + console.log("=== Deploying Upgradeable Swarm Contracts ==="); + console.log("Bond Token:", bondToken); + console.log("Base Bond:", baseBond); + console.log("Owner:", owner); + console.log("Registry Type:", deployL1Registry ? "L1 (SSTORE2)" : "Universal"); + console.log(""); + + vm.startBroadcast(deployerPrivateKey); + + // 1. Deploy ServiceProviderUpgradeable + console.log("1. Deploying ServiceProviderUpgradeable..."); + serviceProviderImpl = address(new ServiceProviderUpgradeable()); + console.log(" Implementation:", serviceProviderImpl); + + bytes memory serviceProviderInitData = + abi.encodeWithSelector(ServiceProviderUpgradeable.initialize.selector, owner); + serviceProviderProxy = address(new ERC1967Proxy(serviceProviderImpl, serviceProviderInitData)); + console.log(" Proxy:", serviceProviderProxy); + console.log(""); + + // 2. Deploy FleetIdentityUpgradeable + console.log("2. Deploying FleetIdentityUpgradeable..."); + fleetIdentityImpl = address(new FleetIdentityUpgradeable()); + console.log(" Implementation:", fleetIdentityImpl); + + bytes memory fleetIdentityInitData = + abi.encodeWithSelector(FleetIdentityUpgradeable.initialize.selector, bondToken, baseBond, owner); + fleetIdentityProxy = address(new ERC1967Proxy(fleetIdentityImpl, fleetIdentityInitData)); + console.log(" Proxy:", fleetIdentityProxy); + console.log(""); + + // 3. Deploy SwarmRegistry (L1 or Universal) + if (deployL1Registry) { + console.log("3. Deploying SwarmRegistryL1Upgradeable..."); + swarmRegistryImpl = address(new SwarmRegistryL1Upgradeable()); + console.log(" Implementation:", swarmRegistryImpl); + + bytes memory swarmRegistryInitData = abi.encodeWithSelector( + SwarmRegistryL1Upgradeable.initialize.selector, fleetIdentityProxy, serviceProviderProxy, owner + ); + swarmRegistryProxy = address(new ERC1967Proxy(swarmRegistryImpl, swarmRegistryInitData)); + } else { + console.log("3. Deploying SwarmRegistryUniversalUpgradeable..."); + swarmRegistryImpl = address(new SwarmRegistryUniversalUpgradeable()); + console.log(" Implementation:", swarmRegistryImpl); + + bytes memory swarmRegistryInitData = abi.encodeWithSelector( + SwarmRegistryUniversalUpgradeable.initialize.selector, fleetIdentityProxy, serviceProviderProxy, owner + ); + swarmRegistryProxy = address(new ERC1967Proxy(swarmRegistryImpl, swarmRegistryInitData)); + } + console.log(" Proxy:", swarmRegistryProxy); + + vm.stopBroadcast(); + + // Summary + console.log(""); + console.log("=== Deployment Complete ==="); + console.log("ServiceProvider Proxy:", serviceProviderProxy); + console.log("FleetIdentity Proxy:", fleetIdentityProxy); + console.log("SwarmRegistry Proxy:", swarmRegistryProxy); + console.log(""); + console.log("Save these proxy addresses for future upgrades!"); + } +} diff --git a/script/UpgradeSwarm.s.sol b/script/UpgradeSwarm.s.sol new file mode 100644 index 00000000..ded3a64e --- /dev/null +++ b/script/UpgradeSwarm.s.sol @@ -0,0 +1,202 @@ +// SPDX-License-Identifier: BSD-3-Clause-Clear + +pragma solidity ^0.8.24; + +import {Script, console} from "forge-std/Script.sol"; +import {UUPSUpgradeable} from "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol"; + +import {ServiceProviderUpgradeable} from "../src/swarms/ServiceProviderUpgradeable.sol"; +import {FleetIdentityUpgradeable} from "../src/swarms/FleetIdentityUpgradeable.sol"; +import {SwarmRegistryUniversalUpgradeable} from "../src/swarms/SwarmRegistryUniversalUpgradeable.sol"; +import {SwarmRegistryL1Upgradeable} from "../src/swarms/SwarmRegistryL1Upgradeable.sol"; + +/** + * @title UpgradeSwarm + * @notice Script for upgrading deployed swarm contracts to new implementations. + * + * @dev **Storage Migration Rules:** + * 1. Never remove or reorder existing storage variables + * 2. Only append new variables at the end (reduce __gap accordingly) + * 3. Never change variable types + * 4. Use `reinitializer(n)` for version-specific initialization + * + * **Pre-Upgrade Checklist:** + * 1. Run `forge inspect NewContract storageLayout` and compare to V1 + * 2. Ensure all tests pass with new implementation + * 3. Test upgrade on fork: `forge script ... --fork-url $RPC_URL` + * 4. Verify new implementation on block explorer + * + * Usage: + * # Upgrade ServiceProvider + * CONTRACT_TYPE=ServiceProvider PROXY_ADDRESS=0x... \ + * forge script script/UpgradeSwarm.s.sol --rpc-url $RPC_URL --broadcast + * + * # Upgrade FleetIdentity + * CONTRACT_TYPE=FleetIdentity PROXY_ADDRESS=0x... \ + * forge script script/UpgradeSwarm.s.sol --rpc-url $RPC_URL --broadcast + * + * # Upgrade SwarmRegistryUniversal + * CONTRACT_TYPE=SwarmRegistryUniversal PROXY_ADDRESS=0x... \ + * forge script script/UpgradeSwarm.s.sol --rpc-url $RPC_URL --broadcast + * + * # Upgrade SwarmRegistryL1 + * CONTRACT_TYPE=SwarmRegistryL1 PROXY_ADDRESS=0x... \ + * forge script script/UpgradeSwarm.s.sol --rpc-url $RPC_URL --broadcast + * + * Environment Variables: + * - DEPLOYER_PRIVATE_KEY: Private key of contract owner + * - PROXY_ADDRESS: Address of the proxy to upgrade + * - CONTRACT_TYPE: One of "ServiceProvider", "FleetIdentity", "SwarmRegistryUniversal", "SwarmRegistryL1" + * - REINIT_DATA: Optional ABI-encoded data for reinitializer call (e.g., for V2 migration) + */ +contract UpgradeSwarm is Script { + function run() external { + uint256 deployerPrivateKey = vm.envUint("DEPLOYER_PRIVATE_KEY"); + address proxyAddress = vm.envAddress("PROXY_ADDRESS"); + string memory contractType = vm.envString("CONTRACT_TYPE"); + bytes memory reinitData = vm.envOr("REINIT_DATA", bytes("")); + + console.log("=== Upgrading Swarm Contract ==="); + console.log("Contract Type:", contractType); + console.log("Proxy Address:", proxyAddress); + console.log("Has Reinit Data:", reinitData.length > 0); + console.log(""); + + vm.startBroadcast(deployerPrivateKey); + + address newImpl; + + if (keccak256(bytes(contractType)) == keccak256("ServiceProvider")) { + newImpl = _upgradeServiceProvider(proxyAddress, reinitData); + } else if (keccak256(bytes(contractType)) == keccak256("FleetIdentity")) { + newImpl = _upgradeFleetIdentity(proxyAddress, reinitData); + } else if (keccak256(bytes(contractType)) == keccak256("SwarmRegistryUniversal")) { + newImpl = _upgradeSwarmRegistryUniversal(proxyAddress, reinitData); + } else if (keccak256(bytes(contractType)) == keccak256("SwarmRegistryL1")) { + newImpl = _upgradeSwarmRegistryL1(proxyAddress, reinitData); + } else { + revert("Invalid CONTRACT_TYPE. Use: ServiceProvider, FleetIdentity, SwarmRegistryUniversal, SwarmRegistryL1"); + } + + vm.stopBroadcast(); + + console.log(""); + console.log("=== Upgrade Complete ==="); + console.log("New Implementation:", newImpl); + console.log("Proxy (unchanged):", proxyAddress); + } + + function _upgradeServiceProvider(address proxy, bytes memory reinitData) internal returns (address impl) { + console.log("Deploying new ServiceProviderUpgradeable implementation..."); + impl = address(new ServiceProviderUpgradeable()); + console.log("New implementation:", impl); + + ServiceProviderUpgradeable proxyContract = ServiceProviderUpgradeable(proxy); + + if (reinitData.length > 0) { + console.log("Calling upgradeToAndCall with reinitializer..."); + proxyContract.upgradeToAndCall(impl, reinitData); + } else { + console.log("Calling upgradeTo..."); + proxyContract.upgradeToAndCall(impl, ""); + } + } + + function _upgradeFleetIdentity(address proxy, bytes memory reinitData) internal returns (address impl) { + console.log("Deploying new FleetIdentityUpgradeable implementation..."); + impl = address(new FleetIdentityUpgradeable()); + console.log("New implementation:", impl); + + FleetIdentityUpgradeable proxyContract = FleetIdentityUpgradeable(proxy); + + if (reinitData.length > 0) { + console.log("Calling upgradeToAndCall with reinitializer..."); + proxyContract.upgradeToAndCall(impl, reinitData); + } else { + console.log("Calling upgradeTo..."); + proxyContract.upgradeToAndCall(impl, ""); + } + } + + function _upgradeSwarmRegistryUniversal(address proxy, bytes memory reinitData) internal returns (address impl) { + console.log("Deploying new SwarmRegistryUniversalUpgradeable implementation..."); + impl = address(new SwarmRegistryUniversalUpgradeable()); + console.log("New implementation:", impl); + + SwarmRegistryUniversalUpgradeable proxyContract = SwarmRegistryUniversalUpgradeable(proxy); + + if (reinitData.length > 0) { + console.log("Calling upgradeToAndCall with reinitializer..."); + proxyContract.upgradeToAndCall(impl, reinitData); + } else { + console.log("Calling upgradeTo..."); + proxyContract.upgradeToAndCall(impl, ""); + } + } + + function _upgradeSwarmRegistryL1(address proxy, bytes memory reinitData) internal returns (address impl) { + console.log("Deploying new SwarmRegistryL1Upgradeable implementation..."); + impl = address(new SwarmRegistryL1Upgradeable()); + console.log("New implementation:", impl); + + SwarmRegistryL1Upgradeable proxyContract = SwarmRegistryL1Upgradeable(proxy); + + if (reinitData.length > 0) { + console.log("Calling upgradeToAndCall with reinitializer..."); + proxyContract.upgradeToAndCall(impl, reinitData); + } else { + console.log("Calling upgradeTo..."); + proxyContract.upgradeToAndCall(impl, ""); + } + } +} + +/** + * @title ExampleV2Migration + * @notice Example of how to add a V2 reinitializer function and migrate storage. + * + * @dev When you need to add new storage to an existing contract: + * + * 1. Add new storage variables ABOVE the __gap (reduce gap size accordingly) + * 2. Add a reinitializer function: + * + * ```solidity + * function initializeV2(uint256 newParam) external reinitializer(2) { + * _newParamIntroducedInV2 = newParam; + * } + * ``` + * + * 3. Generate reinit calldata: + * ```bash + * cast calldata "initializeV2(uint256)" 12345 + * ``` + * + * 4. Pass to upgrade script: + * ```bash + * REINIT_DATA=0x... forge script ... + * ``` + * + * Example V2 contract structure (do not deploy this, it's documentation): + * + * contract ServiceProviderUpgradeableV2 is ServiceProviderUpgradeable { + * // New V2 storage - add ABOVE __gap + * mapping(uint256 => uint256) public providerScores; + * + * // Reduce __gap from 49 to 48 (added 1 slot) + * uint256[48] private __gap; + * + * // V2 reinitializer + * function initializeV2() external reinitializer(2) { + * // Initialize any V2-specific state here + * } + * + * // New V2 functions + * function setProviderScore(uint256 tokenId, uint256 score) external { + * if (ownerOf(tokenId) != msg.sender) revert NotTokenOwner(); + * providerScores[tokenId] = score; + * } + * } + */ +contract ExampleV2Migration { + // This contract is documentation only +} diff --git a/src/swarms/FleetIdentity.sol b/src/swarms/FleetIdentityUpgradeable.sol similarity index 63% rename from src/swarms/FleetIdentity.sol rename to src/swarms/FleetIdentityUpgradeable.sol index dcbf3bdb..c9a2a640 100644 --- a/src/swarms/FleetIdentity.sol +++ b/src/swarms/FleetIdentityUpgradeable.sol @@ -2,18 +2,33 @@ pragma solidity ^0.8.24; -import {ERC721} from "@openzeppelin/contracts/token/ERC721/ERC721.sol"; -import {ERC721Enumerable} from "@openzeppelin/contracts/token/ERC721/extensions/ERC721Enumerable.sol"; +import {ERC721EnumerableUpgradeable} from + "@openzeppelin/contracts-upgradeable/token/ERC721/extensions/ERC721EnumerableUpgradeable.sol"; +import {Ownable2StepUpgradeable} from "@openzeppelin/contracts-upgradeable/access/Ownable2StepUpgradeable.sol"; +import {UUPSUpgradeable} from "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol"; +import {Initializable} from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol"; import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; -import {ReentrancyGuard} from "@openzeppelin/contracts/utils/ReentrancyGuard.sol"; +import {ReentrancyGuard} from "solady/utils/ReentrancyGuard.sol"; /** - * @title FleetIdentity - * @notice ERC-721 with ERC721Enumerable representing ownership of a BLE fleet, + * @title FleetIdentityUpgradeable + * @notice UUPS-upgradeable ERC-721 with ERC721Enumerable representing ownership of a BLE fleet, * secured by an ERC-20 bond organized into geometric tiers. * - * @dev **Two-level geographic registration** + * @dev **Upgrade Pattern:** + * - Uses OpenZeppelin UUPS proxy pattern for upgradeability. + * - Only the contract owner can authorize upgrades. + * - Storage layout must be preserved across upgrades (append-only). + * + * **Storage Migration Example (V1 → V2):** + * ```solidity + * function initializeV2(uint256 newParam) external reinitializer(2) { + * _newParamIntroducedInV2 = newParam; + * } + * ``` + * + * **Two-level geographic registration** * * Fleets register at exactly one level: * - Country — regionKey = countryCode (ISO 3166-1 numeric, 1-999) @@ -29,31 +44,19 @@ import {ReentrancyGuard} from "@openzeppelin/contracts/utils/ReentrancyGuard.sol * - Local bond: BASE_BOND * 2^tier * - Country bond: BASE_BOND * COUNTRY_BOND_MULTIPLIER * 2^tier (16× local) * - * Country fleets pay 16× more but appear in all admin-area bundles within - * their country. This economic difference provides locals a significant - * advantage: a local can reach tier 4 for the same cost a country player - * pays for tier 0. Bundle slots are filled by simple tier-descent priority: - * higher tier first, locals before country within each tier. - * - * EdgeBeaconScanner discovery uses 2-level fallback: - * 1. Admin area (highest priority) - * 2. Country (lower priority) - * - * On-chain indexes track which countries and admin areas have active fleets, - * enabling EdgeBeaconScanner enumeration without off-chain indexers. - * * **TokenID Encoding** * * TokenID = (regionKey << 128) | uuid * - Bits 0-127: UUID (bytes16 Proximity UUID) * - Bits 128-159: Region key (32-bit country or admin-area code) - * - * This allows the same UUID to be registered in multiple regions, - * each with a distinct token. Region and UUID can be extracted: - * - uuid = bytes16(uint128(tokenId)) - * - region = uint32(tokenId >> 128) */ -contract FleetIdentity is ERC721Enumerable, ReentrancyGuard { +contract FleetIdentityUpgradeable is + Initializable, + ERC721EnumerableUpgradeable, + Ownable2StepUpgradeable, + UUPSUpgradeable, + ReentrancyGuard +{ using SafeERC20 for IERC20; // ────────────────────────────────────────────── @@ -82,14 +85,14 @@ contract FleetIdentity is ERC721Enumerable, ReentrancyGuard { /// @notice Registration level for a UUID. enum RegistrationLevel { - None, // 0 - not registered (default) - Owned, // 1 - owned but not registered in any region - Local, // 2 - admin area (local) level - Country // 3 - country level + None, // 0 - not registered (default) + Owned, // 1 - owned but not registered in any region + Local, // 2 - admin area (local) level + Country // 3 - country level } // ────────────────────────────────────────────── - // Constants & Immutables + // Constants // ────────────────────────────────────────────── /// @notice Unified tier capacity for all levels. @@ -99,10 +102,6 @@ contract FleetIdentity is ERC721Enumerable, ReentrancyGuard { uint256 public constant COUNTRY_BOND_MULTIPLIER = 16; /// @notice Hard cap on tier count per region. - /// @dev Derived from anti-spam analysis: with a bond doubling per tier - /// and capacity 4, a spammer spending half the total token supply - /// against a BASE_BOND set 10 000× too low fills ~20 tiers. - /// 24 provides comfortable headroom. uint256 public constant MAX_TIERS = 24; /// @notice Maximum UUIDs returned by buildHighestBondedUuidBundle. @@ -112,8 +111,6 @@ contract FleetIdentity is ERC721Enumerable, ReentrancyGuard { uint16 internal constant MAX_COUNTRY_CODE = 999; /// @notice Upper bound for admin-area codes within a country. - /// @dev Set to 255 to cover all real-world countries (UK has ~172, the highest). - /// Dense indices from ISO 3166-2 mappings range 0-254, stored as adminCode 1-255. uint16 internal constant MAX_ADMIN_CODE = 255; /// @dev Bit shift for packing countryCode into an admin-area region key. @@ -124,11 +121,17 @@ contract FleetIdentity is ERC721Enumerable, ReentrancyGuard { /// @notice Region key for owned-only UUIDs (not registered in any region). uint32 public constant OWNED_REGION_KEY = 0; - /// @notice The ERC-20 token used for bonds (immutable, e.g. NODL). - IERC20 public immutable BOND_TOKEN; + // ────────────────────────────────────────────── + // Storage (V1) - Order matters for upgrades! + // ────────────────────────────────────────────── + + /// @notice The ERC-20 token used for bonds (e.g. NODL). + /// @dev In non-upgradeable version this was immutable. Now stored in proxy storage. + IERC20 private _bondToken; /// @notice Base bond for tier 0 in any region. Tier K requires BASE_BOND * 2^K. - uint256 public immutable BASE_BOND; + /// @dev In non-upgradeable version this was immutable. Now stored in proxy storage. + uint256 private _baseBond; // ────────────────────────────────────────────── // Region-namespaced tier data @@ -155,40 +158,40 @@ contract FleetIdentity is ERC721Enumerable, ReentrancyGuard { // ────────────────────────────────────────────── /// @notice UUID -> address that first registered a token for this UUID. - /// All subsequent registrations for the same UUID must come from this address. mapping(bytes16 => address) public uuidOwner; /// @notice UUID -> count of active tokens for this UUID (across all regions). - /// When this reaches 0, uuidOwner is cleared. mapping(bytes16 => uint256) public uuidTokenCount; /// @notice UUID -> registration level. - /// All tokens for a UUID must be at the same level. mapping(bytes16 => RegistrationLevel) public uuidLevel; /// @notice UUID -> operator address for tier maintenance. - /// If address(0), the uuidOwner acts as operator. - /// Operator can only be set for registered UUIDs (Local or Country level). - /// The operator pays/receives tier bond differentials; owner pays BASE_BOND. mapping(bytes16 => address) public uuidOperator; /// @notice UUID -> total tier bonds across all registered regions. - /// Tracked incrementally to allow O(1) lookup for setOperator. - /// Updated on registration, burn, promote, and demote. mapping(bytes16 => uint256) public uuidTotalTierBonds; // ────────────────────────────────────────────── // On-chain region indexes // ────────────────────────────────────────────── - /// @dev Set of country codes with at least one active fleet (country-level or admin-area). + /// @dev Set of country codes with at least one active fleet. uint16[] internal _activeCountries; - mapping(uint16 => uint256) internal _activeCountryIndex; // value = index+1 (0 = not present) + mapping(uint16 => uint256) internal _activeCountryIndex; /// @dev Country → list of admin-area region keys with at least one active fleet. - /// Enables bounded iteration in countryInclusionHint (max 255 per country). mapping(uint16 => uint32[]) internal _countryAdminAreas; - mapping(uint32 => uint256) internal _countryAdminAreaIndex; // adminKey → index+1 in country's array + mapping(uint32 => uint256) internal _countryAdminAreaIndex; + + // ────────────────────────────────────────────── + // Storage Gap (for future upgrades) + // ────────────────────────────────────────────── + + /// @dev Reserved storage slots for future upgrades. + /// When adding new storage in V2+, reduce this gap accordingly. + // solhint-disable-next-line var-name-mixedcase + uint256[40] private __gap; // ────────────────────────────────────────────── // Events @@ -204,10 +207,7 @@ contract FleetIdentity is ERC721Enumerable, ReentrancyGuard { address operator ); event OperatorSet( - bytes16 indexed uuid, - address indexed oldOperator, - address indexed newOperator, - uint256 tierExcessTransferred + bytes16 indexed uuid, address indexed oldOperator, address indexed newOperator, uint256 tierExcessTransferred ); event FleetPromoted( uint256 indexed tokenId, uint256 indexed fromTier, uint256 indexed toTier, uint256 additionalBond @@ -219,27 +219,51 @@ contract FleetIdentity is ERC721Enumerable, ReentrancyGuard { event UuidClaimed(address indexed owner, bytes16 indexed uuid, address indexed operator); // ────────────────────────────────────────────── - // Constructor + // Constructor (disables initializers on implementation) // ────────────────────────────────────────────── - /// @param _bondToken Address of the ERC-20 token used for bonds. - /// @param _baseBond Base bond for tier 0 in any region. - constructor(address _bondToken, uint256 _baseBond) ERC721("Swarm Fleet Identity", "SFID") { - BOND_TOKEN = IERC20(_bondToken); - BASE_BOND = _baseBond; + /// @custom:oz-upgrades-unsafe-allow constructor + constructor() { + _disableInitializers(); + } + + // ────────────────────────────────────────────── + // Initializer (replaces constructor) + // ────────────────────────────────────────────── + + /// @notice Initializes the contract. Must be called once via proxy. + /// @param bondToken_ Address of the ERC-20 token used for bonds. + /// @param baseBond_ Base bond for tier 0 in any region. + /// @param owner_ The address that will own this contract and can authorize upgrades. + function initialize(address bondToken_, uint256 baseBond_, address owner_) external initializer { + __ERC721_init("Swarm Fleet Identity", "SFID"); + __ERC721Enumerable_init(); + __Ownable_init(owner_); + __Ownable2Step_init(); + + _bondToken = IERC20(bondToken_); + _baseBond = baseBond_; + } + + // ────────────────────────────────────────────── + // Public Getters for former immutables + // ────────────────────────────────────────────── + + /// @notice Returns the bond token address. + function BOND_TOKEN() external view returns (IERC20) { + return _bondToken; + } + + /// @notice Returns the base bond amount. + function BASE_BOND() external view returns (uint256) { + return _baseBond; } // ══════════════════════════════════════════════ - // Registration: Country (operator-only with tier) + // Registration: Country (operator-only with tier) // ══════════════════════════════════════════════ /// @notice Register a fleet under a country at a specific tier. - /// @dev Only callable by the operator (or by caller for fresh UUIDs, who becomes owner+operator). - /// For owned UUIDs: operator pays tier bond, token mints to owner. - /// For fresh UUIDs: caller becomes owner+operator, pays BASE_BOND + tier bond. - /// @param uuid The Proximity UUID to register. - /// @param countryCode ISO 3166-1 numeric country code (1-999). - /// @param targetTier The tier to register at (use countryInclusionHint for guidance). function registerFleetCountry(bytes16 uuid, uint16 countryCode, uint256 targetTier) external nonReentrant @@ -257,13 +281,6 @@ contract FleetIdentity is ERC721Enumerable, ReentrancyGuard { // ══════════════════════════════════════════════ /// @notice Register a fleet under a country + admin area at a specific tier. - /// @dev Only callable by the operator (or by caller for fresh UUIDs, who becomes owner+operator). - /// For owned UUIDs: operator pays tier bond, token mints to owner. - /// For fresh UUIDs: caller becomes owner+operator, pays BASE_BOND + tier bond. - /// @param uuid The Proximity UUID to register. - /// @param countryCode ISO 3166-1 numeric country code (1-999). - /// @param adminCode Admin area code within the country (1-255). - /// @param targetTier The tier to register at (use localInclusionHint for guidance). function registerFleetLocal(bytes16 uuid, uint16 countryCode, uint16 adminCode, uint256 targetTier) external nonReentrant @@ -282,15 +299,11 @@ contract FleetIdentity is ERC721Enumerable, ReentrancyGuard { // ══════════════════════════════════════════════ /// @notice Promotes a fleet to the next tier within its region. - /// Only callable by the effective operator (or owner if no operator set). function promote(uint256 tokenId) external nonReentrant { _promote(tokenId, fleetTier[tokenId] + 1); } /// @notice Moves a fleet to a different tier within its region. - /// If targetTier > current tier, promotes (pulls additional bond from operator). - /// If targetTier < current tier, demotes (refunds bond difference to operator). - /// Only callable by the effective operator (or owner if no operator set). function reassignTier(uint256 tokenId, uint256 targetTier) external nonReentrant { uint256 currentTier = fleetTier[tokenId]; if (targetTier == currentTier) revert TargetTierSameAsCurrent(); @@ -306,42 +319,25 @@ contract FleetIdentity is ERC721Enumerable, ReentrancyGuard { // ══════════════════════════════════════════════ /// @notice Sets or changes the operator for a UUID. - /// The operator is responsible for tier maintenance (promote/demote) and registration. - /// When changing operators, the new operator must pay the old operator - /// for all accumulated tier bonds across all registered regions. - /// @dev Only the UUID owner can call this. Can be called for any owned UUID (including owned-only). - /// Setting operator to owner address or address(0) clears the explicit operator. - /// @param uuid The UUID to set the operator for. - /// @param newOperator The new operator address. Use address(0) or owner to clear. function setOperator(bytes16 uuid, address newOperator) external nonReentrant { - // Only owner can set operator if (uuidOwner[uuid] != msg.sender) revert NotUuidOwner(); - - // Cannot set operator for unowned UUIDs if (uuidLevel[uuid] == RegistrationLevel.None) { revert UuidNotOwned(); } - + address oldOperator = operatorOf(uuid); - - // Normalize: if newOperator is owner, store as address(0) address storedOperator = (newOperator == msg.sender) ? address(0) : newOperator; address effectiveNewOperator = (storedOperator == address(0)) ? msg.sender : storedOperator; - - // O(1) lookup of total tier bonds from storage + uint256 tierBonds = uuidTotalTierBonds[uuid]; - - // Effects: Update operator + uuidOperator[uuid] = storedOperator; - - // Interactions: Transfer tier bonds from new operator to old operator + if (tierBonds > 0 && oldOperator != effectiveNewOperator) { - // Pull from new operator _pullBond(effectiveNewOperator, tierBonds); - // Refund to old operator _refundBond(oldOperator, tierBonds); } - + emit OperatorSet(uuid, oldOperator, effectiveNewOperator, tierBonds); } @@ -350,65 +346,44 @@ contract FleetIdentity is ERC721Enumerable, ReentrancyGuard { // ══════════════════════════════════════════════ /// @notice Burns the fleet NFT and refunds the bond. - /// - /// **Owned-only tokens (region=0):** - /// - Only the token owner can burn. - /// - Refunds BASE_BOND to owner. Clears UUID ownership completely. - /// - /// **Registered tokens (region>0):** - /// - Owner OR operator can burn. - /// - Refunds tier bond to operator. - /// - If this is the LAST registered token for the UUID: - /// transitions to owned-only mode (mints owned-only token to owner). - /// - If other tokens remain: just decrements count. - /// - /// This function subsumes the former `unregisterToOwned` (operator burns - /// last registered token) and `releaseUuid` (owner burns owned-only token). function burn(uint256 tokenId) external nonReentrant { address tokenHolder = ownerOf(tokenId); uint32 region = tokenRegion(tokenId); bytes16 uuid = tokenUuid(tokenId); - address owner = uuidOwner[uuid]; + address owner_ = uuidOwner[uuid]; address operator = operatorOf(uuid); bool isLastToken = uuidTokenCount[uuid] == 1; if (region == OWNED_REGION_KEY) { - // Owned-only token: only owner can burn if (tokenHolder != msg.sender) revert NotTokenOwner(); - + _burn(tokenId); _clearUuidOwnership(uuid); - _refundBond(owner, BASE_BOND); + _refundBond(owner_, _baseBond); - emit FleetBurned(tokenHolder, tokenId, region, 0, BASE_BOND); + emit FleetBurned(tokenHolder, tokenId, region, 0, _baseBond); } else { - // Registered fleet: only operator can burn if (msg.sender != operator) { revert NotOperator(); } uint256 tier = fleetTier[tokenId]; uint256 tierBondAmount = tierBond(tier, _isCountryRegion(region)); - - // Update tracked tier bonds before cleanup + uuidTotalTierBonds[uuid] -= tierBondAmount; - + _cleanupFleetFromTier(tokenId, region, tier); _burn(tokenId); - + if (isLastToken) { - // Transition to owned-only: mint owned-only token to owner uuidLevel[uuid] = RegistrationLevel.Owned; uint256 ownedTokenId = uint256(uint128(uuid)); - _mint(owner, ownedTokenId); - // Note: uuidTokenCount stays 1, operator is preserved + _mint(owner_, ownedTokenId); } else { - // Just decrement count uuidTokenCount[uuid]--; } - - // Refund tier bond to operator + _refundBond(operator, tierBondAmount); emit FleetBurned(tokenHolder, tokenId, region, tier, tierBondAmount); @@ -420,26 +395,19 @@ contract FleetIdentity is ERC721Enumerable, ReentrancyGuard { // ══════════════════════════════════════════════ /// @notice Claim ownership of a UUID without registering in any region. - /// Costs BASE_BOND. The UUID can later be registered via registerFleetLocal/Country. - /// @param uuid The Proximity UUID to claim. - /// @param operator Optional operator address for future tier management. Use address(0) for owner as operator. - /// @return tokenId The token ID for the owned-only UUID (region=0). function claimUuid(bytes16 uuid, address operator) external nonReentrant returns (uint256 tokenId) { if (uuid == bytes16(0)) revert InvalidUUID(); if (uuidOwner[uuid] != address(0)) revert UuidAlreadyOwned(); - // Set ownership uuidOwner[uuid] = msg.sender; uuidLevel[uuid] = RegistrationLevel.Owned; uuidTokenCount[uuid] = 1; - // Normalize operator: address(0) or msg.sender means owner is operator uuidOperator[uuid] = (operator == address(0) || operator == msg.sender) ? address(0) : operator; - // Mint token with region=0 tokenId = uint256(uint128(uuid)); _mint(msg.sender, tokenId); - _pullBond(msg.sender, BASE_BOND); + _pullBond(msg.sender, _baseBond); emit UuidClaimed(msg.sender, uuid, operatorOf(uuid)); } @@ -449,16 +417,12 @@ contract FleetIdentity is ERC721Enumerable, ReentrancyGuard { // ══════════════════════════════════════════════ /// @notice Bond required for tier K. - /// Local (admin area): BASE_BOND * 2^K - /// Country: BASE_BOND * COUNTRY_BOND_MULTIPLIER * 2^K (16× local) function tierBond(uint256 tier, bool isCountry) public view returns (uint256) { - uint256 base = BASE_BOND << tier; + uint256 base = _baseBond << tier; return isCountry ? base * COUNTRY_BOND_MULTIPLIER : base; } - /// @notice Returns the cheapest tier that guarantees a **local** fleet - /// appears in `buildHighestBondedUuidBundle` for (countryCode, adminCode). - /// Bounded: O(MAX_TIERS). + /// @notice Returns the cheapest tier for local inclusion. function localInclusionHint(uint16 countryCode, uint16 adminCode) external view @@ -470,18 +434,12 @@ contract FleetIdentity is ERC721Enumerable, ReentrancyGuard { bond = tierBond(inclusionTier, false); } - /// @notice Returns the cheapest tier that guarantees a **country** fleet - /// appears in every `buildHighestBondedUuidBundle` query within - /// the country (across all active admin areas). - /// @dev Bounded view — iterates only over active admin areas in the - /// specific country (max 255). Safe for RPC calls. + /// @notice Returns the cheapest tier for country inclusion. function countryInclusionHint(uint16 countryCode) external view returns (uint256 inclusionTier, uint256 bond) { if (countryCode == 0 || countryCode > MAX_COUNTRY_CODE) revert InvalidCountryCode(); - // Check the country-only location (no admin area active). inclusionTier = _findCheapestInclusionTier(countryCode, 0, true); - // Scan only admin areas belonging to this specific country (bounded by MAX_ADMIN_CODE=255). uint32[] storage countryAreas = _countryAdminAreas[countryCode]; uint256 len = countryAreas.length; for (uint256 i = 0; i < len; ++i) { @@ -518,12 +476,12 @@ contract FleetIdentity is ERC721Enumerable, ReentrancyGuard { } } - /// @notice UUID for a token ID (extracts lower 128 bits). + /// @notice UUID for a token ID. function tokenUuid(uint256 tokenId) public pure returns (bytes16) { return bytes16(uint128(tokenId)); } - /// @notice Region key encoded in a token ID (extracts bits 128-159). + /// @notice Region key encoded in a token ID. function tokenRegion(uint256 tokenId) public pure returns (uint32) { return uint32(tokenId >> 128); } @@ -533,24 +491,20 @@ contract FleetIdentity is ERC721Enumerable, ReentrancyGuard { return (uint256(regionKey) << 128) | uint256(uint128(uuid)); } - /// @notice Bond amount for a token. Returns 0 for nonexistent tokens. + /// @notice Bond amount for a token. function bonds(uint256 tokenId) external view returns (uint256) { if (_ownerOf(tokenId) == address(0)) return 0; uint32 region = tokenRegion(tokenId); - if (region == OWNED_REGION_KEY) return BASE_BOND; + if (region == OWNED_REGION_KEY) return _baseBond; return tierBond(fleetTier[tokenId], _isCountryRegion(region)); } - /// @notice Returns true if the UUID is in owned-only state (claimed but not registered). + /// @notice Returns true if the UUID is in owned-only state. function isOwnedOnly(bytes16 uuid) external view returns (bool) { return uuidLevel[uuid] == RegistrationLevel.Owned; } /// @notice Returns the effective operator for a UUID. - /// If no explicit operator is set, returns the uuidOwner (owner acts as operator). - /// Returns address(0) if UUID is not registered. - /// @param uuid The UUID to query. - /// @return operator The effective operator address responsible for tier maintenance. function operatorOf(bytes16 uuid) public view returns (address operator) { operator = uuidOperator[uuid]; if (operator == address(0)) { @@ -562,23 +516,7 @@ contract FleetIdentity is ERC721Enumerable, ReentrancyGuard { // Views: EdgeBeaconScanner discovery // ══════════════════════════════════════════════ - /// @notice Builds a priority-ordered bundle of up to 20 UUIDs for an EdgeBeaconScanner, - /// merging the highest-bonded tiers across admin-area and country levels. - /// - /// @dev **Priority Rules:** - /// 1. Higher bond tier always beats lower bond tier - /// 2. Within same tier: local (admin area) beats country - /// 3. Within same tier + level: earlier registration wins - /// - /// **Economic Fairness:** Country fleets pay 16× more (COUNTRY_BOND_MULTIPLIER) - /// than local fleets at the same tier. This means a local can reach tier 4 - /// for the same cost a country player pays for tier 0, giving locals a - /// significant economic advantage when competing for bundle slots. - /// - /// @param countryCode EdgeBeaconScanner country (must be > 0). - /// @param adminCode EdgeBeaconScanner admin area (must be > 0). - /// @return uuids The merged UUID bundle (up to 20). - /// @return count Actual number of UUIDs returned. + /// @notice Builds a priority-ordered bundle of up to 20 UUIDs. function buildHighestBondedUuidBundle(uint16 countryCode, uint16 adminCode) external view @@ -590,23 +528,10 @@ contract FleetIdentity is ERC721Enumerable, ReentrancyGuard { uint32 countryKey = uint32(countryCode); uint32 adminKey = makeAdminRegion(countryCode, adminCode); - (uuids, count, , ) = _buildHighestBondedUuidBundle(countryKey, adminKey); + (uuids, count,,) = _buildHighestBondedUuidBundle(countryKey, adminKey); } - /// @notice Builds a bundle containing ONLY country-level fleets for a country. - /// Use this when no admin areas are active to verify country fleet positions. - /// - /// @dev When no admin areas exist in a country, EdgeBeaconScanners are not yet - /// active there. This function lets country fleet owners inspect their - /// competitive position before scanners come online. - /// - /// The returned bundle represents the country-only contribution to any - /// future admin-area bundle. Local fleets (when they appear) will have - /// priority over country fleets at the same tier. - /// - /// @param countryCode ISO 3166-1 numeric country code (1-999). - /// @return uuids The country-only UUID bundle (up to 20). - /// @return count Actual number of UUIDs returned. + /// @notice Builds a bundle containing ONLY country-level fleets. function buildCountryOnlyBundle(uint16 countryCode) external view @@ -615,88 +540,26 @@ contract FleetIdentity is ERC721Enumerable, ReentrancyGuard { if (countryCode == 0 || countryCode > MAX_COUNTRY_CODE) revert InvalidCountryCode(); uint32 countryKey = uint32(countryCode); - // Use a virtual admin region with no members (adminCode=0) uint32 adminKey = makeAdminRegion(countryCode, 0); - (uuids, count, , ) = _buildHighestBondedUuidBundle(countryKey, adminKey); - } - - /// @dev Internal bundle builder that returns additional state for `_findCheapestInclusionTier`. - /// - /// Builds a priority-ordered bundle by descending from highestTier to tier 0, - /// including admin-area members before country members at each tier. - /// - /// @return uuids The UUIDs included in the bundle (trimmed to actual count). - /// @return count Number of UUIDs in the bundle. - /// @return highestTier The highest tier with any registered members. - /// @return lowestTier The lowest tier processed (may be > 0 if bundle filled early). - function _buildHighestBondedUuidBundle(uint32 countryKey, uint32 adminKey) - internal - view - returns (bytes16[] memory uuids, uint256 count, uint256 highestTier, uint256 lowestTier) - { - highestTier = _findMaxTierIndex(countryKey, adminKey); - - uuids = new bytes16[](MAX_BONDED_UUID_BUNDLE_SIZE); - - // Simple tier-descent: at each tier, locals first, then country - for (lowestTier = highestTier + 1; lowestTier > 0 && count < MAX_BONDED_UUID_BUNDLE_SIZE;) { - unchecked { --lowestTier; } - - // Include local (admin area) members first - count = _appendTierUuids(adminKey, lowestTier, uuids, count); - - // Include country members - count = _appendTierUuids(countryKey, lowestTier, uuids, count); - } - - // Trim array to actual size - assembly { - mstore(uuids, count) - } - } - - /// @dev Appends UUIDs from a region's tier to the bundle array. - /// If the tier has no members (empty region or tier beyond regionTierCount), - /// this is a no-op. Returns the updated count. - function _appendTierUuids( - uint32 regionKey, - uint256 tier, - bytes16[] memory uuids, - uint256 count - ) internal view returns (uint256) { - uint256[] storage members = _regionTierMembers[regionKey][tier]; - uint256 len = members.length; - uint256 room = MAX_BONDED_UUID_BUNDLE_SIZE - count; - uint256 toInclude = len < room ? len : room; - - for (uint256 i = 0; i < toInclude; ++i) { - uuids[count] = tokenUuid(members[i]); - unchecked { ++count; } - } - return count; + (uuids, count,,) = _buildHighestBondedUuidBundle(countryKey, adminKey); } // ══════════════════════════════════════════════ // Views: Region indexes // ══════════════════════════════════════════════ - /// @notice Returns all country codes with at least one active fleet. function getActiveCountries() external view returns (uint16[] memory) { return _activeCountries; } - /// @notice Returns all admin-area region keys with at least one active fleet. - /// @dev Computed by iterating active countries. O(countries × avg_admins). function getActiveAdminAreas() external view returns (uint32[] memory) { - // Count total admin areas across all countries uint256 total = 0; uint256 countryCount = _activeCountries.length; for (uint256 i = 0; i < countryCount; ++i) { total += _countryAdminAreas[_activeCountries[i]].length; } - // Build result array uint32[] memory result = new uint32[](total); uint256 idx = 0; for (uint256 i = 0; i < countryCount; ++i) { @@ -709,59 +572,49 @@ contract FleetIdentity is ERC721Enumerable, ReentrancyGuard { return result; } - /// @notice Returns the admin areas active in a specific country. - /// @dev Useful for off-chain enumeration without full index scan. function getCountryAdminAreas(uint16 countryCode) external view returns (uint32[] memory) { return _countryAdminAreas[countryCode]; } - /// @notice Builds an admin-area region key from country + admin codes. - /// @dev Country region key is simply uint32(countryCode) - no helper needed. function makeAdminRegion(uint16 countryCode, uint16 adminCode) public pure returns (uint32) { return (uint32(countryCode) << uint32(ADMIN_SHIFT)) | uint32(adminCode); } // ══════════════════════════════════════════════ - // Internals + // UUPS Authorization // ══════════════════════════════════════════════ - // -- Region key encoding -- + /// @dev Only the owner can authorize an upgrade. + function _authorizeUpgrade(address newImplementation) internal override onlyOwner {} + + // ══════════════════════════════════════════════ + // Internal Functions + // ══════════════════════════════════════════════ - /// @dev Extracts the country code from an admin-area region key. function _countryFromRegion(uint32 adminRegion) internal pure returns (uint16) { return uint16(adminRegion >> uint32(ADMIN_SHIFT)); } - /// @dev Extracts the admin code from an admin-area region key. function _adminFromRegion(uint32 adminRegion) internal pure returns (uint16) { return uint16(adminRegion & ADMIN_CODE_MASK); } - /// @dev Returns true if the region key represents a country-level registration. - /// Region 0 (owned-only) is not a country region. function _isCountryRegion(uint32 regionKey) internal pure returns (bool) { return regionKey > 0 && regionKey <= MAX_COUNTRY_CODE; } - // -- Bond transfer helpers -- - - /// @dev Pulls bond tokens from an address (CEI: call after state changes). function _pullBond(address from, uint256 amount) internal { if (amount > 0) { - BOND_TOKEN.safeTransferFrom(from, address(this), amount); + _bondToken.safeTransferFrom(from, address(this), amount); } } - /// @dev Refunds bond tokens to an address (CEI: call after state changes). function _refundBond(address to, uint256 amount) internal { if (amount > 0) { - BOND_TOKEN.safeTransfer(to, amount); + _bondToken.safeTransfer(to, amount); } } - // -- UUID ownership helpers -- - - /// @dev Clears all UUID ownership state. Used when last token for a UUID is burned. function _clearUuidOwnership(bytes16 uuid) internal { delete uuidOwner[uuid]; delete uuidTokenCount[uuid]; @@ -770,8 +623,6 @@ contract FleetIdentity is ERC721Enumerable, ReentrancyGuard { delete uuidTotalTierBonds[uuid]; } - /// @dev Decrements UUID token count. Clears ownership if count reaches zero. - /// @return newCount The new token count after decrement. function _decrementUuidCount(bytes16 uuid) internal returns (uint256 newCount) { newCount = uuidTokenCount[uuid] - 1; if (newCount == 0) { @@ -781,10 +632,6 @@ contract FleetIdentity is ERC721Enumerable, ReentrancyGuard { } } - // -- Tier cleanup helpers -- - - /// @dev Removes a fleet from its tier and cleans up associated state. - /// Does NOT burn the token - caller must handle that. function _cleanupFleetFromTier(uint256 tokenId, uint32 region, uint256 tier) internal { _removeFromTier(tokenId, region, tier); delete fleetTier[tokenId]; @@ -793,10 +640,6 @@ contract FleetIdentity is ERC721Enumerable, ReentrancyGuard { _removeFromRegionIndex(region); } - // -- Registration helpers -- - - /// @dev Mints a fleet token to msg.sender. Used for fresh registrations. - /// @return tokenId The newly minted token ID. function _mintFleetToken(bytes16 uuid, uint32 region, uint256 tier) internal returns (uint256 tokenId) { tokenId = computeTokenId(uuid, region); fleetTier[tokenId] = tier; @@ -805,9 +648,10 @@ contract FleetIdentity is ERC721Enumerable, ReentrancyGuard { _mint(msg.sender, tokenId); } - /// @dev Mints a fleet token to a specific owner. Used when operator registers for an owner. - /// @return tokenId The newly minted token ID. - function _mintFleetTokenTo(address to, bytes16 uuid, uint32 region, uint256 tier) internal returns (uint256 tokenId) { + function _mintFleetTokenTo(address to, bytes16 uuid, uint32 region, uint256 tier) + internal + returns (uint256 tokenId) + { tokenId = computeTokenId(uuid, region); fleetTier[tokenId] = tier; _addToTier(tokenId, region, tier); @@ -815,12 +659,6 @@ contract FleetIdentity is ERC721Enumerable, ReentrancyGuard { _mint(to, tokenId); } - /// @dev Shared registration logic. Handles fresh, Owned → Registered, and multi-region registrations. - /// Only operator can register. Operator pays tier bond; owner pays BASE_BOND. - /// For fresh UUIDs, caller becomes both owner and operator. - /// @param uuid The Proximity UUID to register. - /// @param region The region key (country or admin area). - /// @param targetTier The tier to register at. function _register(bytes16 uuid, uint32 region, uint256 targetTier) internal returns (uint256 tokenId) { RegistrationLevel existingLevel = uuidLevel[uuid]; bool isCountry = _isCountryRegion(region); @@ -828,55 +666,47 @@ contract FleetIdentity is ERC721Enumerable, ReentrancyGuard { uint256 targetTierBond = tierBond(targetTier, isCountry); if (existingLevel == RegistrationLevel.Owned) { - // Owned → Registered transition: only operator can register address operator = operatorOf(uuid); if (operator != msg.sender) revert NotOperator(); - address owner = uuidOwner[uuid]; - - _burn(uint256(uint128(uuid))); // Burn owned-only token + address owner_ = uuidOwner[uuid]; + + _burn(uint256(uint128(uuid))); uuidLevel[uuid] = targetLevel; uuidTotalTierBonds[uuid] = targetTierBond; - - tokenId = _mintFleetTokenTo(owner, uuid, region, targetTier); - - // Operator pays full tier bond (owner already paid BASE_BOND via claimUuid) + + tokenId = _mintFleetTokenTo(owner_, uuid, region, targetTier); + _pullBond(operator, targetTierBond); - - emit FleetRegistered(owner, uuid, tokenId, region, targetTier, targetTierBond, operator); + + emit FleetRegistered(owner_, uuid, tokenId, region, targetTier, targetTierBond, operator); } else if (existingLevel == RegistrationLevel.None) { - // Fresh registration: caller becomes owner+operator, pays BASE_BOND + tier bond uuidOwner[uuid] = msg.sender; uuidLevel[uuid] = targetLevel; uuidTokenCount[uuid] = 1; uuidTotalTierBonds[uuid] = targetTierBond; - // uuidOperator stays address(0) - caller acts as operator via operatorOf() - + tokenId = _mintFleetToken(uuid, region, targetTier); - - // Caller pays BASE_BOND (ownership) + tier bond (registration) - _pullBond(msg.sender, BASE_BOND + targetTierBond); - - emit FleetRegistered(msg.sender, uuid, tokenId, region, targetTier, BASE_BOND + targetTierBond, msg.sender); + + _pullBond(msg.sender, _baseBond + targetTierBond); + + emit FleetRegistered(msg.sender, uuid, tokenId, region, targetTier, _baseBond + targetTierBond, msg.sender); } else { - // Multi-region registration: only operator can register additional regions address operator = operatorOf(uuid); if (operator != msg.sender) revert NotOperator(); if (existingLevel != targetLevel) revert UuidLevelMismatch(); - address owner = uuidOwner[uuid]; - + address owner_ = uuidOwner[uuid]; + uuidTokenCount[uuid]++; uuidTotalTierBonds[uuid] += targetTierBond; - - tokenId = _mintFleetTokenTo(owner, uuid, region, targetTier); - - // Operator pays tier bond + + tokenId = _mintFleetTokenTo(owner_, uuid, region, targetTier); + _pullBond(operator, targetTierBond); - - emit FleetRegistered(owner, uuid, tokenId, region, targetTier, targetTierBond, operator); + + emit FleetRegistered(owner_, uuid, tokenId, region, targetTier, targetTierBond, operator); } } - /// @dev Shared promotion logic. Only operator can call. function _promote(uint256 tokenId, uint256 targetTier) internal { bytes16 uuid = tokenUuid(tokenId); address operator = operatorOf(uuid); @@ -893,19 +723,16 @@ contract FleetIdentity is ERC721Enumerable, ReentrancyGuard { uint256 targetBond = tierBond(targetTier, isCountry); uint256 additionalBond = targetBond - currentBond; - // Effects uuidTotalTierBonds[uuid] += additionalBond; _removeFromTier(tokenId, region, currentTier); fleetTier[tokenId] = targetTier; _addToTier(tokenId, region, targetTier); - // Interaction: pull from operator _pullBond(operator, additionalBond); emit FleetPromoted(tokenId, currentTier, targetTier, additionalBond); } - /// @dev Shared demotion logic. Refunds bond difference to operator. function _demote(uint256 tokenId, uint256 targetTier) internal { bytes16 uuid = tokenUuid(tokenId); address operator = operatorOf(uuid); @@ -921,33 +748,65 @@ contract FleetIdentity is ERC721Enumerable, ReentrancyGuard { uint256 targetBond = tierBond(targetTier, isCountry); uint256 refund = currentBond - targetBond; - // Effects uuidTotalTierBonds[uuid] -= refund; _removeFromTier(tokenId, region, currentTier); fleetTier[tokenId] = targetTier; _addToTier(tokenId, region, targetTier); _trimTierCount(region); - // Interaction: refund to operator _refundBond(operator, refund); emit FleetDemoted(tokenId, currentTier, targetTier, refund); } - /// @dev Validates that a tier is available for registration (pure validation, no state changes). function _validateExplicitTier(uint32 region, uint256 targetTier) internal view { if (targetTier >= MAX_TIERS) revert MaxTiersReached(); if (_regionTierMembers[region][targetTier].length >= TIER_CAPACITY) revert TierFull(); } - // -- Bundle-level helpers (shared by buildHighestBondedUuidBundle & inclusion hints) -- + function _buildHighestBondedUuidBundle(uint32 countryKey, uint32 adminKey) + internal + view + returns (bytes16[] memory uuids, uint256 count, uint256 highestTier, uint256 lowestTier) + { + highestTier = _findMaxTierIndex(countryKey, adminKey); + + uuids = new bytes16[](MAX_BONDED_UUID_BUNDLE_SIZE); + + for (lowestTier = highestTier + 1; lowestTier > 0 && count < MAX_BONDED_UUID_BUNDLE_SIZE;) { + unchecked { + --lowestTier; + } + + count = _appendTierUuids(adminKey, lowestTier, uuids, count); + count = _appendTierUuids(countryKey, lowestTier, uuids, count); + } + + assembly { + mstore(uuids, count) + } + } - /// @dev Finds the highest active tier index across both bundle levels. - function _findMaxTierIndex(uint32 countryKey, uint32 adminKey) + function _appendTierUuids(uint32 regionKey, uint256 tier, bytes16[] memory uuids, uint256 count) internal view - returns (uint256 maxTierIndex) + returns (uint256) { + uint256[] storage members = _regionTierMembers[regionKey][tier]; + uint256 len = members.length; + uint256 room = MAX_BONDED_UUID_BUNDLE_SIZE - count; + uint256 toInclude = len < room ? len : room; + + for (uint256 i = 0; i < toInclude; ++i) { + uuids[count] = tokenUuid(members[i]); + unchecked { + ++count; + } + } + return count; + } + + function _findMaxTierIndex(uint32 countryKey, uint32 adminKey) internal view returns (uint256 maxTierIndex) { uint256 adminTiers = regionTierCount[adminKey]; uint256 countryTiers = regionTierCount[countryKey]; @@ -956,23 +815,6 @@ contract FleetIdentity is ERC721Enumerable, ReentrancyGuard { return maxTier; } - // -- Inclusion-tier logic -- - - /// @dev Uses `_buildHighestBondedUuidBundle` to determine the cheapest tier at - /// `candidateRegion` that guarantees bundle inclusion. Bounded: O(MAX_TIERS). - /// - /// Walks from the bundle's lowestTier upward, "unwinding" the bundle count - /// by subtracting both regions' contributions at each tier. Returns the first - /// tier where: - /// (a) The tier has capacity (< TIER_CAPACITY members). - /// (b) The unwound count shows room in the bundle (< MAX_BONDED_UUID_BUNDLE_SIZE). - /// - /// If no existing tier qualifies and highestTier + 1 < MAX_TIERS, returns - /// highestTier + 1 (joining above current max guarantees inclusion). - /// - /// @param countryCode The country code for the bundle location. - /// @param adminCode The admin area code (0 for country-only bundles). - /// @param isCountry True if candidate is joining country region, false for admin. function _findCheapestInclusionTier(uint16 countryCode, uint16 adminCode, bool isCountry) internal view @@ -982,11 +824,9 @@ contract FleetIdentity is ERC721Enumerable, ReentrancyGuard { uint32 adminKey = makeAdminRegion(countryCode, adminCode); uint32 candidateRegion = isCountry ? countryKey : adminKey; - (, uint256 count, uint256 highestTier, uint256 lowestTier) = _buildHighestBondedUuidBundle(countryKey, adminKey); + (, uint256 count, uint256 highestTier, uint256 lowestTier) = + _buildHighestBondedUuidBundle(countryKey, adminKey); - // Walk from lowestTier upward, unwinding the bundle count at each tier. - // Subtracting both regions' contributions simulates "what if we built the - // bundle stopping at this tier instead". for (uint256 tier = lowestTier; tier <= highestTier; ++tier) { bool tierHasCapacity = _regionTierMembers[candidateRegion][tier].length < TIER_CAPACITY; bool bundleHasRoom = count < MAX_BONDED_UUID_BUNDLE_SIZE; @@ -995,15 +835,12 @@ contract FleetIdentity is ERC721Enumerable, ReentrancyGuard { return tier; } - // Unwind: subtract both regions' contributions at this tier. - // Use saturating subtraction to handle edge cases gracefully. uint256 adminMembers = _regionTierMembers[adminKey][tier].length; uint256 countryMembers = _regionTierMembers[countryKey][tier].length; uint256 tierTotal = adminMembers + countryMembers; count = tierTotal > count ? 0 : count - tierTotal; } - // No fit in existing tiers — try joining above current max. if (highestTier < MAX_TIERS - 1) { return highestTier + 1; } @@ -1011,19 +848,15 @@ contract FleetIdentity is ERC721Enumerable, ReentrancyGuard { revert MaxTiersReached(); } - /// @dev Appends a token to a region's tier member array and records its index. - /// Updates regionTierCount if this opens a new highest tier. function _addToTier(uint256 tokenId, uint32 region, uint256 tier) internal { _regionTierMembers[region][tier].push(tokenId); _indexInTier[tokenId] = _regionTierMembers[region][tier].length - 1; - // Update tier count if we're opening a new tier if (tier >= regionTierCount[region]) { regionTierCount[region] = tier + 1; } } - /// @dev Swap-and-pop removal from a region's tier member array. function _removeFromTier(uint256 tokenId, uint32 region, uint256 tier) internal { uint256[] storage members = _regionTierMembers[region][tier]; uint256 idx = _indexInTier[tokenId]; @@ -1037,31 +870,24 @@ contract FleetIdentity is ERC721Enumerable, ReentrancyGuard { members.pop(); } - /// @dev Shrinks regionTierCount so the top tier is always non-empty. function _trimTierCount(uint32 region) internal { - uint256 tierCount = regionTierCount[region]; - while (tierCount > 0 && _regionTierMembers[region][tierCount - 1].length == 0) { - tierCount--; + uint256 tierCount_ = regionTierCount[region]; + while (tierCount_ > 0 && _regionTierMembers[region][tierCount_ - 1].length == 0) { + tierCount_--; } - regionTierCount[region] = tierCount; + regionTierCount[region] = tierCount_; } - // -- Region index maintenance -- - - /// @dev Adds a region to the appropriate index set if not already present. function _addToRegionIndex(uint32 region) internal { if (_isCountryRegion(region)) { - // Country uint16 cc = uint16(region); if (_activeCountryIndex[cc] == 0) { _activeCountries.push(cc); - _activeCountryIndex[cc] = _activeCountries.length; // 1-indexed + _activeCountryIndex[cc] = _activeCountries.length; } } else { - // Admin area: add to country's list if (_countryAdminAreaIndex[region] == 0) { uint16 cc = _countryFromRegion(region); - // Ensure country is in active list (for getActiveAdminAreas iteration) if (_activeCountryIndex[cc] == 0) { _activeCountries.push(cc); _activeCountryIndex[cc] = _activeCountries.length; @@ -1072,15 +898,13 @@ contract FleetIdentity is ERC721Enumerable, ReentrancyGuard { } } - /// @dev Removes a region from the index set if the region is now completely empty. function _removeFromRegionIndex(uint32 region) internal { - if (regionTierCount[region] > 0) return; // still has fleets + if (regionTierCount[region] > 0) return; if (_isCountryRegion(region)) { uint16 cc = uint16(region); uint256 oneIdx = _activeCountryIndex[cc]; if (oneIdx > 0) { - // Only remove country if it has no admin areas if (_countryAdminAreas[cc].length > 0) return; uint256 lastIdx = _activeCountries.length - 1; @@ -1094,7 +918,6 @@ contract FleetIdentity is ERC721Enumerable, ReentrancyGuard { delete _activeCountryIndex[cc]; } } else { - // Admin area: remove from country's list uint256 oneIdx = _countryAdminAreaIndex[region]; if (oneIdx > 0) { uint16 cc = _countryFromRegion(region); @@ -1109,7 +932,6 @@ contract FleetIdentity is ERC721Enumerable, ReentrancyGuard { countryAreas.pop(); delete _countryAdminAreaIndex[region]; - // Remove country from active list if no more admin areas AND no country fleets if (countryAreas.length == 0 && regionTierCount[uint32(cc)] == 0) { uint256 countryOneIdx = _activeCountryIndex[cc]; if (countryOneIdx > 0) { @@ -1132,24 +954,26 @@ contract FleetIdentity is ERC721Enumerable, ReentrancyGuard { // Overrides required by ERC721Enumerable // ────────────────────────────────────────────── - function _update(address to, uint256 tokenId, address auth) internal override(ERC721Enumerable) returns (address) { + function _update(address to, uint256 tokenId, address auth) + internal + override(ERC721EnumerableUpgradeable) + returns (address) + { address from = super._update(to, tokenId, auth); - - // For owned-only tokens, transfer uuidOwner when the token is transferred - // This allows marketplace trading of owned-only UUIDs + uint32 region = tokenRegion(tokenId); if (region == OWNED_REGION_KEY && from != address(0) && to != address(0)) { uuidOwner[tokenUuid(tokenId)] = to; } - + return from; } - function _increaseBalance(address account, uint128 value) internal override(ERC721Enumerable) { + function _increaseBalance(address account, uint128 value) internal override(ERC721EnumerableUpgradeable) { super._increaseBalance(account, value); } - function supportsInterface(bytes4 interfaceId) public view override(ERC721Enumerable) returns (bool) { + function supportsInterface(bytes4 interfaceId) public view override(ERC721EnumerableUpgradeable) returns (bool) { return super.supportsInterface(interfaceId); } } diff --git a/src/swarms/ServiceProvider.sol b/src/swarms/ServiceProvider.sol deleted file mode 100644 index e4a777b7..00000000 --- a/src/swarms/ServiceProvider.sol +++ /dev/null @@ -1,54 +0,0 @@ -// SPDX-License-Identifier: BSD-3-Clause-Clear - -pragma solidity ^0.8.24; - -import {ERC721} from "@openzeppelin/contracts/token/ERC721/ERC721.sol"; - -/** - * @title ServiceProvider - * @notice Permissionless ERC-721 representing ownership of a service endpoint URL. - * @dev TokenID = keccak256(url), guaranteeing one owner per URL. - */ -contract ServiceProvider is ERC721 { - error EmptyURL(); - error NotTokenOwner(); - - // Maps TokenID -> Provider URL - mapping(uint256 => string) public providerUrls; - - event ProviderRegistered(address indexed owner, string url, uint256 indexed tokenId); - event ProviderBurned(address indexed owner, uint256 indexed tokenId); - - constructor() ERC721("Swarm Service Provider", "SSV") {} - - /// @notice Mints a new provider NFT for the given URL. - /// @param url The backend service URL (must be unique). - /// @return tokenId The deterministic token ID derived from `url`. - function registerProvider(string calldata url) external returns (uint256 tokenId) { - if (bytes(url).length == 0) { - revert EmptyURL(); - } - - tokenId = uint256(keccak256(bytes(url))); - - providerUrls[tokenId] = url; - - _mint(msg.sender, tokenId); - - emit ProviderRegistered(msg.sender, url, tokenId); - } - - /// @notice Burns the provider NFT. Caller must be the token owner. - /// @param tokenId The provider token ID to burn. - function burn(uint256 tokenId) external { - if (ownerOf(tokenId) != msg.sender) { - revert NotTokenOwner(); - } - - delete providerUrls[tokenId]; - - _burn(tokenId); - - emit ProviderBurned(msg.sender, tokenId); - } -} diff --git a/src/swarms/ServiceProviderUpgradeable.sol b/src/swarms/ServiceProviderUpgradeable.sol new file mode 100644 index 00000000..70c3b7d4 --- /dev/null +++ b/src/swarms/ServiceProviderUpgradeable.sol @@ -0,0 +1,123 @@ +// SPDX-License-Identifier: BSD-3-Clause-Clear + +pragma solidity ^0.8.24; + +import {ERC721Upgradeable} from "@openzeppelin/contracts-upgradeable/token/ERC721/ERC721Upgradeable.sol"; +import {Ownable2StepUpgradeable} from "@openzeppelin/contracts-upgradeable/access/Ownable2StepUpgradeable.sol"; +import {UUPSUpgradeable} from "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol"; +import {Initializable} from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol"; + +/** + * @title ServiceProviderUpgradeable + * @notice UUPS-upgradeable ERC-721 representing ownership of a service endpoint URL. + * @dev TokenID = keccak256(url), guaranteeing one owner per URL. + * + * **Upgrade Pattern:** + * - Uses OpenZeppelin UUPS proxy pattern for upgradeability. + * - Only the contract owner can authorize upgrades. + * - Storage layout must be preserved across upgrades (append-only). + * + * **Storage Migration:** + * - V1 storage is automatically preserved in the proxy. + * - Future versions can add new storage variables at the end. + * - Use `reinitializer(n)` for version-specific initialization. + */ +contract ServiceProviderUpgradeable is + Initializable, + ERC721Upgradeable, + Ownable2StepUpgradeable, + UUPSUpgradeable +{ + // ────────────────────────────────────────────── + // Errors + // ────────────────────────────────────────────── + error EmptyURL(); + error NotTokenOwner(); + + // ────────────────────────────────────────────── + // Storage (V1) + // ────────────────────────────────────────────── + + /// @notice Maps TokenID -> Provider URL + mapping(uint256 => string) public providerUrls; + + // ────────────────────────────────────────────── + // Storage Gap (for future upgrades) + // ────────────────────────────────────────────── + + /// @dev Reserved storage slots for future upgrades. + /// When adding new storage in V2+, reduce this gap accordingly. + /// Example: Adding 1 new storage variable → change to __gap[48] + // solhint-disable-next-line var-name-mixedcase + uint256[49] private __gap; + + // ────────────────────────────────────────────── + // Events + // ────────────────────────────────────────────── + + event ProviderRegistered(address indexed owner, string url, uint256 indexed tokenId); + event ProviderBurned(address indexed owner, uint256 indexed tokenId); + + // ────────────────────────────────────────────── + // Constructor (disables initializers on implementation) + // ────────────────────────────────────────────── + + /// @custom:oz-upgrades-unsafe-allow constructor + constructor() { + _disableInitializers(); + } + + // ────────────────────────────────────────────── + // Initializer (replaces constructor) + // ────────────────────────────────────────────── + + /// @notice Initializes the contract. Must be called once via proxy. + /// @param owner_ The address that will own this contract and can authorize upgrades. + function initialize(address owner_) external initializer { + __ERC721_init("Swarm Service Provider", "SSV"); + __Ownable_init(owner_); + __Ownable2Step_init(); + } + + // ────────────────────────────────────────────── + // Core Functions + // ────────────────────────────────────────────── + + /// @notice Mints a new provider NFT for the given URL. + /// @param url The backend service URL (must be unique). + /// @return tokenId The deterministic token ID derived from `url`. + function registerProvider(string calldata url) external returns (uint256 tokenId) { + if (bytes(url).length == 0) { + revert EmptyURL(); + } + + tokenId = uint256(keccak256(bytes(url))); + + providerUrls[tokenId] = url; + + _mint(msg.sender, tokenId); + + emit ProviderRegistered(msg.sender, url, tokenId); + } + + /// @notice Burns the provider NFT. Caller must be the token owner. + /// @param tokenId The provider token ID to burn. + function burn(uint256 tokenId) external { + if (ownerOf(tokenId) != msg.sender) { + revert NotTokenOwner(); + } + + delete providerUrls[tokenId]; + + _burn(tokenId); + + emit ProviderBurned(msg.sender, tokenId); + } + + // ────────────────────────────────────────────── + // UUPS Authorization + // ────────────────────────────────────────────── + + /// @dev Only the owner can authorize an upgrade. + function _authorizeUpgrade(address newImplementation) internal override onlyOwner {} +} diff --git a/src/swarms/SwarmRegistryL1.sol b/src/swarms/SwarmRegistryL1.sol deleted file mode 100644 index 4885cd66..00000000 --- a/src/swarms/SwarmRegistryL1.sol +++ /dev/null @@ -1,356 +0,0 @@ -// SPDX-License-Identifier: BSD-3-Clause-Clear - -pragma solidity ^0.8.24; - -// NOTE: SSTORE2 is not compatible with ZkSync Era due to EXTCODECOPY limitation. -// For ZkSync deployment, consider using chunked storage or calldata alternatives. -import {SSTORE2} from "solady/utils/SSTORE2.sol"; -import {ReentrancyGuard} from "@openzeppelin/contracts/utils/ReentrancyGuard.sol"; -import {FleetIdentity} from "./FleetIdentity.sol"; -import {ServiceProvider} from "./ServiceProvider.sol"; - -/** - * @title SwarmRegistryL1 - * @notice Permissionless BLE swarm registry optimized for Ethereum L1 (uses SSTORE2 for filter storage). - * @dev Not compatible with ZkSync Era — use SwarmRegistryUniversal instead. - * - * Swarms are defined for a **fleet UUID** (not a token ID), allowing swarms to be - * registered for any UUID that has been claimed/registered in FleetIdentity, - * regardless of whether it's assigned to a region or is in "owned-only" mode. - * This decouples swarm management from geographic tier placement. - */ -contract SwarmRegistryL1 is ReentrancyGuard { - error InvalidFingerprintSize(); - error InvalidFilterSize(); - error InvalidUuid(); - error NotUuidOwner(); - error ProviderDoesNotExist(); - error NotProviderOwner(); - error SwarmNotFound(); - error InvalidSwarmData(); - error SwarmAlreadyExists(); - error SwarmNotOrphaned(); - error SwarmOrphaned(); - - enum SwarmStatus { - REGISTERED, - ACCEPTED, - REJECTED - } - - // Internal Schema version for Tag ID construction - enum TagType { - IBEACON_PAYLOAD_ONLY, // 0x00: proxUUID || major || minor - IBEACON_INCLUDES_MAC, // 0x01: proxUUID || major || minor || MAC (Normalized) - VENDOR_ID, // 0x02: companyID || hash(vendorBytes) - GENERIC // 0x03 - - } - - struct Swarm { - bytes16 fleetUuid; // Fleet UUID (not token ID) - allows swarms for any registered UUID - uint256 providerId; // The Service Provider TokenID - address filterPointer; // SSTORE2 pointer - uint8 fingerprintSize; - TagType tagType; - SwarmStatus status; - } - - uint8 public constant MAX_FINGERPRINT_SIZE = 16; - - FleetIdentity public immutable FLEET_CONTRACT; - - ServiceProvider public immutable PROVIDER_CONTRACT; - - // SwarmID -> Swarm - mapping(uint256 => Swarm) public swarms; - - // UUID -> List of SwarmIDs (keyed by fleet UUID, not token ID) - mapping(bytes16 => uint256[]) public uuidSwarms; - - // SwarmID -> index in uuidSwarms[fleetUuid] (for O(1) removal) - mapping(uint256 => uint256) public swarmIndexInUuid; - - event SwarmRegistered(uint256 indexed swarmId, bytes16 indexed fleetUuid, uint256 indexed providerId, address owner); - event SwarmStatusChanged(uint256 indexed swarmId, SwarmStatus status); - event SwarmProviderUpdated(uint256 indexed swarmId, uint256 indexed oldProvider, uint256 indexed newProvider); - event SwarmDeleted(uint256 indexed swarmId, bytes16 indexed fleetUuid, address indexed owner); - event SwarmPurged(uint256 indexed swarmId, bytes16 indexed fleetUuid, address indexed purgedBy); - - /// @notice Derives a deterministic swarm ID. Callable off-chain to predict IDs before registration. - /// @dev Swarm identity is based on fleet, filter, fingerprintSize, and tagType. ProviderId is mutable and not part of identity. - /// @return swarmId keccak256(fleetUuid, filterData, fingerprintSize, tagType) - function computeSwarmId(bytes16 fleetUuid, bytes calldata filterData, uint8 fingerprintSize, TagType tagType) - public - pure - returns (uint256) - { - return uint256(keccak256(abi.encode(fleetUuid, filterData, fingerprintSize, tagType))); - } - - constructor(address _fleetContract, address _providerContract) { - if (_fleetContract == address(0) || _providerContract == address(0)) { - revert InvalidSwarmData(); - } - FLEET_CONTRACT = FleetIdentity(_fleetContract); - PROVIDER_CONTRACT = ServiceProvider(_providerContract); - } - - /// @notice Registers a new swarm. Caller must own the fleet UUID (via FleetIdentity.uuidOwner). - /// @param fleetUuid Fleet UUID (bytes16) - the UUID must be registered in FleetIdentity. - /// @param providerId Service provider token ID. - /// @param filterData XOR filter blob (1–24 576 bytes). - /// @param fingerprintSize Fingerprint width in bits (1–16). - /// @param tagType Tag identity schema. - /// @return swarmId Deterministic ID for this swarm. - function registerSwarm( - bytes16 fleetUuid, - uint256 providerId, - bytes calldata filterData, - uint8 fingerprintSize, - TagType tagType - ) external nonReentrant returns (uint256 swarmId) { - if (fleetUuid == bytes16(0)) { - revert InvalidUuid(); - } - if (fingerprintSize == 0 || fingerprintSize > MAX_FINGERPRINT_SIZE) { - revert InvalidFingerprintSize(); - } - if (filterData.length == 0 || filterData.length > 24576) { - revert InvalidFilterSize(); - } - - // Check UUID ownership - works for any registered UUID regardless of region - if (FLEET_CONTRACT.uuidOwner(fleetUuid) != msg.sender) { - revert NotUuidOwner(); - } - try PROVIDER_CONTRACT.ownerOf(providerId) returns (address) {} - catch { - revert ProviderDoesNotExist(); - } - - swarmId = computeSwarmId(fleetUuid, filterData, fingerprintSize, tagType); - - if (swarms[swarmId].filterPointer != address(0)) { - revert SwarmAlreadyExists(); - } - - Swarm storage s = swarms[swarmId]; - s.fleetUuid = fleetUuid; - s.providerId = providerId; - s.fingerprintSize = fingerprintSize; - s.tagType = tagType; - s.status = SwarmStatus.REGISTERED; - - uuidSwarms[fleetUuid].push(swarmId); - swarmIndexInUuid[swarmId] = uuidSwarms[fleetUuid].length - 1; - - s.filterPointer = SSTORE2.write(filterData); - - emit SwarmRegistered(swarmId, fleetUuid, providerId, msg.sender); - } - - /// @notice Approves a swarm. Caller must own the provider NFT. - /// @param swarmId The swarm to accept. - function acceptSwarm(uint256 swarmId) external { - Swarm storage s = swarms[swarmId]; - if (s.filterPointer == address(0)) revert SwarmNotFound(); - - (bool fleetValid, bool providerValid) = isSwarmValid(swarmId); - if (!fleetValid || !providerValid) revert SwarmOrphaned(); - - if (PROVIDER_CONTRACT.ownerOf(s.providerId) != msg.sender) { - revert NotProviderOwner(); - } - s.status = SwarmStatus.ACCEPTED; - emit SwarmStatusChanged(swarmId, SwarmStatus.ACCEPTED); - } - - /// @notice Rejects a swarm. Caller must own the provider NFT. - /// @param swarmId The swarm to reject. - function rejectSwarm(uint256 swarmId) external { - Swarm storage s = swarms[swarmId]; - if (s.filterPointer == address(0)) revert SwarmNotFound(); - - (bool fleetValid, bool providerValid) = isSwarmValid(swarmId); - if (!fleetValid || !providerValid) revert SwarmOrphaned(); - - if (PROVIDER_CONTRACT.ownerOf(s.providerId) != msg.sender) { - revert NotProviderOwner(); - } - s.status = SwarmStatus.REJECTED; - emit SwarmStatusChanged(swarmId, SwarmStatus.REJECTED); - } - - /// @notice Reassigns the service provider. Resets status to REGISTERED. Caller must own the fleet UUID. - /// @param swarmId The swarm to update. - /// @param newProviderId New provider token ID. - function updateSwarmProvider(uint256 swarmId, uint256 newProviderId) external { - Swarm storage s = swarms[swarmId]; - if (s.filterPointer == address(0)) { - revert SwarmNotFound(); - } - if (FLEET_CONTRACT.uuidOwner(s.fleetUuid) != msg.sender) { - revert NotUuidOwner(); - } - try PROVIDER_CONTRACT.ownerOf(newProviderId) returns (address) {} - catch { - revert ProviderDoesNotExist(); - } - - uint256 oldProvider = s.providerId; - - s.providerId = newProviderId; - - s.status = SwarmStatus.REGISTERED; - - emit SwarmProviderUpdated(swarmId, oldProvider, newProviderId); - } - - /// @notice Permanently deletes a swarm. Caller must own the fleet UUID. - /// @param swarmId The swarm to delete. - function deleteSwarm(uint256 swarmId) external { - Swarm storage s = swarms[swarmId]; - if (s.filterPointer == address(0)) { - revert SwarmNotFound(); - } - if (FLEET_CONTRACT.uuidOwner(s.fleetUuid) != msg.sender) { - revert NotUuidOwner(); - } - - bytes16 fleetUuid = s.fleetUuid; - - _removeFromUuidSwarms(fleetUuid, swarmId); - - delete swarms[swarmId]; - - emit SwarmDeleted(swarmId, fleetUuid, msg.sender); - } - - /// @notice Returns whether the swarm's fleet UUID and provider NFT are still valid. - /// @param swarmId The swarm to check. - /// @return fleetValid True if the fleet UUID is still owned (uuidOwner != address(0)). - /// @return providerValid True if the provider NFT exists. - function isSwarmValid(uint256 swarmId) public view returns (bool fleetValid, bool providerValid) { - Swarm storage s = swarms[swarmId]; - if (s.filterPointer == address(0)) revert SwarmNotFound(); - - // Fleet is valid if UUID is still owned (not released) - fleetValid = FLEET_CONTRACT.uuidOwner(s.fleetUuid) != address(0); - - try PROVIDER_CONTRACT.ownerOf(s.providerId) returns (address) { - providerValid = true; - } catch { - providerValid = false; - } - } - - /// @notice Permissionless-ly removes a swarm whose fleet UUID has been released or provider NFT has been burned. - /// @param swarmId The orphaned swarm to purge. - function purgeOrphanedSwarm(uint256 swarmId) external { - Swarm storage s = swarms[swarmId]; - if (s.filterPointer == address(0)) revert SwarmNotFound(); - - (bool fleetValid, bool providerValid) = isSwarmValid(swarmId); - if (fleetValid && providerValid) revert SwarmNotOrphaned(); - - bytes16 fleetUuid = s.fleetUuid; - - _removeFromUuidSwarms(fleetUuid, swarmId); - - delete swarms[swarmId]; - - emit SwarmPurged(swarmId, fleetUuid, msg.sender); - } - - /// @notice Tests tag membership against the swarm's XOR filter. - /// @param swarmId The swarm to query. - /// @param tagHash keccak256 of the tag identity bytes (caller must pre-normalize per tagType). - /// @return isValid True if the tag passes the XOR filter check. - function checkMembership(uint256 swarmId, bytes32 tagHash) external view returns (bool isValid) { - Swarm storage s = swarms[swarmId]; - if (s.filterPointer == address(0)) { - revert SwarmNotFound(); - } - - // Reject queries against orphaned swarms - (bool fleetValid, bool providerValid) = isSwarmValid(swarmId); - if (!fleetValid || !providerValid) revert SwarmOrphaned(); - - uint256 dataLen; - address pointer = s.filterPointer; - assembly { - dataLen := extcodesize(pointer) - } - - // SSTORE2 adds 1 byte overhead (0x00), So actual data length = codeSize - 1. - if (dataLen > 0) { - unchecked { - --dataLen; - } - } - - // 2. Calculate M (number of slots) - uint256 m = (dataLen * 8) / s.fingerprintSize; - if (m == 0) return false; - - bytes32 h = tagHash; - - uint32 h1 = uint32(uint256(h)) % uint32(m); - uint32 h2 = uint32(uint256(h) >> 32) % uint32(m); - uint32 h3 = uint32(uint256(h) >> 64) % uint32(m); - - uint256 fpMask = (uint256(1) << s.fingerprintSize) - 1; - uint256 expectedFp = (uint256(h) >> 96) & fpMask; - - uint256 f1 = _readFingerprint(pointer, h1, s.fingerprintSize); - uint256 f2 = _readFingerprint(pointer, h2, s.fingerprintSize); - uint256 f3 = _readFingerprint(pointer, h3, s.fingerprintSize); - - return (f1 ^ f2 ^ f3) == expectedFp; - } - - /** - * @dev O(1) removal of a swarm from its UUID's swarm list using index tracking. - */ - function _removeFromUuidSwarms(bytes16 fleetUuid, uint256 swarmId) internal { - uint256[] storage arr = uuidSwarms[fleetUuid]; - uint256 index = swarmIndexInUuid[swarmId]; - uint256 lastId = arr[arr.length - 1]; - - arr[index] = lastId; - swarmIndexInUuid[lastId] = index; - arr.pop(); - delete swarmIndexInUuid[swarmId]; - } - - /** - * @dev Reads a packed fingerprint of arbitrary bit size from SSTORE2 blob. - * @param pointer The contract address storing data. - * @param index The slot index. - * @param bits The bit size of the fingerprint. - */ - function _readFingerprint(address pointer, uint256 index, uint8 bits) internal view returns (uint256) { - uint256 bitOffset = index * bits; - uint256 startByte = bitOffset / 8; - uint256 endByte = (bitOffset + bits - 1) / 8; - - // Read raw bytes. SSTORE2 uses 0-based index relative to data. - bytes memory chunk = SSTORE2.read(pointer, startByte, endByte + 1); - - // Convert chunk to uint256 - uint256 raw; - for (uint256 i = 0; i < chunk.length;) { - raw = (raw << 8) | uint8(chunk[i]); - unchecked { - ++i; - } - } - - uint256 totalBitsRead = chunk.length * 8; - uint256 localStart = bitOffset % 8; - uint256 shiftRight = totalBitsRead - (localStart + bits); - - return (raw >> shiftRight) & ((uint256(1) << bits) - 1); - } -} diff --git a/src/swarms/SwarmRegistryL1Upgradeable.sol b/src/swarms/SwarmRegistryL1Upgradeable.sol new file mode 100644 index 00000000..31ed8399 --- /dev/null +++ b/src/swarms/SwarmRegistryL1Upgradeable.sol @@ -0,0 +1,420 @@ +// SPDX-License-Identifier: BSD-3-Clause-Clear + +pragma solidity ^0.8.24; + +// NOTE: SSTORE2 is not compatible with ZkSync Era due to EXTCODECOPY limitation. +// For ZkSync deployment, use SwarmRegistryUniversalUpgradeable instead. +import {SSTORE2} from "solady/utils/SSTORE2.sol"; +import {Ownable2StepUpgradeable} from "@openzeppelin/contracts-upgradeable/access/Ownable2StepUpgradeable.sol"; +import {UUPSUpgradeable} from "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol"; +import {Initializable} from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol"; +import {ReentrancyGuard} from "solady/utils/ReentrancyGuard.sol"; + +// Import interfaces only - the registry stores proxy addresses +interface IFleetIdentity { + function uuidOwner(bytes16 uuid) external view returns (address); +} + +interface IServiceProvider { + function ownerOf(uint256 tokenId) external view returns (address); +} + +/** + * @title SwarmRegistryL1Upgradeable + * @notice UUPS-upgradeable permissionless BLE swarm registry optimized for Ethereum L1 (uses SSTORE2 for filter storage). + * @dev Not compatible with ZkSync Era — use SwarmRegistryUniversalUpgradeable instead. + * + * **Upgrade Pattern:** + * - Uses OpenZeppelin UUPS proxy pattern for upgradeability. + * - Only the contract owner can authorize upgrades. + * - Storage layout must be preserved across upgrades (append-only). + * + * **Important:** The FleetIdentity and ServiceProvider addresses should point to + * **proxy addresses** (stable), not implementation addresses. + * + * **L1-Only:** This contract uses SSTORE2 which relies on EXTCODECOPY. + * Build/test WITHOUT --zksync flag: + * ```bash + * forge build --match-path src/swarms/SwarmRegistryL1Upgradeable.sol + * forge test --match-path test/SwarmRegistryL1Upgradeable.t.sol + * ``` + */ +contract SwarmRegistryL1Upgradeable is Initializable, Ownable2StepUpgradeable, UUPSUpgradeable, ReentrancyGuard { + // ────────────────────────────────────────────── + // Errors + // ────────────────────────────────────────────── + error InvalidFingerprintSize(); + error InvalidFilterSize(); + error InvalidUuid(); + error NotUuidOwner(); + error ProviderDoesNotExist(); + error NotProviderOwner(); + error SwarmNotFound(); + error InvalidSwarmData(); + error SwarmAlreadyExists(); + error SwarmNotOrphaned(); + error SwarmOrphaned(); + + // ────────────────────────────────────────────── + // Enums & Structs + // ────────────────────────────────────────────── + enum SwarmStatus { + REGISTERED, + ACCEPTED, + REJECTED + } + + enum TagType { + IBEACON_PAYLOAD_ONLY, // 0x00: proxUUID || major || minor + IBEACON_INCLUDES_MAC, // 0x01: proxUUID || major || minor || MAC (Normalized) + VENDOR_ID, // 0x02: companyID || hash(vendorBytes) + GENERIC // 0x03 + } + + struct Swarm { + bytes16 fleetUuid; + uint256 providerId; + address filterPointer; // SSTORE2 pointer + uint8 fingerprintSize; + TagType tagType; + SwarmStatus status; + } + + // ────────────────────────────────────────────── + // Constants + // ────────────────────────────────────────────── + uint8 public constant MAX_FINGERPRINT_SIZE = 16; + + // ────────────────────────────────────────────── + // Storage (V1) - Order matters for upgrades! + // ────────────────────────────────────────────── + + /// @notice The FleetIdentity contract (proxy address). + /// @dev In non-upgradeable version this was immutable. + IFleetIdentity private _fleetContract; + + /// @notice The ServiceProvider contract (proxy address). + /// @dev In non-upgradeable version this was immutable. + IServiceProvider private _providerContract; + + /// @notice SwarmID -> Swarm metadata + mapping(uint256 => Swarm) public swarms; + + /// @notice UUID -> List of SwarmIDs + mapping(bytes16 => uint256[]) public uuidSwarms; + + /// @notice SwarmID -> index in uuidSwarms[fleetUuid] (for O(1) removal) + mapping(uint256 => uint256) public swarmIndexInUuid; + + // ────────────────────────────────────────────── + // Storage Gap (for future upgrades) + // ────────────────────────────────────────────── + + /// @dev Reserved storage slots for future upgrades. + // solhint-disable-next-line var-name-mixedcase + uint256[45] private __gap; + + // ────────────────────────────────────────────── + // Events + // ────────────────────────────────────────────── + event SwarmRegistered(uint256 indexed swarmId, bytes16 indexed fleetUuid, uint256 indexed providerId, address owner); + event SwarmStatusChanged(uint256 indexed swarmId, SwarmStatus status); + event SwarmProviderUpdated(uint256 indexed swarmId, uint256 indexed oldProvider, uint256 indexed newProvider); + event SwarmDeleted(uint256 indexed swarmId, bytes16 indexed fleetUuid, address indexed owner); + event SwarmPurged(uint256 indexed swarmId, bytes16 indexed fleetUuid, address indexed purgedBy); + + // ────────────────────────────────────────────── + // Constructor (disables initializers on implementation) + // ────────────────────────────────────────────── + + /// @custom:oz-upgrades-unsafe-allow constructor + constructor() { + _disableInitializers(); + } + + // ────────────────────────────────────────────── + // Initializer + // ────────────────────────────────────────────── + + /// @notice Initializes the contract. Must be called once via proxy. + /// @param fleetContract_ Address of the FleetIdentity proxy contract. + /// @param providerContract_ Address of the ServiceProvider proxy contract. + /// @param owner_ The address that will own this contract and can authorize upgrades. + function initialize(address fleetContract_, address providerContract_, address owner_) external initializer { + if (fleetContract_ == address(0) || providerContract_ == address(0)) { + revert InvalidSwarmData(); + } + + __Ownable_init(owner_); + __Ownable2Step_init(); + + _fleetContract = IFleetIdentity(fleetContract_); + _providerContract = IServiceProvider(providerContract_); + } + + // ────────────────────────────────────────────── + // Public Getters for former immutables + // ────────────────────────────────────────────── + + /// @notice Returns the FleetIdentity contract address. + function FLEET_CONTRACT() external view returns (IFleetIdentity) { + return _fleetContract; + } + + /// @notice Returns the ServiceProvider contract address. + function PROVIDER_CONTRACT() external view returns (IServiceProvider) { + return _providerContract; + } + + // ────────────────────────────────────────────── + // Pure Functions + // ────────────────────────────────────────────── + + /// @notice Derives a deterministic swarm ID. + function computeSwarmId(bytes16 fleetUuid, bytes calldata filterData_, uint8 fingerprintSize, TagType tagType) + public + pure + returns (uint256) + { + return uint256(keccak256(abi.encode(fleetUuid, filterData_, fingerprintSize, tagType))); + } + + // ────────────────────────────────────────────── + // Core Functions + // ────────────────────────────────────────────── + + /// @notice Registers a new swarm. Caller must own the fleet UUID. + function registerSwarm( + bytes16 fleetUuid, + uint256 providerId, + bytes calldata filterData_, + uint8 fingerprintSize, + TagType tagType + ) external nonReentrant returns (uint256 swarmId) { + if (fleetUuid == bytes16(0)) { + revert InvalidUuid(); + } + if (fingerprintSize == 0 || fingerprintSize > MAX_FINGERPRINT_SIZE) { + revert InvalidFingerprintSize(); + } + if (filterData_.length == 0 || filterData_.length > 24576) { + revert InvalidFilterSize(); + } + + if (_fleetContract.uuidOwner(fleetUuid) != msg.sender) { + revert NotUuidOwner(); + } + try _providerContract.ownerOf(providerId) returns (address) {} + catch { + revert ProviderDoesNotExist(); + } + + swarmId = computeSwarmId(fleetUuid, filterData_, fingerprintSize, tagType); + + if (swarms[swarmId].filterPointer != address(0)) { + revert SwarmAlreadyExists(); + } + + Swarm storage s = swarms[swarmId]; + s.fleetUuid = fleetUuid; + s.providerId = providerId; + s.fingerprintSize = fingerprintSize; + s.tagType = tagType; + s.status = SwarmStatus.REGISTERED; + + uuidSwarms[fleetUuid].push(swarmId); + swarmIndexInUuid[swarmId] = uuidSwarms[fleetUuid].length - 1; + + s.filterPointer = SSTORE2.write(filterData_); + + emit SwarmRegistered(swarmId, fleetUuid, providerId, msg.sender); + } + + /// @notice Approves a swarm. Caller must own the provider NFT. + function acceptSwarm(uint256 swarmId) external { + Swarm storage s = swarms[swarmId]; + if (s.filterPointer == address(0)) revert SwarmNotFound(); + + (bool fleetValid, bool providerValid) = isSwarmValid(swarmId); + if (!fleetValid || !providerValid) revert SwarmOrphaned(); + + if (_providerContract.ownerOf(s.providerId) != msg.sender) { + revert NotProviderOwner(); + } + s.status = SwarmStatus.ACCEPTED; + emit SwarmStatusChanged(swarmId, SwarmStatus.ACCEPTED); + } + + /// @notice Rejects a swarm. Caller must own the provider NFT. + function rejectSwarm(uint256 swarmId) external { + Swarm storage s = swarms[swarmId]; + if (s.filterPointer == address(0)) revert SwarmNotFound(); + + (bool fleetValid, bool providerValid) = isSwarmValid(swarmId); + if (!fleetValid || !providerValid) revert SwarmOrphaned(); + + if (_providerContract.ownerOf(s.providerId) != msg.sender) { + revert NotProviderOwner(); + } + s.status = SwarmStatus.REJECTED; + emit SwarmStatusChanged(swarmId, SwarmStatus.REJECTED); + } + + /// @notice Reassigns the service provider. Resets status to REGISTERED. + function updateSwarmProvider(uint256 swarmId, uint256 newProviderId) external { + Swarm storage s = swarms[swarmId]; + if (s.filterPointer == address(0)) { + revert SwarmNotFound(); + } + if (_fleetContract.uuidOwner(s.fleetUuid) != msg.sender) { + revert NotUuidOwner(); + } + try _providerContract.ownerOf(newProviderId) returns (address) {} + catch { + revert ProviderDoesNotExist(); + } + + uint256 oldProvider = s.providerId; + + s.providerId = newProviderId; + s.status = SwarmStatus.REGISTERED; + + emit SwarmProviderUpdated(swarmId, oldProvider, newProviderId); + } + + /// @notice Permanently deletes a swarm. Caller must own the fleet UUID. + function deleteSwarm(uint256 swarmId) external { + Swarm storage s = swarms[swarmId]; + if (s.filterPointer == address(0)) { + revert SwarmNotFound(); + } + if (_fleetContract.uuidOwner(s.fleetUuid) != msg.sender) { + revert NotUuidOwner(); + } + + bytes16 fleetUuid = s.fleetUuid; + + _removeFromUuidSwarms(fleetUuid, swarmId); + + delete swarms[swarmId]; + + emit SwarmDeleted(swarmId, fleetUuid, msg.sender); + } + + /// @notice Returns whether the swarm's fleet UUID and provider NFT are still valid. + function isSwarmValid(uint256 swarmId) public view returns (bool fleetValid, bool providerValid) { + Swarm storage s = swarms[swarmId]; + if (s.filterPointer == address(0)) revert SwarmNotFound(); + + fleetValid = _fleetContract.uuidOwner(s.fleetUuid) != address(0); + + try _providerContract.ownerOf(s.providerId) returns (address) { + providerValid = true; + } catch { + providerValid = false; + } + } + + /// @notice Permissionless-ly removes an orphaned swarm. + function purgeOrphanedSwarm(uint256 swarmId) external { + Swarm storage s = swarms[swarmId]; + if (s.filterPointer == address(0)) revert SwarmNotFound(); + + (bool fleetValid, bool providerValid) = isSwarmValid(swarmId); + if (fleetValid && providerValid) revert SwarmNotOrphaned(); + + bytes16 fleetUuid = s.fleetUuid; + + _removeFromUuidSwarms(fleetUuid, swarmId); + + delete swarms[swarmId]; + + emit SwarmPurged(swarmId, fleetUuid, msg.sender); + } + + /// @notice Tests tag membership against the swarm's XOR filter. + function checkMembership(uint256 swarmId, bytes32 tagHash) external view returns (bool isValid) { + Swarm storage s = swarms[swarmId]; + if (s.filterPointer == address(0)) { + revert SwarmNotFound(); + } + + (bool fleetValid, bool providerValid) = isSwarmValid(swarmId); + if (!fleetValid || !providerValid) revert SwarmOrphaned(); + + uint256 dataLen; + address pointer = s.filterPointer; + assembly { + dataLen := extcodesize(pointer) + } + + // SSTORE2 adds 1 byte overhead (0x00) + if (dataLen > 0) { + unchecked { + --dataLen; + } + } + + uint256 m = (dataLen * 8) / s.fingerprintSize; + if (m == 0) return false; + + bytes32 h = tagHash; + + uint32 h1 = uint32(uint256(h)) % uint32(m); + uint32 h2 = uint32(uint256(h) >> 32) % uint32(m); + uint32 h3 = uint32(uint256(h) >> 64) % uint32(m); + + uint256 fpMask = (uint256(1) << s.fingerprintSize) - 1; + uint256 expectedFp = (uint256(h) >> 96) & fpMask; + + uint256 f1 = _readFingerprint(pointer, h1, s.fingerprintSize); + uint256 f2 = _readFingerprint(pointer, h2, s.fingerprintSize); + uint256 f3 = _readFingerprint(pointer, h3, s.fingerprintSize); + + return (f1 ^ f2 ^ f3) == expectedFp; + } + + // ────────────────────────────────────────────── + // UUPS Authorization + // ────────────────────────────────────────────── + + /// @dev Only the owner can authorize an upgrade. + function _authorizeUpgrade(address newImplementation) internal override onlyOwner {} + + // ────────────────────────────────────────────── + // Internal Functions + // ────────────────────────────────────────────── + + function _removeFromUuidSwarms(bytes16 fleetUuid, uint256 swarmId) internal { + uint256[] storage arr = uuidSwarms[fleetUuid]; + uint256 index = swarmIndexInUuid[swarmId]; + uint256 lastId = arr[arr.length - 1]; + + arr[index] = lastId; + swarmIndexInUuid[lastId] = index; + arr.pop(); + delete swarmIndexInUuid[swarmId]; + } + + function _readFingerprint(address pointer, uint256 index, uint8 bits) internal view returns (uint256) { + uint256 bitOffset = index * bits; + uint256 startByte = bitOffset / 8; + uint256 endByte = (bitOffset + bits - 1) / 8; + + bytes memory chunk = SSTORE2.read(pointer, startByte, endByte + 1); + + uint256 raw; + for (uint256 i = 0; i < chunk.length;) { + raw = (raw << 8) | uint8(chunk[i]); + unchecked { + ++i; + } + } + + uint256 totalBitsRead = chunk.length * 8; + uint256 localStart = bitOffset % 8; + uint256 shiftRight = totalBitsRead - (localStart + bits); + + return (raw >> shiftRight) & ((uint256(1) << bits) - 1); + } +} diff --git a/src/swarms/SwarmRegistryUniversal.sol b/src/swarms/SwarmRegistryUniversalUpgradeable.sol similarity index 53% rename from src/swarms/SwarmRegistryUniversal.sol rename to src/swarms/SwarmRegistryUniversalUpgradeable.sol index a8f80185..a3486282 100644 --- a/src/swarms/SwarmRegistryUniversal.sol +++ b/src/swarms/SwarmRegistryUniversalUpgradeable.sol @@ -2,21 +2,49 @@ pragma solidity ^0.8.24; -import {ReentrancyGuard} from "@openzeppelin/contracts/utils/ReentrancyGuard.sol"; -import {FleetIdentity} from "./FleetIdentity.sol"; -import {ServiceProvider} from "./ServiceProvider.sol"; +import {Ownable2StepUpgradeable} from "@openzeppelin/contracts-upgradeable/access/Ownable2StepUpgradeable.sol"; +import {UUPSUpgradeable} from "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol"; +import {Initializable} from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol"; +import {ReentrancyGuard} from "solady/utils/ReentrancyGuard.sol"; + +// Import interfaces only - the registry stores proxy addresses +interface IFleetIdentity { + function uuidOwner(bytes16 uuid) external view returns (address); +} + +interface IServiceProvider { + function ownerOf(uint256 tokenId) external view returns (address); +} /** - * @title SwarmRegistryUniversal - * @notice Permissionless BLE swarm registry compatible with all EVM chains (including ZkSync Era). + * @title SwarmRegistryUniversalUpgradeable + * @notice UUPS-upgradeable permissionless BLE swarm registry compatible with all EVM chains (including ZkSync Era). * @dev Uses native `bytes` storage for cross-chain compatibility. * - * Swarms are defined for a **fleet UUID** (not a token ID), allowing swarms to be - * registered for any UUID that has been claimed/registered in FleetIdentity, - * regardless of whether it's assigned to a region or is in "owned-only" mode. - * This decouples swarm management from geographic tier placement. + * **Upgrade Pattern:** + * - Uses OpenZeppelin UUPS proxy pattern for upgradeability. + * - Only the contract owner can authorize upgrades. + * - Storage layout must be preserved across upgrades (append-only). + * + * **Important:** The FleetIdentity and ServiceProvider addresses should point to + * **proxy addresses** (stable), not implementation addresses. + * + * **Storage Migration Example (V1 → V2):** + * ```solidity + * function initializeV2(uint256 newParam) external reinitializer(2) { + * _newParamIntroducedInV2 = newParam; + * } + * ``` */ -contract SwarmRegistryUniversal is ReentrancyGuard { +contract SwarmRegistryUniversalUpgradeable is + Initializable, + Ownable2StepUpgradeable, + UUPSUpgradeable, + ReentrancyGuard +{ + // ────────────────────────────────────────────── + // Errors + // ────────────────────────────────────────────── error InvalidFingerprintSize(); error InvalidFilterSize(); error InvalidUuid(); @@ -30,6 +58,9 @@ contract SwarmRegistryUniversal is ReentrancyGuard { error SwarmNotOrphaned(); error SwarmOrphaned(); + // ────────────────────────────────────────────── + // Enums & Structs + // ────────────────────────────────────────────── enum SwarmStatus { REGISTERED, ACCEPTED, @@ -41,26 +72,36 @@ contract SwarmRegistryUniversal is ReentrancyGuard { IBEACON_INCLUDES_MAC, // 0x01: proxUUID || major || minor || MAC (Normalized) VENDOR_ID, // 0x02: companyID || hash(vendorBytes) GENERIC // 0x03 - } struct Swarm { - bytes16 fleetUuid; // Fleet UUID (not token ID) - allows swarms for any registered UUID + bytes16 fleetUuid; uint256 providerId; - uint32 filterLength; // Length of filter in bytes (max ~4GB, practically limited) + uint32 filterLength; uint8 fingerprintSize; TagType tagType; SwarmStatus status; } + // ────────────────────────────────────────────── + // Constants + // ────────────────────────────────────────────── uint8 public constant MAX_FINGERPRINT_SIZE = 16; - /// @notice Maximum filter size per swarm (24KB - fits in ~15M gas on cold write) + /// @notice Maximum filter size per swarm (24KB) uint32 public constant MAX_FILTER_SIZE = 24576; - FleetIdentity public immutable FLEET_CONTRACT; + // ────────────────────────────────────────────── + // Storage (V1) - Order matters for upgrades! + // ────────────────────────────────────────────── - ServiceProvider public immutable PROVIDER_CONTRACT; + /// @notice The FleetIdentity contract (proxy address). + /// @dev In non-upgradeable version this was immutable. + IFleetIdentity private _fleetContract; + + /// @notice The ServiceProvider contract (proxy address). + /// @dev In non-upgradeable version this was immutable. + IServiceProvider private _providerContract; /// @notice SwarmID -> Swarm metadata mapping(uint256 => Swarm) public swarms; @@ -68,24 +109,79 @@ contract SwarmRegistryUniversal is ReentrancyGuard { /// @notice SwarmID -> XOR filter data (stored as bytes) mapping(uint256 => bytes) internal filterData; - /// @notice UUID -> List of SwarmIDs (keyed by fleet UUID, not token ID) + /// @notice UUID -> List of SwarmIDs mapping(bytes16 => uint256[]) public uuidSwarms; /// @notice SwarmID -> index in uuidSwarms[fleetUuid] (for O(1) removal) mapping(uint256 => uint256) public swarmIndexInUuid; + // ────────────────────────────────────────────── + // Storage Gap (for future upgrades) + // ────────────────────────────────────────────── + + /// @dev Reserved storage slots for future upgrades. + // solhint-disable-next-line var-name-mixedcase + uint256[44] private __gap; + + // ────────────────────────────────────────────── + // Events + // ────────────────────────────────────────────── event SwarmRegistered( uint256 indexed swarmId, bytes16 indexed fleetUuid, uint256 indexed providerId, address owner, uint32 filterSize ); - event SwarmStatusChanged(uint256 indexed swarmId, SwarmStatus status); event SwarmProviderUpdated(uint256 indexed swarmId, uint256 indexed oldProvider, uint256 indexed newProvider); event SwarmDeleted(uint256 indexed swarmId, bytes16 indexed fleetUuid, address indexed owner); event SwarmPurged(uint256 indexed swarmId, bytes16 indexed fleetUuid, address indexed purgedBy); - /// @notice Derives a deterministic swarm ID. Callable off-chain to predict IDs before registration. - /// @dev Swarm identity is based on fleet, filter, fingerprintSize, and tagType. ProviderId is mutable and not part of identity. - /// @return swarmId keccak256(fleetUuid, filter, fingerprintSize, tagType) + // ────────────────────────────────────────────── + // Constructor (disables initializers on implementation) + // ────────────────────────────────────────────── + + /// @custom:oz-upgrades-unsafe-allow constructor + constructor() { + _disableInitializers(); + } + + // ────────────────────────────────────────────── + // Initializer + // ────────────────────────────────────────────── + + /// @notice Initializes the contract. Must be called once via proxy. + /// @param fleetContract_ Address of the FleetIdentity proxy contract. + /// @param providerContract_ Address of the ServiceProvider proxy contract. + /// @param owner_ The address that will own this contract and can authorize upgrades. + function initialize(address fleetContract_, address providerContract_, address owner_) external initializer { + if (fleetContract_ == address(0) || providerContract_ == address(0)) { + revert InvalidSwarmData(); + } + + __Ownable_init(owner_); + __Ownable2Step_init(); + + _fleetContract = IFleetIdentity(fleetContract_); + _providerContract = IServiceProvider(providerContract_); + } + + // ────────────────────────────────────────────── + // Public Getters for former immutables + // ────────────────────────────────────────────── + + /// @notice Returns the FleetIdentity contract address. + function FLEET_CONTRACT() external view returns (IFleetIdentity) { + return _fleetContract; + } + + /// @notice Returns the ServiceProvider contract address. + function PROVIDER_CONTRACT() external view returns (IServiceProvider) { + return _providerContract; + } + + // ────────────────────────────────────────────── + // Pure Functions + // ────────────────────────────────────────────── + + /// @notice Derives a deterministic swarm ID. function computeSwarmId(bytes16 fleetUuid, bytes calldata filter, uint8 fingerprintSize, TagType tagType) public pure @@ -94,21 +190,11 @@ contract SwarmRegistryUniversal is ReentrancyGuard { return uint256(keccak256(abi.encode(fleetUuid, filter, fingerprintSize, tagType))); } - constructor(address _fleetContract, address _providerContract) { - if (_fleetContract == address(0) || _providerContract == address(0)) { - revert InvalidSwarmData(); - } - FLEET_CONTRACT = FleetIdentity(_fleetContract); - PROVIDER_CONTRACT = ServiceProvider(_providerContract); - } + // ────────────────────────────────────────────── + // Core Functions + // ────────────────────────────────────────────── - /// @notice Registers a new swarm. Caller must own the fleet UUID (via FleetIdentity.uuidOwner). - /// @param fleetUuid Fleet UUID (bytes16) - the UUID must be registered in FleetIdentity. - /// @param providerId Service provider token ID. - /// @param filter XOR filter blob (1–24 576 bytes). - /// @param fingerprintSize Fingerprint width in bits (1–16). - /// @param tagType Tag identity schema. - /// @return swarmId Deterministic ID for this swarm. + /// @notice Registers a new swarm. Caller must own the fleet UUID. function registerSwarm( bytes16 fleetUuid, uint256 providerId, @@ -129,11 +215,10 @@ contract SwarmRegistryUniversal is ReentrancyGuard { revert FilterTooLarge(); } - // Check UUID ownership - works for any registered UUID regardless of region - if (FLEET_CONTRACT.uuidOwner(fleetUuid) != msg.sender) { + if (_fleetContract.uuidOwner(fleetUuid) != msg.sender) { revert NotUuidOwner(); } - try PROVIDER_CONTRACT.ownerOf(providerId) returns (address) {} + try _providerContract.ownerOf(providerId) returns (address) {} catch { revert ProviderDoesNotExist(); } @@ -161,7 +246,6 @@ contract SwarmRegistryUniversal is ReentrancyGuard { } /// @notice Approves a swarm. Caller must own the provider NFT. - /// @param swarmId The swarm to accept. function acceptSwarm(uint256 swarmId) external { Swarm storage s = swarms[swarmId]; if (s.filterLength == 0) revert SwarmNotFound(); @@ -169,7 +253,7 @@ contract SwarmRegistryUniversal is ReentrancyGuard { (bool fleetValid, bool providerValid) = isSwarmValid(swarmId); if (!fleetValid || !providerValid) revert SwarmOrphaned(); - if (PROVIDER_CONTRACT.ownerOf(s.providerId) != msg.sender) { + if (_providerContract.ownerOf(s.providerId) != msg.sender) { revert NotProviderOwner(); } s.status = SwarmStatus.ACCEPTED; @@ -177,7 +261,6 @@ contract SwarmRegistryUniversal is ReentrancyGuard { } /// @notice Rejects a swarm. Caller must own the provider NFT. - /// @param swarmId The swarm to reject. function rejectSwarm(uint256 swarmId) external { Swarm storage s = swarms[swarmId]; if (s.filterLength == 0) revert SwarmNotFound(); @@ -185,32 +268,29 @@ contract SwarmRegistryUniversal is ReentrancyGuard { (bool fleetValid, bool providerValid) = isSwarmValid(swarmId); if (!fleetValid || !providerValid) revert SwarmOrphaned(); - if (PROVIDER_CONTRACT.ownerOf(s.providerId) != msg.sender) { + if (_providerContract.ownerOf(s.providerId) != msg.sender) { revert NotProviderOwner(); } s.status = SwarmStatus.REJECTED; emit SwarmStatusChanged(swarmId, SwarmStatus.REJECTED); } - /// @notice Reassigns the service provider. Resets status to REGISTERED. Caller must own the fleet UUID. - /// @param swarmId The swarm to update. - /// @param newProviderId New provider token ID. + /// @notice Reassigns the service provider. Resets status to REGISTERED. function updateSwarmProvider(uint256 swarmId, uint256 newProviderId) external { Swarm storage s = swarms[swarmId]; if (s.filterLength == 0) { revert SwarmNotFound(); } - if (FLEET_CONTRACT.uuidOwner(s.fleetUuid) != msg.sender) { + if (_fleetContract.uuidOwner(s.fleetUuid) != msg.sender) { revert NotUuidOwner(); } - try PROVIDER_CONTRACT.ownerOf(newProviderId) returns (address) {} + try _providerContract.ownerOf(newProviderId) returns (address) {} catch { revert ProviderDoesNotExist(); } uint256 oldProvider = s.providerId; - // Effects — update provider and reset status s.providerId = newProviderId; s.status = SwarmStatus.REGISTERED; @@ -218,13 +298,12 @@ contract SwarmRegistryUniversal is ReentrancyGuard { } /// @notice Permanently deletes a swarm. Caller must own the fleet UUID. - /// @param swarmId The swarm to delete. function deleteSwarm(uint256 swarmId) external { Swarm storage s = swarms[swarmId]; if (s.filterLength == 0) { revert SwarmNotFound(); } - if (FLEET_CONTRACT.uuidOwner(s.fleetUuid) != msg.sender) { + if (_fleetContract.uuidOwner(s.fleetUuid) != msg.sender) { revert NotUuidOwner(); } @@ -239,25 +318,20 @@ contract SwarmRegistryUniversal is ReentrancyGuard { } /// @notice Returns whether the swarm's fleet UUID and provider NFT are still valid. - /// @param swarmId The swarm to check. - /// @return fleetValid True if the fleet UUID is still owned (uuidOwner != address(0)). - /// @return providerValid True if the provider NFT exists. function isSwarmValid(uint256 swarmId) public view returns (bool fleetValid, bool providerValid) { Swarm storage s = swarms[swarmId]; if (s.filterLength == 0) revert SwarmNotFound(); - // Fleet is valid if UUID is still owned (not released) - fleetValid = FLEET_CONTRACT.uuidOwner(s.fleetUuid) != address(0); + fleetValid = _fleetContract.uuidOwner(s.fleetUuid) != address(0); - try PROVIDER_CONTRACT.ownerOf(s.providerId) returns (address) { + try _providerContract.ownerOf(s.providerId) returns (address) { providerValid = true; } catch { providerValid = false; } } - /// @notice Permissionless-ly removes a swarm whose fleet UUID has been released or provider NFT has been burned. - /// @param swarmId The orphaned swarm to purge. + /// @notice Permissionless-ly removes an orphaned swarm. function purgeOrphanedSwarm(uint256 swarmId) external { Swarm storage s = swarms[swarmId]; if (s.filterLength == 0) revert SwarmNotFound(); @@ -276,27 +350,21 @@ contract SwarmRegistryUniversal is ReentrancyGuard { } /// @notice Tests tag membership against the swarm's XOR filter. - /// @param swarmId The swarm to query. - /// @param tagHash keccak256 of the tag identity bytes (caller must pre-normalize per tagType). - /// @return isValid True if the tag passes the XOR filter check. function checkMembership(uint256 swarmId, bytes32 tagHash) external view returns (bool isValid) { Swarm storage s = swarms[swarmId]; if (s.filterLength == 0) { revert SwarmNotFound(); } - // Reject queries against orphaned swarms (bool fleetValid, bool providerValid) = isSwarmValid(swarmId); if (!fleetValid || !providerValid) revert SwarmOrphaned(); bytes storage filter = filterData[swarmId]; uint256 dataLen = s.filterLength; - // Calculate M (number of fingerprint slots) uint256 m = (dataLen * 8) / s.fingerprintSize; if (m == 0) return false; - // Derive 3 indices and expected fingerprint from hash uint32 h1 = uint32(uint256(tagHash)) % uint32(m); uint32 h2 = uint32(uint256(tagHash) >> 32) % uint32(m); uint32 h3 = uint32(uint256(tagHash) >> 64) % uint32(m); @@ -304,7 +372,6 @@ contract SwarmRegistryUniversal is ReentrancyGuard { uint256 fpMask = (uint256(1) << s.fingerprintSize) - 1; uint256 expectedFp = (uint256(tagHash) >> 96) & fpMask; - // Read and XOR fingerprints uint256 f1 = _readFingerprint(filter, h1, s.fingerprintSize); uint256 f2 = _readFingerprint(filter, h2, s.fingerprintSize); uint256 f3 = _readFingerprint(filter, h3, s.fingerprintSize); @@ -313,8 +380,6 @@ contract SwarmRegistryUniversal is ReentrancyGuard { } /// @notice Returns the raw XOR filter bytes for a swarm. - /// @param swarmId The swarm to query. - /// @return filter The XOR filter blob. function getFilterData(uint256 swarmId) external view returns (bytes memory filter) { if (swarms[swarmId].filterLength == 0) { revert SwarmNotFound(); @@ -322,9 +387,17 @@ contract SwarmRegistryUniversal is ReentrancyGuard { return filterData[swarmId]; } - /** - * @dev O(1) removal of a swarm from its UUID's swarm list using index tracking. - */ + // ────────────────────────────────────────────── + // UUPS Authorization + // ────────────────────────────────────────────── + + /// @dev Only the owner can authorize an upgrade. + function _authorizeUpgrade(address newImplementation) internal override onlyOwner {} + + // ────────────────────────────────────────────── + // Internal Functions + // ────────────────────────────────────────────── + function _removeFromUuidSwarms(bytes16 fleetUuid, uint256 swarmId) internal { uint256[] storage arr = uuidSwarms[fleetUuid]; uint256 index = swarmIndexInUuid[swarmId]; @@ -336,18 +409,11 @@ contract SwarmRegistryUniversal is ReentrancyGuard { delete swarmIndexInUuid[swarmId]; } - /** - * @dev Reads a packed fingerprint from storage bytes. - * @param filter The filter bytes in storage. - * @param index The fingerprint slot index. - * @param bits The fingerprint size in bits. - */ function _readFingerprint(bytes storage filter, uint256 index, uint8 bits) internal view returns (uint256) { uint256 bitOffset = index * bits; uint256 startByte = bitOffset / 8; uint256 endByte = (bitOffset + bits - 1) / 8; - // Read bytes and assemble into uint256 uint256 raw; for (uint256 i = startByte; i <= endByte;) { raw = (raw << 8) | uint8(filter[i]); @@ -356,7 +422,6 @@ contract SwarmRegistryUniversal is ReentrancyGuard { } } - // Extract the fingerprint bits uint256 totalBitsRead = (endByte - startByte + 1) * 8; uint256 localStart = bitOffset % 8; uint256 shiftRight = totalBitsRead - (localStart + bits); diff --git a/src/swarms/doc/README.md b/src/swarms/doc/README.md index 5e73b805..497e196a 100644 --- a/src/swarms/doc/README.md +++ b/src/swarms/doc/README.md @@ -112,15 +112,16 @@ The system provides **non-enumerating** tag verification—individual tags aren' ## Documentation -| Document | Description | -| :--------------------------------------------- | :------------------------------------------------ | -| [data-model.md](data-model.md) | Contract interfaces, enums, storage layout | -| [fleet-registration.md](fleet-registration.md) | Fleet & UUID registration, tier economics | -| [swarm-operations.md](swarm-operations.md) | Swarm registration, filters, provider approval | -| [lifecycle.md](lifecycle.md) | State machines, updates, deletion, orphan cleanup | -| [discovery.md](discovery.md) | Client discovery flows, tag hash construction | -| [maintenance.md](maintenance.md) | Bundle inclusion monitoring, tier optimization | -| [iso3166-reference.md](iso3166-reference.md) | ISO 3166-1/2 codes and admin area mappings | +| Document | Description | +| :--------------------------------------------------- | :------------------------------------------------ | +| [data-model.md](data-model.md) | Contract interfaces, enums, storage layout | +| [fleet-registration.md](fleet-registration.md) | Fleet & UUID registration, tier economics | +| [swarm-operations.md](swarm-operations.md) | Swarm registration, filters, provider approval | +| [lifecycle.md](lifecycle.md) | State machines, updates, deletion, orphan cleanup | +| [discovery.md](discovery.md) | Client discovery flows, tag hash construction | +| [maintenance.md](maintenance.md) | Bundle inclusion monitoring, tier optimization | +| [iso3166-reference.md](iso3166-reference.md) | ISO 3166-1/2 codes and admin area mappings | +| [upgradeable-contracts.md](upgradeable-contracts.md) | UUPS proxy pattern and upgrade procedures | ## End-to-End Flow diff --git a/src/swarms/doc/upgradeable-contracts.md b/src/swarms/doc/upgradeable-contracts.md new file mode 100644 index 00000000..9b8537cb --- /dev/null +++ b/src/swarms/doc/upgradeable-contracts.md @@ -0,0 +1,806 @@ +# Upgradeable Swarm Contracts + +This document covers the UUPS-upgradeable versions of the swarm registry contracts. + +## Table of Contents + +1. [Overview](#overview) +2. [Architecture](#architecture) +3. [Storage Migration](#storage-migration) +4. [Deployment](#deployment) +5. [Interacting with Proxies](#interacting-with-proxies) +6. [Upgrade Process](#upgrade-process) +7. [Emergency Procedures](#emergency-procedures) +8. [Security Considerations](#security-considerations) + +--- + +## Overview + +The following contracts have been converted to UUPS-upgradeable versions: + +| Contract | File | Description | +| ----------------------------------- | -------------------------------------------------- | -------------------------------------------- | +| `ServiceProviderUpgradeable` | `src/swarms/ServiceProviderUpgradeable.sol` | ERC721 for service endpoint URLs | +| `FleetIdentityUpgradeable` | `src/swarms/FleetIdentityUpgradeable.sol` | ERC721Enumerable with tier-based bond system | +| `SwarmRegistryUniversalUpgradeable` | `src/swarms/SwarmRegistryUniversalUpgradeable.sol` | ZkSync-compatible swarm registry | +| `SwarmRegistryL1Upgradeable` | `src/swarms/SwarmRegistryL1Upgradeable.sol` | L1-only registry with SSTORE2 | + +--- + +## Architecture + +### Proxy Pattern + +Each contract is deployed as a pair: + +- **Proxy (ERC1967Proxy)**: Immutable, stores state, forwards calls +- **Implementation**: Contains logic, can be replaced + +``` +┌──────────────────────────┐ +│ ERC1967Proxy │ +│ ┌──────────────────┐ │ +│ │ Implementation │────┼──> FleetIdentityUpgradeable +│ │ Slot │ │ (logic contract) +│ └──────────────────┘ │ +│ ┌──────────────────┐ │ +│ │ Storage │ │ ← bondToken, baseBond, +│ │ (lives here) │ │ fleets, bonds, etc. +│ └──────────────────┘ │ +└──────────────────────────┘ +``` + +### Storage Layout (ERC-7201) + +All upgradeable contracts use namespaced storage with gaps for future expansion: + +```solidity +// Storage variables (inherited from OpenZeppelin) +// + Custom storage at contract-specific slots +// + Storage gap at the end + +uint256[40] private __gap; // FleetIdentity: 40 slots +uint256[49] private __gap; // ServiceProvider: 49 slots +uint256[44] private __gap; // SwarmRegistryUniversal: 44 slots +uint256[45] private __gap; // SwarmRegistryL1: 45 slots +``` + +**Storage Gap Cost**: The `__gap` costs **zero gas** at runtime. Uninitialized storage slots are not written to chain, and the gap is never read or written. It's purely a compile-time placeholder that reserves slot numbers for safe future upgrades. + +--- + +## Storage Migration + +### How Storage Persists Through Upgrades + +When you upgrade a UUPS proxy, **only the logic contract address changes**. All storage remains in the proxy at exactly the same slots. + +**Example: Adding a reputation score to ServiceProvider** + +**Version 1 storage:** + +``` +Slot 0-10: ERC721 state (name, symbol, balances...) +Slot 11: Ownable state (owner) +Slot 12-14: UUPSUpgradeable (empty, stateless) +Slot 15: providerUrls mapping +Slot 16-64: __gap (49 empty slots) +``` + +**Version 2 storage (adding 1 new mapping):** + +``` +Slot 0-10: ERC721 state ← UNCHANGED +Slot 11: Ownable state ← UNCHANGED +Slot 12-14: UUPSUpgradeable ← UNCHANGED +Slot 15: providerUrls mapping ← UNCHANGED +Slot 16: providerScores mapping ← NEW (from gap) +Slot 17-64: __gap (48 empty slots) ← REDUCED BY 1 +``` + +**Key rule**: Existing slot offsets must NEVER change. + +### Safe Upgrade Rules + +1. ✅ **Append new variables** at the end (consume from `__gap`) +2. ✅ **Add new functions** +3. ✅ **Modify function logic** +4. ❌ **Never delete existing variables** +5. ❌ **Never reorder existing variables** +6. ❌ **Never change variable types** (e.g., `uint256` → `uint128`) +7. ❌ **Never insert variables between existing ones** + +### Reinitializer Pattern for V2+ + +When adding new storage that needs initialization: + +```solidity +// In V2 implementation +function initializeV2(uint256 newParam) external reinitializer(2) { + _newParamIntroducedInV2 = newParam; +} +``` + +The `reinitializer(N)` modifier: + +- Ensures this can only run once +- Must be called with N > previous version +- Prevents re-initialization attacks + +### Upgrade with Reinitializer + +```bash +# Generate reinitializer calldata +cast calldata "initializeV2(uint256)" 12345 +# Output: 0x... + +# Execute upgrade with initialization +REINIT_DATA=0x... CONTRACT_TYPE=ServiceProvider PROXY_ADDRESS=0x... \ + forge script script/UpgradeSwarm.s.sol --rpc-url $RPC_URL --broadcast +``` + +--- + +## Deployment + +### Fresh Deployment + +Use the deployment script: + +```bash +# Set environment variables +export DEPLOYER_PRIVATE_KEY=0x... +export OWNER=0x... # Contract owner address +export BOND_TOKEN=0x... # ERC20 token for bonds +export BASE_BOND=1000000000000000000 # 1 token (18 decimals) +export DEPLOY_L1_REGISTRY=false # true for L1, false for ZkSync + +# Deploy +forge script script/DeploySwarmUpgradeable.s.sol \ + --rpc-url $RPC_URL \ + --broadcast +``` + +The script deploys in order: + +1. ServiceProviderUpgradeable + proxy +2. FleetIdentityUpgradeable + proxy +3. SwarmRegistry (L1 or Universal) + proxy + +### Output + +Save these addresses: + +``` +ServiceProvider Proxy: 0x... ← Use this address for interactions +FleetIdentity Proxy: 0x... ← Use this address for interactions +SwarmRegistry Proxy: 0x... ← Use this address for interactions +``` + +--- + +## Interacting with Proxies + +### Important: Always Use the Proxy Address + +When interacting with upgradeable contracts, **always use the proxy address**, never the implementation address. The proxy contains all the state (storage) and forwards calls to the current implementation. + +### TypeScript/Backend Integration + +#### Setup with ethers.js v6 + +```typescript +import { ethers } from "ethers"; +import { Contract, Provider, Wallet } from "ethers"; + +// Import ABIs from your compilation artifacts +import ServiceProviderABI from "./artifacts/ServiceProviderUpgradeable.json"; +import FleetIdentityABI from "./artifacts/FleetIdentityUpgradeable.json"; +import SwarmRegistryABI from "./artifacts/SwarmRegistryUniversalUpgradeable.json"; + +// Proxy addresses (saved from deployment) +const PROXY_ADDRESSES = { + serviceProvider: "0x...", // From deployment output + fleetIdentity: "0x...", + swarmRegistry: "0x...", +}; + +// Setup provider +const provider = new ethers.JsonRpcProvider(process.env.RPC_URL); +const wallet = new ethers.Wallet(process.env.PRIVATE_KEY, provider); + +// Connect to contracts via proxies +const serviceProvider = new ethers.Contract( + PROXY_ADDRESSES.serviceProvider, + ServiceProviderABI.abi, + wallet, +); + +const fleetIdentity = new ethers.Contract( + PROXY_ADDRESSES.fleetIdentity, + FleetIdentityABI.abi, + wallet, +); + +const swarmRegistry = new ethers.Contract( + PROXY_ADDRESSES.swarmRegistry, + SwarmRegistryABI.abi, + wallet, +); +``` + +#### Read-Only Operations + +```typescript +// Query provider endpoint +async function getProviderUrl(tokenId: bigint): Promise { + return await serviceProvider.providerUrls(tokenId); +} + +// Check fleet bond +async function getFleetBond(tokenId: bigint): Promise { + return await fleetIdentity.bonds(tokenId); +} + +// Get swarm details +async function getSwarmInfo(swarmId: bigint) { + const swarm = await swarmRegistry.swarms(swarmId); + return { + fleetUuid: swarm.fleetUuid, + providerId: swarm.providerId, + status: swarm.status, // 0: REGISTERED, 1: ACCEPTED, 2: REJECTED + tagType: swarm.tagType, + }; +} + +// Check tag membership +async function checkMembership( + swarmId: bigint, + tagHash: string, +): Promise { + return await swarmRegistry.checkMembership(swarmId, tagHash); +} + +// Get highest bonded UUIDs for a region +async function discoverFleets( + countryCode: number, + adminCode: number, +): Promise<{ uuids: string[]; count: bigint }> { + const [uuids, count] = await fleetIdentity.buildHighestBondedUuidBundle( + countryCode, + adminCode, + ); + return { uuids, count }; +} +``` + +#### Write Operations + +```typescript +// Register a service provider +async function registerProvider(url: string): Promise { + const tx = await serviceProvider.registerProvider(url); + const receipt = await tx.wait(); + + // Extract tokenId from event + const event = receipt.logs + .map((log) => serviceProvider.interface.parseLog(log)) + .find((e) => e?.name === "ProviderRegistered"); + + return event.args.tokenId; +} + +// Claim a UUID +async function claimUuid( + uuid: string, // bytes16 as hex string + operator: string, +): Promise { + // Approve bond token first + const bondToken = new ethers.Contract( + await fleetIdentity.BOND_TOKEN(), + ["function approve(address,uint256)"], + wallet, + ); + + const baseBond = await fleetIdentity.BASE_BOND(); + await (await bondToken.approve(fleetIdentity.target, baseBond)).wait(); + + // Claim UUID + const tx = await fleetIdentity.claimUuid(uuid, operator); + const receipt = await tx.wait(); + + const event = receipt.logs + .map((log) => fleetIdentity.interface.parseLog(log)) + .find((e) => e?.name === "UuidClaimed"); + + return event.args.tokenId; +} + +// Register a swarm +async function registerSwarm( + fleetUuid: string, + providerId: bigint, + filterData: Uint8Array, + fingerprintSize: number, + tagType: number, // 0: IBEACON_PAYLOAD_ONLY, etc. +): Promise { + const tx = await swarmRegistry.registerSwarm( + fleetUuid, + providerId, + filterData, + fingerprintSize, + tagType, + ); + const receipt = await tx.wait(); + + const event = receipt.logs + .map((log) => swarmRegistry.interface.parseLog(log)) + .find((e) => e?.name === "SwarmRegistered"); + + return event.args.swarmId; +} + +// Accept swarm (provider) +async function acceptSwarm(swarmId: bigint): Promise { + const tx = await swarmRegistry.acceptSwarm(swarmId); + await tx.wait(); +} +``` + +#### Error Handling + +```typescript +import { ErrorFragment } from "ethers"; + +try { + await fleetIdentity.claimUuid(uuid, operator); +} catch (error: any) { + // Parse custom errors + if (error.data) { + const iface = fleetIdentity.interface; + const decodedError = iface.parseError(error.data); + + if (decodedError?.name === "UuidAlreadyOwned") { + console.error("UUID is already claimed"); + } else if (decodedError?.name === "InvalidUUID") { + console.error("Invalid UUID format"); + } + } + + // Handle revert reasons + if (error.reason) { + console.error("Revert reason:", error.reason); + } + + throw error; +} +``` + +#### Environment Configuration + +```typescript +// .env +RPC_URL=https://mainnet.era.zksync.io +PRIVATE_KEY=0x... +SERVICE_PROVIDER_PROXY=0x... +FLEET_IDENTITY_PROXY=0x... +SWARM_REGISTRY_PROXY=0x... + +// config.ts +export const CONTRACTS = { + serviceProvider: process.env.SERVICE_PROVIDER_PROXY!, + fleetIdentity: process.env.FLEET_IDENTITY_PROXY!, + swarmRegistry: process.env.SWARM_REGISTRY_PROXY! +}; +``` + +--- + +### Developer/Maintainer Tools + +#### Using Cast (Foundry) + +**Read Operations:** + +```bash +# Set proxy addresses as environment variables +export SERVICE_PROVIDER=0x... +export FLEET_IDENTITY=0x... +export SWARM_REGISTRY=0x... +export RPC_URL=https://mainnet.era.zksync.io + +# Query provider URL +cast call $SERVICE_PROVIDER "providerUrls(uint256)(string)" 12345 --rpc-url $RPC_URL + +# Get fleet bond +cast call $FLEET_IDENTITY "bonds(uint256)(uint256)" 67890 --rpc-url $RPC_URL + +# Get contract owner +cast call $FLEET_IDENTITY "owner()(address)" --rpc-url $RPC_URL + +# Get base bond amount +cast call $FLEET_IDENTITY "BASE_BOND()(uint256)" --rpc-url $RPC_URL + +# Check swarm status +cast call $SWARM_REGISTRY "swarms(uint256)" 101 --rpc-url $RPC_URL + +# Check membership +cast call $SWARM_REGISTRY \ + "checkMembership(uint256,bytes32)(bool)" \ + 101 \ + 0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef \ + --rpc-url $RPC_URL + +# Decode hex output to decimal +cast --to-dec $(cast call $FLEET_IDENTITY "BASE_BOND()(uint256)" --rpc-url $RPC_URL) +``` + +**Write Operations:** + +```bash +# Register a provider +cast send $SERVICE_PROVIDER \ + "registerProvider(string)(uint256)" \ + "https://api.example.com" \ + --rpc-url $RPC_URL \ + --private-key $PRIVATE_KEY + +# Approve bond token (first get bond token address) +BOND_TOKEN=$(cast call $FLEET_IDENTITY "BOND_TOKEN()(address)" --rpc-url $RPC_URL) +BASE_BOND=$(cast call $FLEET_IDENTITY "BASE_BOND()(uint256)" --rpc-url $RPC_URL) + +cast send $BOND_TOKEN \ + "approve(address,uint256)" \ + $FLEET_IDENTITY \ + $BASE_BOND \ + --rpc-url $RPC_URL \ + --private-key $PRIVATE_KEY + +# Claim UUID +cast send $FLEET_IDENTITY \ + "claimUuid(bytes16,address)(uint256)" \ + 0x12345678901234567890123456789012 \ + 0x0000000000000000000000000000000000000000 \ + --rpc-url $RPC_URL \ + --private-key $PRIVATE_KEY + +# Accept swarm (as provider) +cast send $SWARM_REGISTRY \ + "acceptSwarm(uint256)" \ + 101 \ + --rpc-url $RPC_URL \ + --private-key $PRIVATE_KEY +``` + +**Get Transaction Receipt:** + +```bash +# Send transaction and save hash +TX_HASH=$(cast send $SERVICE_PROVIDER \ + "registerProvider(string)" \ + "https://api.example.com" \ + --rpc-url $RPC_URL \ + --private-key $PRIVATE_KEY \ + --json | jq -r .transactionHash) + +# Get receipt +cast receipt $TX_HASH --rpc-url $RPC_URL + +# Parse logs +cast receipt $TX_HASH --rpc-url $RPC_URL --json | jq .logs +``` + +#### Using Block Explorers (Etherscan/Blockscout) + +**Verifying Proxy Contracts:** + +1. **Navigate to proxy address** on block explorer +2. **Read Contract tab**: + + - Shows current implementation address + - All read functions are available + - Use "Read as Proxy" mode to see implementation ABI + +3. **Write Contract tab**: + - Connect wallet (MetaMask, WalletConnect) + - "Write as Proxy" mode essential for upgradeable contracts + - All write functions visible with implementation ABI + +**Common Operations via GUI:** + +``` +1. Register Provider: + Contract: ServiceProvider Proxy + Function: registerProvider + Parameters: + - url: "https://api.example.com" + +2. Claim UUID: + Contract: FleetIdentity Proxy + Function: claimUuid + Parameters: + - uuid: 0x12345678901234567890123456789012 + - operator: 0x0000000000000000000000000000000000000000 + + IMPORTANT: Approve bond token first! + Contract: Bond Token (get address from BOND_TOKEN() view) + Function: approve + Parameters: + - spender: [FleetIdentity Proxy Address] + - amount: [Get from BASE_BOND() view] + +3. Register Swarm: + Contract: SwarmRegistry Proxy + Function: registerSwarm + Parameters: + - fleetUuid: 0x12345678901234567890123456789012 + - providerId: 12345 + - filter: 0x0102030405... + - fingerprintSize: 16 + - tagType: 0 + +4. Accept Swarm: + Contract: SwarmRegistry Proxy + Function: acceptSwarm + Parameters: + - swarmId: 101 +``` + +**Checking Implementation:** + +```bash +# Via cast +cast implementation $PROXY_ADDRESS --rpc-url $RPC_URL + +# Or read the slot directly (EIP-1967) +cast storage $PROXY_ADDRESS \ + 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc \ + --rpc-url $RPC_URL +``` + +**Reading Events:** + +```bash +# Get all ProviderRegistered events +cast logs \ + --address $SERVICE_PROVIDER \ + "ProviderRegistered(address,string,uint256)" \ + --from-block 1000000 \ + --to-block latest \ + --rpc-url $RPC_URL + +# Get specific swarm events +cast logs \ + --address $SWARM_REGISTRY \ + "SwarmRegistered(address,uint256,bytes16,uint256)" \ + --from-block 1000000 \ + --rpc-url $RPC_URL +``` + +#### Upgrading via Cast (Owner Only) + +```bash +# Deploy new implementation +NEW_IMPL=$(forge create src/swarms/ServiceProviderUpgradeable.sol:ServiceProviderUpgradeable \ + --rpc-url $RPC_URL \ + --private-key $OWNER_KEY \ + --json | jq -r .deployedTo) + +# Upgrade proxy +cast send $SERVICE_PROVIDER \ + "upgradeToAndCall(address,bytes)" \ + $NEW_IMPL \ + 0x \ # ← No init code, just upgrade \ + --rpc-url $RPC_URL \ + --private-key $OWNER_KEY + +# Verify implementation changed +cast implementation $SERVICE_PROVIDER --rpc-url $RPC_URL +``` + +--- + +## Upgrade Process + +### Pre-Upgrade Checklist + +1. **Verify storage compatibility**: + + ```bash + forge inspect ServiceProviderUpgradeable storageLayout > v1-layout.json + forge inspect ServiceProviderV2 storageLayout > v2-layout.json + # Manually compare: ensure all V1 variables are in same slots in V2 + ``` + +2. **Run all tests**: + + ```bash + forge test + ``` + +3. **Test on fork**: + ```bash + forge script script/UpgradeSwarm.s.sol \ + --fork-url $RPC_URL \ + --sender $OWNER + ``` + +### Execute Upgrade + +```bash +# Without reinitializer +CONTRACT_TYPE=ServiceProvider PROXY_ADDRESS=0x... \ + forge script script/UpgradeSwarm.s.sol --rpc-url $RPC_URL --broadcast + +# With reinitializer +REINIT_DATA=$(cast calldata "initializeV2()") \ +CONTRACT_TYPE=ServiceProvider PROXY_ADDRESS=0x... \ + forge script script/UpgradeSwarm.s.sol --rpc-url $RPC_URL --broadcast +``` + +### Post-Upgrade Verification + +```bash +# Verify implementation changed +cast implementation $PROXY_ADDRESS --rpc-url $RPC_URL + +# Verify owner unchanged +cast call $PROXY_ADDRESS "owner()" --rpc-url $RPC_URL + +# If V2 has version() function +cast call $PROXY_ADDRESS "version()" --rpc-url $RPC_URL + +# Test a core function +cast call $PROXY_ADDRESS "totalSupply()" --rpc-url $RPC_URL +``` + +--- + +### Rollback + +If a bug is found after upgrade: + +1. **Deploy the previous (or fixed) implementation**: + + ```bash + forge create ServiceProviderUpgradeable \ + --rpc-url $RPC_URL \ + --private-key $DEPLOYER_PRIVATE_KEY + ``` + +2. **Upgrade proxy to point back**: + + ```bash + # Get proxy admin (owner) + cast call $PROXY_ADDRESS "owner()" --rpc-url $RPC_URL + + # Upgrade to previous/fixed implementation + cast send $PROXY_ADDRESS \ + "upgradeToAndCall(address,bytes)" \ + $PREVIOUS_IMPL_ADDRESS \ + 0x \ + --rpc-url $RPC_URL \ + --private-key $OWNER_PRIVATE_KEY + ``` + +### Emergency Access Recovery + +If owner key is compromised or lost: + +**Prevention** (recommended during deployment): + +```bash +# Use a multisig or Ownable2Step for ownership +# Ownable2Step is already included in all upgradeable contracts +``` + +**Recovery**: + +1. If using `Ownable2Step`, the pending owner can accept ownership +2. If owner is a multisig, execute recovery through governance +3. If neither: contract is effectively immutable (by design) + +--- + +## Security Considerations + +### Constructor Disable + +All upgradeable contracts disable their constructors: + +```solidity +constructor() { + _disableInitializers(); +} +``` + +This prevents anyone from initializing the implementation contract directly (only proxies can be initialized). + +### Authorization + +- Upgrades require `onlyOwner` access via `_authorizeUpgrade()` +- Use `Ownable2Step` for safe ownership transfers +- Consider timelock governance for production upgrades + +### Storage Collision Prevention + +- OpenZeppelin's `Initializable` uses ERC-7201 namespaced storage +- Custom upgradeable contracts follow same pattern +- Storage gaps prevent child contract collisions +- Use `forge inspect storageLayout` to verify before upgrades + +### Audit Recommendations + +Before production deployment: + +1. **Storage layout audit**: Verify all upgradeable contracts' storage compatibility +2. **Upgrade simulation**: Test full upgrade path on testnet +3. **Access control audit**: Verify only authorized addresses can upgrade +4. **Initialization audit**: Ensure all initializers are protected +5. **Reinitializer audit**: Verify V2+ initializers cannot be called multiple times + +### Testing Checklist + +- [ ] Deploy proxy + implementation V1 +- [ ] Initialize V1 +- [ ] Verify V1 cannot be reinitialized +- [ ] Register/use core functionality +- [ ] Deploy implementation V2 +- [ ] Upgrade proxy to V2 +- [ ] Verify V1 data persists +- [ ] Test V2 new functionality +- [ ] Verify only owner can upgrade +- [ ] Test ownership transfer (2-step) + +--- + +## ZkSync Compatibility + +### Universal vs L1 Registry + +| Feature | SwarmRegistryUniversalUpgradeable | SwarmRegistryL1Upgradeable | +| :------------------ | :-------------------------------- | :--------------------------- | +| ZkSync Era | ✅ Compatible | ❌ Not compatible | +| Storage | Native `bytes` | SSTORE2 (external contracts) | +| Gas efficiency (L1) | Medium | High | +| Deployment | Standard proxy | Standard proxy | + +**Important**: `SwarmRegistryL1Upgradeable` uses `SSTORE2` which relies on `EXTCODECOPY` (unsupported on ZkSync). Always deploy `SwarmRegistryUniversalUpgradeable` on ZkSync Era. + +### Build Commands + +```bash +# ZkSync-compatible contracts +forge build --zksync + +# L1-only contracts (SwarmRegistryL1) +forge build --match-path src/swarms/SwarmRegistryL1Upgradeable.sol + +# Test ZkSync contracts +forge test --zksync + +# Test L1-only contracts +forge test --match-path test/upgradeable/SwarmRegistryL1Upgradeable.t.sol +``` + +--- + +## Version History Example + +Track versions in documentation: + +| Version | Date | Contract | Changes | +| :------ | :--------- | :-------------- | :------------------------- | +| 1.0.0 | 2026-03-04 | All | Initial UUPS deployment | +| 1.1.0 | TBD | ServiceProvider | Added reputation system | +| 1.2.0 | TBD | FleetIdentity | Added staking requirements | + +--- + +## References + +- [OpenZeppelin UUPS Upgradeable](https://docs.openzeppelin.com/contracts/5.x/api/proxy#UUPSUpgradeable) +- [EIP-1967: Proxy Storage Slots](https://eips.ethereum.org/EIPS/eip-1967) +- [ERC-7201: Namespaced Storage Layout](https://eips.ethereum.org/EIPS/eip-7201) +- [Foundry Book: Testing](https://book.getfoundry.sh/forge/tests) diff --git a/test/FleetIdentity.t.sol b/test/FleetIdentity.t.sol index 4ef24315..18fee313 100644 --- a/test/FleetIdentity.t.sol +++ b/test/FleetIdentity.t.sol @@ -2,7 +2,8 @@ pragma solidity ^0.8.24; import {Test} from "forge-std/Test.sol"; -import {FleetIdentity} from "../src/swarms/FleetIdentity.sol"; +import {ERC1967Proxy} from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol"; +import {FleetIdentityUpgradeable} from "../src/swarms/FleetIdentityUpgradeable.sol"; import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; /// @dev Minimal ERC-20 mock with public mint for testing. @@ -40,9 +41,10 @@ contract BadERC20 is ERC20 { } contract FleetIdentityTest is Test { - FleetIdentity fleet; + FleetIdentityUpgradeable fleet; MockERC20 bondToken; + address owner = address(0x1111); address alice = address(0xA); address bob = address(0xB); address carol = address(0xC); @@ -85,7 +87,18 @@ contract FleetIdentityTest is Test { function setUp() public { bondToken = new MockERC20(); - fleet = new FleetIdentity(address(bondToken), BASE_BOND); + + // Deploy implementation + FleetIdentityUpgradeable impl = new FleetIdentityUpgradeable(); + + // Deploy proxy with initialize call + ERC1967Proxy proxy = new ERC1967Proxy( + address(impl), + abi.encodeCall(FleetIdentityUpgradeable.initialize, (address(bondToken), BASE_BOND, owner)) + ); + + // Cast proxy to contract type + fleet = FleetIdentityUpgradeable(address(proxy)); // Mint enough for all 24 tiers (tier 23 bond = BASE_BOND * 2^23 ≈ 838M ether) // Total for 8 members across 24 tiers ≈ 13.4 billion ether @@ -250,13 +263,13 @@ contract FleetIdentityTest is Test { function test_RevertIf_registerFleetCountry_invalidCode_zero() public { vm.prank(alice); - vm.expectRevert(FleetIdentity.InvalidCountryCode.selector); + vm.expectRevert(FleetIdentityUpgradeable.InvalidCountryCode.selector); fleet.registerFleetCountry(UUID_1, 0, 0); } function test_RevertIf_registerFleetCountry_invalidCode_over999() public { vm.prank(alice); - vm.expectRevert(FleetIdentity.InvalidCountryCode.selector); + vm.expectRevert(FleetIdentityUpgradeable.InvalidCountryCode.selector); fleet.registerFleetCountry(UUID_1, 1000, 0); } @@ -273,19 +286,19 @@ contract FleetIdentityTest is Test { function test_RevertIf_registerFleetLocal_invalidCountry() public { vm.prank(alice); - vm.expectRevert(FleetIdentity.InvalidCountryCode.selector); + vm.expectRevert(FleetIdentityUpgradeable.InvalidCountryCode.selector); fleet.registerFleetLocal(UUID_1, 0, ADMIN_CA, 0); } function test_RevertIf_registerFleetLocal_invalidAdmin_zero() public { vm.prank(alice); - vm.expectRevert(FleetIdentity.InvalidAdminCode.selector); + vm.expectRevert(FleetIdentityUpgradeable.InvalidAdminCode.selector); fleet.registerFleetLocal(UUID_1, US, 0, 0); } function test_RevertIf_registerFleetLocal_invalidAdmin_over4095() public { vm.prank(alice); - vm.expectRevert(FleetIdentity.InvalidAdminCode.selector); + vm.expectRevert(FleetIdentityUpgradeable.InvalidAdminCode.selector); fleet.registerFleetLocal(UUID_1, US, 4096, 0); } @@ -446,7 +459,7 @@ contract FleetIdentityTest is Test { uint256 tokenId = fleet.registerFleetLocal(UUID_1, US, ADMIN_CA, 0); vm.prank(bob); - vm.expectRevert(FleetIdentity.NotOperator.selector); + vm.expectRevert(FleetIdentityUpgradeable.NotOperator.selector); fleet.promote(tokenId); } @@ -457,7 +470,7 @@ contract FleetIdentityTest is Test { fleet.reassignTier(tokenId, 2); vm.prank(alice); - vm.expectRevert(FleetIdentity.TargetTierSameAsCurrent.selector); + vm.expectRevert(FleetIdentityUpgradeable.TargetTierSameAsCurrent.selector); fleet.reassignTier(tokenId, 2); } @@ -472,7 +485,7 @@ contract FleetIdentityTest is Test { } vm.prank(alice); - vm.expectRevert(FleetIdentity.TierFull.selector); + vm.expectRevert(FleetIdentityUpgradeable.TierFull.selector); fleet.promote(tokenId); } @@ -481,7 +494,7 @@ contract FleetIdentityTest is Test { uint256 tokenId = fleet.registerFleetLocal(UUID_1, US, ADMIN_CA, 0); vm.prank(alice); - vm.expectRevert(FleetIdentity.MaxTiersReached.selector); + vm.expectRevert(FleetIdentityUpgradeable.MaxTiersReached.selector); fleet.reassignTier(tokenId, 50); } @@ -548,7 +561,7 @@ contract FleetIdentityTest is Test { fleet.reassignTier(tokenId, 2); vm.prank(bob); - vm.expectRevert(FleetIdentity.NotOperator.selector); + vm.expectRevert(FleetIdentityUpgradeable.NotOperator.selector); fleet.reassignTier(tokenId, 0); } @@ -562,7 +575,7 @@ contract FleetIdentityTest is Test { fleet.reassignTier(tokenId, 2); vm.prank(bob); - vm.expectRevert(FleetIdentity.TierFull.selector); + vm.expectRevert(FleetIdentityUpgradeable.TierFull.selector); fleet.reassignTier(tokenId, 0); } @@ -571,7 +584,7 @@ contract FleetIdentityTest is Test { uint256 tokenId = fleet.registerFleetLocal(UUID_1, US, ADMIN_CA, 0); vm.prank(bob); - vm.expectRevert(FleetIdentity.NotOperator.selector); + vm.expectRevert(FleetIdentityUpgradeable.NotOperator.selector); fleet.reassignTier(tokenId, 3); } @@ -671,7 +684,7 @@ contract FleetIdentityTest is Test { // Bob is not operator - should revert vm.prank(bob); - vm.expectRevert(FleetIdentity.NotOperator.selector); + vm.expectRevert(FleetIdentityUpgradeable.NotOperator.selector); fleet.burn(tokenId); } @@ -972,7 +985,15 @@ contract FleetIdentityTest is Test { function test_RevertIf_bondToken_transferFromReturnsFalse() public { BadERC20 badToken = new BadERC20(); - FleetIdentity f = new FleetIdentity(address(badToken), BASE_BOND); + + // Deploy implementation + FleetIdentityUpgradeable impl = new FleetIdentityUpgradeable(); + // Deploy proxy with initialize call + ERC1967Proxy proxy = new ERC1967Proxy( + address(impl), + abi.encodeCall(FleetIdentityUpgradeable.initialize, (address(badToken), BASE_BOND, owner)) + ); + FleetIdentityUpgradeable f = FleetIdentityUpgradeable(address(proxy)); badToken.mint(alice, 1_000 ether); vm.prank(alice); @@ -1037,7 +1058,15 @@ contract FleetIdentityTest is Test { // --- Edge cases --- function test_zeroBaseBond_allowsRegistration() public { - FleetIdentity f = new FleetIdentity(address(bondToken), 0); + // Deploy implementation + FleetIdentityUpgradeable impl = new FleetIdentityUpgradeable(); + // Deploy proxy with initialize call (zero bond) + ERC1967Proxy proxy = new ERC1967Proxy( + address(impl), + abi.encodeCall(FleetIdentityUpgradeable.initialize, (address(bondToken), 0, owner)) + ); + FleetIdentityUpgradeable f = FleetIdentityUpgradeable(address(proxy)); + vm.prank(alice); bondToken.approve(address(f), type(uint256).max); @@ -1083,7 +1112,7 @@ contract FleetIdentityTest is Test { uint256 tokenId = fleet.registerFleetLocal(UUID_1, US, ADMIN_CA, 0); vm.prank(caller); - vm.expectRevert(FleetIdentity.NotOperator.selector); + vm.expectRevert(FleetIdentityUpgradeable.NotOperator.selector); fleet.promote(tokenId); } @@ -1096,7 +1125,7 @@ contract FleetIdentityTest is Test { // Only operator (alice) can burn registered tokens, not random callers vm.prank(caller); - vm.expectRevert(FleetIdentity.NotOperator.selector); + vm.expectRevert(FleetIdentityUpgradeable.NotOperator.selector); fleet.burn(tokenId); } @@ -1142,7 +1171,7 @@ contract FleetIdentityTest is Test { // Bob tries to register same UUID in different region → revert vm.prank(bob); - vm.expectRevert(FleetIdentity.NotOperator.selector); + vm.expectRevert(FleetIdentityUpgradeable.NotOperator.selector); fleet.registerFleetLocal(UUID_1, DE, ADMIN_CA, 0); } @@ -1153,7 +1182,7 @@ contract FleetIdentityTest is Test { // Bob tries to register same UUID in different country → revert vm.prank(bob); - vm.expectRevert(FleetIdentity.NotOperator.selector); + vm.expectRevert(FleetIdentityUpgradeable.NotOperator.selector); fleet.registerFleetCountry(UUID_1, DE, 0); } @@ -1164,7 +1193,7 @@ contract FleetIdentityTest is Test { // Bob tries to register same UUID at local level → revert vm.prank(bob); - vm.expectRevert(FleetIdentity.NotOperator.selector); + vm.expectRevert(FleetIdentityUpgradeable.NotOperator.selector); fleet.registerFleetLocal(UUID_1, DE, ADMIN_CA, 0); } @@ -1291,7 +1320,7 @@ contract FleetIdentityTest is Test { // Bob now owns tokenId, but cannot register NEW tokens for UUID_1 vm.prank(bob); - vm.expectRevert(FleetIdentity.NotOperator.selector); + vm.expectRevert(FleetIdentityUpgradeable.NotOperator.selector); fleet.registerFleetLocal(UUID_1, DE, ADMIN_CA, 0); } @@ -1324,11 +1353,11 @@ contract FleetIdentityTest is Test { // Bob cannot register same UUID anywhere vm.prank(bob); - vm.expectRevert(FleetIdentity.NotOperator.selector); + vm.expectRevert(FleetIdentityUpgradeable.NotOperator.selector); fleet.registerFleetLocal(UUID_1, cc2, admin2, 0); vm.prank(bob); - vm.expectRevert(FleetIdentity.NotOperator.selector); + vm.expectRevert(FleetIdentityUpgradeable.NotOperator.selector); fleet.registerFleetCountry(UUID_1, cc2, 0); } @@ -1399,7 +1428,7 @@ contract FleetIdentityTest is Test { // Alice tries to register same UUID at country level → revert vm.prank(alice); - vm.expectRevert(FleetIdentity.UuidLevelMismatch.selector); + vm.expectRevert(FleetIdentityUpgradeable.UuidLevelMismatch.selector); fleet.registerFleetCountry(UUID_1, DE, 0); } @@ -1410,7 +1439,7 @@ contract FleetIdentityTest is Test { // Alice tries to register same UUID at local level → revert vm.prank(alice); - vm.expectRevert(FleetIdentity.UuidLevelMismatch.selector); + vm.expectRevert(FleetIdentityUpgradeable.UuidLevelMismatch.selector); fleet.registerFleetLocal(UUID_1, DE, ADMIN_CA, 0); } @@ -1498,7 +1527,7 @@ contract FleetIdentityTest is Test { fleet.claimUuid(UUID_1, address(0)); vm.prank(bob); - vm.expectRevert(FleetIdentity.UuidAlreadyOwned.selector); + vm.expectRevert(FleetIdentityUpgradeable.UuidAlreadyOwned.selector); fleet.claimUuid(UUID_1, address(0)); } @@ -1507,13 +1536,13 @@ contract FleetIdentityTest is Test { fleet.registerFleetLocal(UUID_1, US, ADMIN_CA, 0); vm.prank(bob); - vm.expectRevert(FleetIdentity.UuidAlreadyOwned.selector); + vm.expectRevert(FleetIdentityUpgradeable.UuidAlreadyOwned.selector); fleet.claimUuid(UUID_1, address(0)); } function test_RevertIf_claimUuid_invalidUuid() public { vm.prank(alice); - vm.expectRevert(FleetIdentity.InvalidUUID.selector); + vm.expectRevert(FleetIdentityUpgradeable.InvalidUUID.selector); fleet.claimUuid(bytes16(0), address(0)); } @@ -1707,7 +1736,7 @@ contract FleetIdentityTest is Test { // Alice cannot burn (not token owner) vm.prank(alice); - vm.expectRevert(FleetIdentity.NotTokenOwner.selector); + vm.expectRevert(FleetIdentityUpgradeable.NotTokenOwner.selector); fleet.burn(tokenId); // Bob can burn @@ -1723,7 +1752,7 @@ contract FleetIdentityTest is Test { // Bob cannot burn owned-only token (not owner) vm.prank(bob); - vm.expectRevert(FleetIdentity.NotTokenOwner.selector); + vm.expectRevert(FleetIdentityUpgradeable.NotTokenOwner.selector); fleet.burn(tokenId); } @@ -2046,7 +2075,7 @@ contract FleetIdentityTest is Test { vm.prank(alice); fleet.registerFleetCountry(UUID_1, US, 0); - vm.expectRevert(FleetIdentity.AdminAreaRequired.selector); + vm.expectRevert(FleetIdentityUpgradeable.AdminAreaRequired.selector); fleet.buildHighestBondedUuidBundle(US, 0); } @@ -2936,7 +2965,7 @@ contract FleetIdentityTest is Test { } // localInclusionHint should revert - vm.expectRevert(FleetIdentity.MaxTiersReached.selector); + vm.expectRevert(FleetIdentityUpgradeable.MaxTiersReached.selector); fleet.localInclusionHint(US, ADMIN_CA); } @@ -2954,7 +2983,7 @@ contract FleetIdentityTest is Test { // Registration at tier 0 (or any full tier) should revert with TierFull vm.prank(bob); - vm.expectRevert(FleetIdentity.TierFull.selector); + vm.expectRevert(FleetIdentityUpgradeable.TierFull.selector); fleet.registerFleetLocal(_uuid(99999), US, ADMIN_CA, 0); } @@ -2970,7 +2999,7 @@ contract FleetIdentityTest is Test { } } - vm.expectRevert(FleetIdentity.MaxTiersReached.selector); + vm.expectRevert(FleetIdentityUpgradeable.MaxTiersReached.selector); fleet.countryInclusionHint(US); } @@ -3064,7 +3093,7 @@ contract FleetIdentityTest is Test { // Now all admin tiers 0-23 are full. A new admin fleet must go to tier 24, // which exceeds MAX_TIERS=24 (valid tiers are 0-23). - vm.expectRevert(FleetIdentity.MaxTiersReached.selector); + vm.expectRevert(FleetIdentityUpgradeable.MaxTiersReached.selector); fleet.localInclusionHint(US, ADMIN_CA); } @@ -3254,10 +3283,10 @@ contract FleetIdentityTest is Test { } function test_RevertIf_buildCountryOnlyBundle_invalidCountryCode() public { - vm.expectRevert(FleetIdentity.InvalidCountryCode.selector); + vm.expectRevert(FleetIdentityUpgradeable.InvalidCountryCode.selector); fleet.buildCountryOnlyBundle(0); - vm.expectRevert(FleetIdentity.InvalidCountryCode.selector); + vm.expectRevert(FleetIdentityUpgradeable.InvalidCountryCode.selector); fleet.buildCountryOnlyBundle(1000); // > MAX_COUNTRY_CODE (999) } @@ -3412,7 +3441,7 @@ contract FleetIdentityTest is Test { fleet.registerFleetLocal(UUID_1, US, ADMIN_CA, 0); vm.prank(bob); - vm.expectRevert(FleetIdentity.NotUuidOwner.selector); + vm.expectRevert(FleetIdentityUpgradeable.NotUuidOwner.selector); fleet.setOperator(UUID_1, carol); } @@ -3508,7 +3537,7 @@ contract FleetIdentityTest is Test { fleet.setOperator(UUID_1, bob); vm.prank(alice); - vm.expectRevert(FleetIdentity.NotOperator.selector); + vm.expectRevert(FleetIdentityUpgradeable.NotOperator.selector); fleet.promote(tokenId); } @@ -3541,7 +3570,7 @@ contract FleetIdentityTest is Test { // Owner cannot burn when there's a separate operator vm.prank(alice); - vm.expectRevert(FleetIdentity.NotOperator.selector); + vm.expectRevert(FleetIdentityUpgradeable.NotOperator.selector); fleet.burn(tokenId); } @@ -3640,7 +3669,7 @@ contract FleetIdentityTest is Test { function test_claimUuid_emitsEventWithOperator() public { vm.expectEmit(true, true, false, true); - emit FleetIdentity.UuidClaimed(alice, UUID_1, bob); + emit FleetIdentityUpgradeable.UuidClaimed(alice, UUID_1, bob); vm.prank(alice); fleet.claimUuid(UUID_1, bob); @@ -3731,7 +3760,7 @@ contract FleetIdentityTest is Test { function test_RevertIf_setOperator_notRegistered() public { // UUID not registered at all - uuidOwner is address(0), so NotUuidOwner reverts first vm.prank(alice); - vm.expectRevert(FleetIdentity.NotUuidOwner.selector); + vm.expectRevert(FleetIdentityUpgradeable.NotUuidOwner.selector); fleet.setOperator(UUID_1, bob); } diff --git a/test/FleetIdentityFairness.t.sol b/test/FleetIdentityFairness.t.sol index 4b67363e..73bbbb03 100644 --- a/test/FleetIdentityFairness.t.sol +++ b/test/FleetIdentityFairness.t.sol @@ -2,7 +2,8 @@ pragma solidity ^0.8.24; import {Test} from "forge-std/Test.sol"; -import {FleetIdentity} from "../src/swarms/FleetIdentity.sol"; +import {ERC1967Proxy} from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol"; +import {FleetIdentityUpgradeable} from "../src/swarms/FleetIdentityUpgradeable.sol"; import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; /// @dev Minimal ERC-20 mock with public mint for testing. @@ -104,8 +105,20 @@ contract FleetIdentityFairnessTest is Test { // Helper Functions // ══════════════════════════════════════════════════════════════════════════════════ - function _deployFleet() internal returns (FleetIdentity) { - FleetIdentity fleet = new FleetIdentity(address(bondToken), BASE_BOND); + address fleetOwner = address(0x1111); + + function _deployFleet() internal returns (FleetIdentityUpgradeable) { + // Deploy implementation + FleetIdentityUpgradeable impl = new FleetIdentityUpgradeable(); + + // Deploy proxy with initialize call + ERC1967Proxy proxy = new ERC1967Proxy( + address(impl), + abi.encodeCall(FleetIdentityUpgradeable.initialize, (address(bondToken), BASE_BOND, fleetOwner)) + ); + + // Cast proxy to contract type + FleetIdentityUpgradeable fleet = FleetIdentityUpgradeable(address(proxy)); // Approve all players vm.prank(whale); @@ -131,7 +144,7 @@ contract FleetIdentityFairnessTest is Test { } /// @dev Count how many slots in a bundle are from country vs local registrations - function _countBundleComposition(FleetIdentity fleet, uint16 cc, uint16 admin) + function _countBundleComposition(FleetIdentityUpgradeable fleet, uint16 cc, uint16 admin) internal view returns (uint256 localCount, uint256 countryCount) @@ -160,7 +173,7 @@ contract FleetIdentityFairnessTest is Test { * Tests that locals correctly fill slots by tier-descent priority. */ function test_scenarioA_localHeavyMarket() public { - FleetIdentity fleet = _deployFleet(); + FleetIdentityUpgradeable fleet = _deployFleet(); uint16 targetAdmin = adminAreas[0]; // 16 local players at tiers 0-3 (4 per tier due to TIER_CAPACITY) @@ -195,7 +208,7 @@ contract FleetIdentityFairnessTest is Test { * Tests that higher-tier country beats lower-tier local. */ function test_scenarioB_countryHighTierDominance() public { - FleetIdentity fleet = _deployFleet(); + FleetIdentityUpgradeable fleet = _deployFleet(); uint16 targetAdmin = adminAreas[0]; // 4 local players at tier 0 @@ -231,7 +244,7 @@ contract FleetIdentityFairnessTest is Test { * Tests that locals get priority within the same tier. */ function test_scenarioC_sameTierLocalPriority() public { - FleetIdentity fleet = _deployFleet(); + FleetIdentityUpgradeable fleet = _deployFleet(); uint16 targetAdmin = adminAreas[0]; // 4 local at tier 0 @@ -267,7 +280,7 @@ contract FleetIdentityFairnessTest is Test { * Tests that whale can dominate IF they outbid locals on tier level. */ function test_scenarioD_countryWhaleHighTier() public { - FleetIdentity fleet = _deployFleet(); + FleetIdentityUpgradeable fleet = _deployFleet(); uint16 targetAdmin = adminAreas[0]; // 12 locals at tiers 0-2 (4 per tier) @@ -303,7 +316,7 @@ contract FleetIdentityFairnessTest is Test { * Shows that locals can economically counter a country whale. */ function test_scenarioE_localsCounterWhale() public { - FleetIdentity fleet = _deployFleet(); + FleetIdentityUpgradeable fleet = _deployFleet(); uint16 targetAdmin = adminAreas[0]; // Whale registers 4 country fleets at tier 3 @@ -352,7 +365,7 @@ contract FleetIdentityFairnessTest is Test { * @notice Verify the 16× economic advantage constants. */ function test_economicAdvantage_8xMultiplier() public { - FleetIdentity fleet = _deployFleet(); + FleetIdentityUpgradeable fleet = _deployFleet(); // Verify multiplier assertEq(fleet.COUNTRY_BOND_MULTIPLIER(), 16, "Multiplier should be 16"); @@ -369,7 +382,7 @@ contract FleetIdentityFairnessTest is Test { * @notice Demonstrate that a local at tier N+4 costs the same as country at tier N. */ function test_economicAdvantage_localTierEquivalence() public { - FleetIdentity fleet = _deployFleet(); + FleetIdentityUpgradeable fleet = _deployFleet(); // Local tier 4 = Country tier 0 (2^4 = 16) assertEq( @@ -401,7 +414,7 @@ contract FleetIdentityFairnessTest is Test { * @notice Analyze country registration efficiency across admin areas. */ function test_economicAdvantage_multiRegionEfficiency() public { - FleetIdentity fleet = _deployFleet(); + FleetIdentityUpgradeable fleet = _deployFleet(); // Single country registration covers ALL admin areas uint256 countryBond = fleet.tierBond(0, true); // 800 NODL @@ -425,7 +438,7 @@ contract FleetIdentityFairnessTest is Test { * @notice Bond escalation analysis showing geometric growth. */ function test_bondEscalationAnalysis() public { - FleetIdentity fleet = _deployFleet(); + FleetIdentityUpgradeable fleet = _deployFleet(); emit log_string(""); emit log_string("=== BOND ESCALATION ANALYSIS ==="); @@ -453,7 +466,7 @@ contract FleetIdentityFairnessTest is Test { * @notice CRITICAL: Core invariants that must ALWAYS hold. */ function test_invariant_coreGuarantees() public { - FleetIdentity fleet = _deployFleet(); + FleetIdentityUpgradeable fleet = _deployFleet(); // Invariant 1: Country multiplier is exactly 16 assertEq(fleet.COUNTRY_BOND_MULTIPLIER(), 16, "INVARIANT: Country multiplier must be 16"); @@ -480,7 +493,7 @@ contract FleetIdentityFairnessTest is Test { * @notice Bundle always respects tier-descent priority. */ function test_invariant_tierDescentPriority() public { - FleetIdentity fleet = _deployFleet(); + FleetIdentityUpgradeable fleet = _deployFleet(); uint16 targetAdmin = adminAreas[0]; // Mixed setup: locals at tier 1, country at tier 2 @@ -518,7 +531,7 @@ contract FleetIdentityFairnessTest is Test { numLocals = uint8(bound(numLocals, 1, 16)); numCountry = uint8(bound(numCountry, 1, 12)); - FleetIdentity fleet = _deployFleet(); + FleetIdentityUpgradeable fleet = _deployFleet(); uint16 targetAdmin = adminAreas[0]; // Register local players (spread across tiers for variety) @@ -552,7 +565,7 @@ contract FleetIdentityFairnessTest is Test { */ function testFuzz_constantMultiplier(uint8 tier) public { tier = uint8(bound(tier, 0, 20)); - FleetIdentity fleet = _deployFleet(); + FleetIdentityUpgradeable fleet = _deployFleet(); uint256 localBond = fleet.tierBond(tier, false); uint256 countryBond = fleet.tierBond(tier, true); diff --git a/test/ServiceProvider.t.sol b/test/ServiceProvider.t.sol index fd55d144..d7f01b73 100644 --- a/test/ServiceProvider.t.sol +++ b/test/ServiceProvider.t.sol @@ -2,10 +2,12 @@ pragma solidity ^0.8.24; import {Test} from "forge-std/Test.sol"; -import {ServiceProvider} from "../src/swarms/ServiceProvider.sol"; +import {ERC1967Proxy} from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol"; +import {ServiceProviderUpgradeable} from "../src/swarms/ServiceProviderUpgradeable.sol"; contract ServiceProviderTest is Test { - ServiceProvider provider; + ServiceProviderUpgradeable provider; + address owner = address(0x1111); address alice = address(0xA); address bob = address(0xB); @@ -18,7 +20,17 @@ contract ServiceProviderTest is Test { event ProviderBurned(address indexed owner, uint256 indexed tokenId); function setUp() public { - provider = new ServiceProvider(); + // Deploy implementation + ServiceProviderUpgradeable impl = new ServiceProviderUpgradeable(); + + // Deploy proxy with initialize call + ERC1967Proxy proxy = new ERC1967Proxy( + address(impl), + abi.encodeCall(ServiceProviderUpgradeable.initialize, (owner)) + ); + + // Cast proxy to contract type + provider = ServiceProviderUpgradeable(address(proxy)); } // ============================== @@ -64,7 +76,7 @@ contract ServiceProviderTest is Test { function test_RevertIf_registerProvider_emptyURL() public { vm.prank(alice); - vm.expectRevert(ServiceProvider.EmptyURL.selector); + vm.expectRevert(ServiceProviderUpgradeable.EmptyURL.selector); provider.registerProvider(""); } @@ -112,7 +124,7 @@ contract ServiceProviderTest is Test { uint256 tokenId = provider.registerProvider(URL_1); vm.prank(bob); - vm.expectRevert(ServiceProvider.NotTokenOwner.selector); + vm.expectRevert(ServiceProviderUpgradeable.NotTokenOwner.selector); provider.burn(tokenId); } @@ -153,7 +165,7 @@ contract ServiceProviderTest is Test { uint256 tokenId = provider.registerProvider(URL_1); vm.prank(caller); - vm.expectRevert(ServiceProvider.NotTokenOwner.selector); + vm.expectRevert(ServiceProviderUpgradeable.NotTokenOwner.selector); provider.burn(tokenId); } } diff --git a/test/SwarmRegistryL1.t.sol b/test/SwarmRegistryL1.t.sol index 812fe03b..126fe38c 100644 --- a/test/SwarmRegistryL1.t.sol +++ b/test/SwarmRegistryL1.t.sol @@ -2,9 +2,10 @@ pragma solidity ^0.8.24; import {Test} from "forge-std/Test.sol"; -import "../src/swarms/SwarmRegistryL1.sol"; -import {FleetIdentity} from "../src/swarms/FleetIdentity.sol"; -import {ServiceProvider} from "../src/swarms/ServiceProvider.sol"; +import {ERC1967Proxy} from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol"; +import "../src/swarms/SwarmRegistryL1Upgradeable.sol"; +import {FleetIdentityUpgradeable} from "../src/swarms/FleetIdentityUpgradeable.sol"; +import {ServiceProviderUpgradeable} from "../src/swarms/ServiceProviderUpgradeable.sol"; import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; contract MockBondTokenL1 is ERC20 { @@ -16,11 +17,12 @@ contract MockBondTokenL1 is ERC20 { } contract SwarmRegistryL1Test is Test { - SwarmRegistryL1 swarmRegistry; - FleetIdentity fleetContract; - ServiceProvider providerContract; + SwarmRegistryL1Upgradeable swarmRegistry; + FleetIdentityUpgradeable fleetContract; + ServiceProviderUpgradeable providerContract; MockBondTokenL1 bondToken; + address contractOwner = address(0x1111); address fleetOwner = address(0x1); address providerOwner = address(0x2); address caller = address(0x3); @@ -32,16 +34,37 @@ contract SwarmRegistryL1Test is Test { uint16 constant ADMIN_CA = 6; // California event SwarmRegistered(uint256 indexed swarmId, bytes16 indexed fleetUuid, uint256 indexed providerId, address owner); - event SwarmStatusChanged(uint256 indexed swarmId, SwarmRegistryL1.SwarmStatus status); + event SwarmStatusChanged(uint256 indexed swarmId, SwarmRegistryL1Upgradeable.SwarmStatus status); event SwarmProviderUpdated(uint256 indexed swarmId, uint256 indexed oldProvider, uint256 indexed newProvider); event SwarmDeleted(uint256 indexed swarmId, bytes16 indexed fleetUuid, address indexed owner); event SwarmPurged(uint256 indexed swarmId, bytes16 indexed fleetUuid, address indexed purgedBy); function setUp() public { bondToken = new MockBondTokenL1(); - fleetContract = new FleetIdentity(address(bondToken), FLEET_BOND); - providerContract = new ServiceProvider(); - swarmRegistry = new SwarmRegistryL1(address(fleetContract), address(providerContract)); + + // Deploy FleetIdentity via proxy + FleetIdentityUpgradeable fleetImpl = new FleetIdentityUpgradeable(); + ERC1967Proxy fleetProxy = new ERC1967Proxy( + address(fleetImpl), + abi.encodeCall(FleetIdentityUpgradeable.initialize, (address(bondToken), FLEET_BOND, contractOwner)) + ); + fleetContract = FleetIdentityUpgradeable(address(fleetProxy)); + + // Deploy ServiceProvider via proxy + ServiceProviderUpgradeable providerImpl = new ServiceProviderUpgradeable(); + ERC1967Proxy providerProxy = new ERC1967Proxy( + address(providerImpl), + abi.encodeCall(ServiceProviderUpgradeable.initialize, (contractOwner)) + ); + providerContract = ServiceProviderUpgradeable(address(providerProxy)); + + // Deploy SwarmRegistry via proxy + SwarmRegistryL1Upgradeable registryImpl = new SwarmRegistryL1Upgradeable(); + ERC1967Proxy registryProxy = new ERC1967Proxy( + address(registryImpl), + abi.encodeCall(SwarmRegistryL1Upgradeable.initialize, (address(fleetContract), address(providerContract), contractOwner)) + ); + swarmRegistry = SwarmRegistryL1Upgradeable(address(registryProxy)); // Fund fleet owner and approve bondToken.mint(fleetOwner, 1_000_000 ether); @@ -73,7 +96,7 @@ contract SwarmRegistryL1Test is Test { uint256 providerId, bytes memory filter, uint8 fpSize, - SwarmRegistryL1.TagType tagType + SwarmRegistryL1Upgradeable.TagType tagType ) internal returns (uint256) { bytes16 fleetUuid = _getFleetUuid(fleetId); vm.prank(owner); @@ -108,24 +131,36 @@ contract SwarmRegistryL1Test is Test { // Constructor // ============================== - function test_constructor_setsImmutables() public view { + function test_initialize_setsContracts() public view { assertEq(address(swarmRegistry.FLEET_CONTRACT()), address(fleetContract)); assertEq(address(swarmRegistry.PROVIDER_CONTRACT()), address(providerContract)); } - function test_RevertIf_constructor_zeroFleetAddress() public { - vm.expectRevert(SwarmRegistryL1.InvalidSwarmData.selector); - new SwarmRegistryL1(address(0), address(providerContract)); + function test_RevertIf_initialize_zeroFleetAddress() public { + SwarmRegistryL1Upgradeable impl = new SwarmRegistryL1Upgradeable(); + vm.expectRevert(SwarmRegistryL1Upgradeable.InvalidSwarmData.selector); + new ERC1967Proxy( + address(impl), + abi.encodeCall(SwarmRegistryL1Upgradeable.initialize, (address(0), address(providerContract), contractOwner)) + ); } - function test_RevertIf_constructor_zeroProviderAddress() public { - vm.expectRevert(SwarmRegistryL1.InvalidSwarmData.selector); - new SwarmRegistryL1(address(fleetContract), address(0)); + function test_RevertIf_initialize_zeroProviderAddress() public { + SwarmRegistryL1Upgradeable impl = new SwarmRegistryL1Upgradeable(); + vm.expectRevert(SwarmRegistryL1Upgradeable.InvalidSwarmData.selector); + new ERC1967Proxy( + address(impl), + abi.encodeCall(SwarmRegistryL1Upgradeable.initialize, (address(fleetContract), address(0), contractOwner)) + ); } - function test_RevertIf_constructor_bothZero() public { - vm.expectRevert(SwarmRegistryL1.InvalidSwarmData.selector); - new SwarmRegistryL1(address(0), address(0)); + function test_RevertIf_initialize_bothZero() public { + SwarmRegistryL1Upgradeable impl = new SwarmRegistryL1Upgradeable(); + vm.expectRevert(SwarmRegistryL1Upgradeable.InvalidSwarmData.selector); + new ERC1967Proxy( + address(impl), + abi.encodeCall(SwarmRegistryL1Upgradeable.initialize, (address(0), address(0), contractOwner)) + ); } // ============================== @@ -137,11 +172,11 @@ contract SwarmRegistryL1Test is Test { uint256 providerId = _registerProvider(providerOwner, "https://api.example.com"); uint256 swarmId = _registerSwarm( - fleetOwner, fleetId, providerId, new bytes(100), 16, SwarmRegistryL1.TagType.IBEACON_INCLUDES_MAC + fleetOwner, fleetId, providerId, new bytes(100), 16, SwarmRegistryL1Upgradeable.TagType.IBEACON_INCLUDES_MAC ); // Swarm ID is deterministic hash of (fleetUuid, filter, fingerprintSize, tagType) - uint256 expectedId = swarmRegistry.computeSwarmId(_getFleetUuid(fleetId), new bytes(100), 16, SwarmRegistryL1.TagType.IBEACON_INCLUDES_MAC); + uint256 expectedId = swarmRegistry.computeSwarmId(_getFleetUuid(fleetId), new bytes(100), 16, SwarmRegistryL1Upgradeable.TagType.IBEACON_INCLUDES_MAC); assertEq(swarmId, expectedId); } @@ -150,23 +185,23 @@ contract SwarmRegistryL1Test is Test { uint256 providerId = _registerProvider(providerOwner, "url1"); uint256 swarmId = - _registerSwarm(fleetOwner, fleetId, providerId, new bytes(50), 8, SwarmRegistryL1.TagType.VENDOR_ID); + _registerSwarm(fleetOwner, fleetId, providerId, new bytes(50), 8, SwarmRegistryL1Upgradeable.TagType.VENDOR_ID); ( bytes16 storedFleetUuid, uint256 storedProviderId, address filterPointer, uint8 storedFpSize, - SwarmRegistryL1.TagType storedTagType, - SwarmRegistryL1.SwarmStatus storedStatus + SwarmRegistryL1Upgradeable.TagType storedTagType, + SwarmRegistryL1Upgradeable.SwarmStatus storedStatus ) = swarmRegistry.swarms(swarmId); assertEq(storedFleetUuid, _getFleetUuid(fleetId)); assertEq(storedProviderId, providerId); assertTrue(filterPointer != address(0)); assertEq(storedFpSize, 8); - assertEq(uint8(storedTagType), uint8(SwarmRegistryL1.TagType.VENDOR_ID)); - assertEq(uint8(storedStatus), uint8(SwarmRegistryL1.SwarmStatus.REGISTERED)); + assertEq(uint8(storedTagType), uint8(SwarmRegistryL1Upgradeable.TagType.VENDOR_ID)); + assertEq(uint8(storedStatus), uint8(SwarmRegistryL1Upgradeable.SwarmStatus.REGISTERED)); } function test_registerSwarm_deterministicId() public { @@ -175,9 +210,9 @@ contract SwarmRegistryL1Test is Test { bytes memory filter = new bytes(32); - uint256 expectedId = swarmRegistry.computeSwarmId(_getFleetUuid(fleetId), filter, 8, SwarmRegistryL1.TagType.GENERIC); + uint256 expectedId = swarmRegistry.computeSwarmId(_getFleetUuid(fleetId), filter, 8, SwarmRegistryL1Upgradeable.TagType.GENERIC); - uint256 swarmId = _registerSwarm(fleetOwner, fleetId, providerId, filter, 8, SwarmRegistryL1.TagType.GENERIC); + uint256 swarmId = _registerSwarm(fleetOwner, fleetId, providerId, filter, 8, SwarmRegistryL1Upgradeable.TagType.GENERIC); assertEq(swarmId, expectedId); } @@ -185,11 +220,11 @@ contract SwarmRegistryL1Test is Test { uint256 fleetId = _registerFleet(fleetOwner, "f1"); uint256 providerId = _registerProvider(providerOwner, "url1"); - _registerSwarm(fleetOwner, fleetId, providerId, new bytes(32), 8, SwarmRegistryL1.TagType.GENERIC); + _registerSwarm(fleetOwner, fleetId, providerId, new bytes(32), 8, SwarmRegistryL1Upgradeable.TagType.GENERIC); vm.prank(fleetOwner); - vm.expectRevert(SwarmRegistryL1.SwarmAlreadyExists.selector); - swarmRegistry.registerSwarm(_getFleetUuid(fleetId), providerId, new bytes(32), 8, SwarmRegistryL1.TagType.GENERIC); + vm.expectRevert(SwarmRegistryL1Upgradeable.SwarmAlreadyExists.selector); + swarmRegistry.registerSwarm(_getFleetUuid(fleetId), providerId, new bytes(32), 8, SwarmRegistryL1Upgradeable.TagType.GENERIC); } function test_registerSwarm_emitsSwarmRegistered() public { @@ -197,12 +232,12 @@ contract SwarmRegistryL1Test is Test { uint256 providerId = _registerProvider(providerOwner, "url1"); bytes memory filter = new bytes(50); - uint256 expectedId = swarmRegistry.computeSwarmId(_getFleetUuid(fleetId), filter, 16, SwarmRegistryL1.TagType.GENERIC); + uint256 expectedId = swarmRegistry.computeSwarmId(_getFleetUuid(fleetId), filter, 16, SwarmRegistryL1Upgradeable.TagType.GENERIC); vm.expectEmit(true, true, true, true); emit SwarmRegistered(expectedId, _getFleetUuid(fleetId), providerId, fleetOwner); - _registerSwarm(fleetOwner, fleetId, providerId, filter, 16, SwarmRegistryL1.TagType.GENERIC); + _registerSwarm(fleetOwner, fleetId, providerId, filter, 16, SwarmRegistryL1Upgradeable.TagType.GENERIC); } function test_registerSwarm_linksUuidSwarms() public { @@ -217,9 +252,9 @@ contract SwarmRegistryL1Test is Test { filter2[0] = 0x02; uint256 swarmId1 = - _registerSwarm(fleetOwner, fleetId, providerId1, filter1, 8, SwarmRegistryL1.TagType.GENERIC); + _registerSwarm(fleetOwner, fleetId, providerId1, filter1, 8, SwarmRegistryL1Upgradeable.TagType.GENERIC); uint256 swarmId2 = - _registerSwarm(fleetOwner, fleetId, providerId2, filter2, 8, SwarmRegistryL1.TagType.GENERIC); + _registerSwarm(fleetOwner, fleetId, providerId2, filter2, 8, SwarmRegistryL1Upgradeable.TagType.GENERIC); assertEq(swarmRegistry.uuidSwarms(_getFleetUuid(fleetId), 0), swarmId1); assertEq(swarmRegistry.uuidSwarms(_getFleetUuid(fleetId), 1), swarmId2); @@ -233,24 +268,24 @@ contract SwarmRegistryL1Test is Test { uint256 providerId = _registerProvider(providerOwner, "url"); uint256 s1 = _registerSwarm( - fleetOwner, fleetId1, providerId, new bytes(32), 8, SwarmRegistryL1.TagType.IBEACON_PAYLOAD_ONLY + fleetOwner, fleetId1, providerId, new bytes(32), 8, SwarmRegistryL1Upgradeable.TagType.IBEACON_PAYLOAD_ONLY ); uint256 s2 = _registerSwarm( - fleetOwner, fleetId2, providerId, new bytes(32), 8, SwarmRegistryL1.TagType.IBEACON_INCLUDES_MAC + fleetOwner, fleetId2, providerId, new bytes(32), 8, SwarmRegistryL1Upgradeable.TagType.IBEACON_INCLUDES_MAC ); uint256 s3 = - _registerSwarm(fleetOwner, fleetId3, providerId, new bytes(32), 8, SwarmRegistryL1.TagType.VENDOR_ID); - uint256 s4 = _registerSwarm(fleetOwner, fleetId4, providerId, new bytes(32), 8, SwarmRegistryL1.TagType.GENERIC); + _registerSwarm(fleetOwner, fleetId3, providerId, new bytes(32), 8, SwarmRegistryL1Upgradeable.TagType.VENDOR_ID); + uint256 s4 = _registerSwarm(fleetOwner, fleetId4, providerId, new bytes(32), 8, SwarmRegistryL1Upgradeable.TagType.GENERIC); - (,,,, SwarmRegistryL1.TagType t1,) = swarmRegistry.swarms(s1); - (,,,, SwarmRegistryL1.TagType t2,) = swarmRegistry.swarms(s2); - (,,,, SwarmRegistryL1.TagType t3,) = swarmRegistry.swarms(s3); - (,,,, SwarmRegistryL1.TagType t4,) = swarmRegistry.swarms(s4); + (,,,, SwarmRegistryL1Upgradeable.TagType t1,) = swarmRegistry.swarms(s1); + (,,,, SwarmRegistryL1Upgradeable.TagType t2,) = swarmRegistry.swarms(s2); + (,,,, SwarmRegistryL1Upgradeable.TagType t3,) = swarmRegistry.swarms(s3); + (,,,, SwarmRegistryL1Upgradeable.TagType t4,) = swarmRegistry.swarms(s4); - assertEq(uint8(t1), uint8(SwarmRegistryL1.TagType.IBEACON_PAYLOAD_ONLY)); - assertEq(uint8(t2), uint8(SwarmRegistryL1.TagType.IBEACON_INCLUDES_MAC)); - assertEq(uint8(t3), uint8(SwarmRegistryL1.TagType.VENDOR_ID)); - assertEq(uint8(t4), uint8(SwarmRegistryL1.TagType.GENERIC)); + assertEq(uint8(t1), uint8(SwarmRegistryL1Upgradeable.TagType.IBEACON_PAYLOAD_ONLY)); + assertEq(uint8(t2), uint8(SwarmRegistryL1Upgradeable.TagType.IBEACON_INCLUDES_MAC)); + assertEq(uint8(t3), uint8(SwarmRegistryL1Upgradeable.TagType.VENDOR_ID)); + assertEq(uint8(t4), uint8(SwarmRegistryL1Upgradeable.TagType.GENERIC)); } // ============================== @@ -261,16 +296,16 @@ contract SwarmRegistryL1Test is Test { uint256 fleetId = _registerFleet(fleetOwner, "my-fleet"); vm.prank(caller); - vm.expectRevert(SwarmRegistryL1.NotUuidOwner.selector); - swarmRegistry.registerSwarm(_getFleetUuid(fleetId), 1, new bytes(10), 16, SwarmRegistryL1.TagType.GENERIC); + vm.expectRevert(SwarmRegistryL1Upgradeable.NotUuidOwner.selector); + swarmRegistry.registerSwarm(_getFleetUuid(fleetId), 1, new bytes(10), 16, SwarmRegistryL1Upgradeable.TagType.GENERIC); } function test_RevertIf_registerSwarm_zeroUuid() public { uint256 providerId = _registerProvider(providerOwner, "url1"); vm.prank(fleetOwner); - vm.expectRevert(SwarmRegistryL1.InvalidUuid.selector); - swarmRegistry.registerSwarm(bytes16(0), providerId, new bytes(32), 8, SwarmRegistryL1.TagType.GENERIC); + vm.expectRevert(SwarmRegistryL1Upgradeable.InvalidUuid.selector); + swarmRegistry.registerSwarm(bytes16(0), providerId, new bytes(32), 8, SwarmRegistryL1Upgradeable.TagType.GENERIC); } function test_RevertIf_registerSwarm_providerDoesNotExist() public { @@ -278,8 +313,8 @@ contract SwarmRegistryL1Test is Test { uint256 nonExistentProvider = 12345; vm.prank(fleetOwner); - vm.expectRevert(SwarmRegistryL1.ProviderDoesNotExist.selector); - swarmRegistry.registerSwarm(_getFleetUuid(fleetId), nonExistentProvider, new bytes(32), 8, SwarmRegistryL1.TagType.GENERIC); + vm.expectRevert(SwarmRegistryL1Upgradeable.ProviderDoesNotExist.selector); + swarmRegistry.registerSwarm(_getFleetUuid(fleetId), nonExistentProvider, new bytes(32), 8, SwarmRegistryL1Upgradeable.TagType.GENERIC); } function test_RevertIf_registerSwarm_fingerprintSizeZero() public { @@ -287,8 +322,8 @@ contract SwarmRegistryL1Test is Test { uint256 providerId = _registerProvider(providerOwner, "url1"); vm.prank(fleetOwner); - vm.expectRevert(SwarmRegistryL1.InvalidFingerprintSize.selector); - swarmRegistry.registerSwarm(_getFleetUuid(fleetId), providerId, new bytes(32), 0, SwarmRegistryL1.TagType.GENERIC); + vm.expectRevert(SwarmRegistryL1Upgradeable.InvalidFingerprintSize.selector); + swarmRegistry.registerSwarm(_getFleetUuid(fleetId), providerId, new bytes(32), 0, SwarmRegistryL1Upgradeable.TagType.GENERIC); } function test_RevertIf_registerSwarm_fingerprintSizeExceedsMax() public { @@ -296,8 +331,8 @@ contract SwarmRegistryL1Test is Test { uint256 providerId = _registerProvider(providerOwner, "url1"); vm.prank(fleetOwner); - vm.expectRevert(SwarmRegistryL1.InvalidFingerprintSize.selector); - swarmRegistry.registerSwarm(_getFleetUuid(fleetId), providerId, new bytes(32), 17, SwarmRegistryL1.TagType.GENERIC); + vm.expectRevert(SwarmRegistryL1Upgradeable.InvalidFingerprintSize.selector); + swarmRegistry.registerSwarm(_getFleetUuid(fleetId), providerId, new bytes(32), 17, SwarmRegistryL1Upgradeable.TagType.GENERIC); } function test_RevertIf_registerSwarm_emptyFilter() public { @@ -305,8 +340,8 @@ contract SwarmRegistryL1Test is Test { uint256 providerId = _registerProvider(providerOwner, "url1"); vm.prank(fleetOwner); - vm.expectRevert(SwarmRegistryL1.InvalidFilterSize.selector); - swarmRegistry.registerSwarm(_getFleetUuid(fleetId), providerId, new bytes(0), 8, SwarmRegistryL1.TagType.GENERIC); + vm.expectRevert(SwarmRegistryL1Upgradeable.InvalidFilterSize.selector); + swarmRegistry.registerSwarm(_getFleetUuid(fleetId), providerId, new bytes(0), 8, SwarmRegistryL1Upgradeable.TagType.GENERIC); } function test_RevertIf_registerSwarm_filterTooLarge() public { @@ -314,8 +349,8 @@ contract SwarmRegistryL1Test is Test { uint256 providerId = _registerProvider(providerOwner, "url1"); vm.prank(fleetOwner); - vm.expectRevert(SwarmRegistryL1.InvalidFilterSize.selector); - swarmRegistry.registerSwarm(_getFleetUuid(fleetId), providerId, new bytes(24577), 8, SwarmRegistryL1.TagType.GENERIC); + vm.expectRevert(SwarmRegistryL1Upgradeable.InvalidFilterSize.selector); + swarmRegistry.registerSwarm(_getFleetUuid(fleetId), providerId, new bytes(24577), 8, SwarmRegistryL1Upgradeable.TagType.GENERIC); } function test_registerSwarm_maxFingerprintSize() public { @@ -324,7 +359,7 @@ contract SwarmRegistryL1Test is Test { // fpSize=16 is MAX_FINGERPRINT_SIZE, should succeed uint256 swarmId = - _registerSwarm(fleetOwner, fleetId, providerId, new bytes(100), 16, SwarmRegistryL1.TagType.GENERIC); + _registerSwarm(fleetOwner, fleetId, providerId, new bytes(100), 16, SwarmRegistryL1Upgradeable.TagType.GENERIC); assertTrue(swarmId != 0); } @@ -334,7 +369,7 @@ contract SwarmRegistryL1Test is Test { // Exactly 24576 bytes should succeed uint256 swarmId = - _registerSwarm(fleetOwner, fleetId, providerId, new bytes(24576), 8, SwarmRegistryL1.TagType.GENERIC); + _registerSwarm(fleetOwner, fleetId, providerId, new bytes(24576), 8, SwarmRegistryL1Upgradeable.TagType.GENERIC); assertTrue(swarmId != 0); } @@ -346,42 +381,42 @@ contract SwarmRegistryL1Test is Test { uint256 fleetId = _registerFleet(fleetOwner, "f1"); uint256 providerId = _registerProvider(providerOwner, "url1"); uint256 swarmId = - _registerSwarm(fleetOwner, fleetId, providerId, new bytes(50), 8, SwarmRegistryL1.TagType.GENERIC); + _registerSwarm(fleetOwner, fleetId, providerId, new bytes(50), 8, SwarmRegistryL1Upgradeable.TagType.GENERIC); vm.expectEmit(true, true, true, true); - emit SwarmStatusChanged(swarmId, SwarmRegistryL1.SwarmStatus.ACCEPTED); + emit SwarmStatusChanged(swarmId, SwarmRegistryL1Upgradeable.SwarmStatus.ACCEPTED); vm.prank(providerOwner); swarmRegistry.acceptSwarm(swarmId); - (,,,,, SwarmRegistryL1.SwarmStatus status) = swarmRegistry.swarms(swarmId); - assertEq(uint8(status), uint8(SwarmRegistryL1.SwarmStatus.ACCEPTED)); + (,,,,, SwarmRegistryL1Upgradeable.SwarmStatus status) = swarmRegistry.swarms(swarmId); + assertEq(uint8(status), uint8(SwarmRegistryL1Upgradeable.SwarmStatus.ACCEPTED)); } function test_rejectSwarm_setsStatusAndEmits() public { uint256 fleetId = _registerFleet(fleetOwner, "f1"); uint256 providerId = _registerProvider(providerOwner, "url1"); uint256 swarmId = - _registerSwarm(fleetOwner, fleetId, providerId, new bytes(50), 8, SwarmRegistryL1.TagType.GENERIC); + _registerSwarm(fleetOwner, fleetId, providerId, new bytes(50), 8, SwarmRegistryL1Upgradeable.TagType.GENERIC); vm.expectEmit(true, true, true, true); - emit SwarmStatusChanged(swarmId, SwarmRegistryL1.SwarmStatus.REJECTED); + emit SwarmStatusChanged(swarmId, SwarmRegistryL1Upgradeable.SwarmStatus.REJECTED); vm.prank(providerOwner); swarmRegistry.rejectSwarm(swarmId); - (,,,,, SwarmRegistryL1.SwarmStatus status) = swarmRegistry.swarms(swarmId); - assertEq(uint8(status), uint8(SwarmRegistryL1.SwarmStatus.REJECTED)); + (,,,,, SwarmRegistryL1Upgradeable.SwarmStatus status) = swarmRegistry.swarms(swarmId); + assertEq(uint8(status), uint8(SwarmRegistryL1Upgradeable.SwarmStatus.REJECTED)); } function test_RevertIf_acceptSwarm_notProviderOwner() public { uint256 fleetId = _registerFleet(fleetOwner, "f1"); uint256 providerId = _registerProvider(providerOwner, "url1"); uint256 swarmId = - _registerSwarm(fleetOwner, fleetId, providerId, new bytes(50), 8, SwarmRegistryL1.TagType.GENERIC); + _registerSwarm(fleetOwner, fleetId, providerId, new bytes(50), 8, SwarmRegistryL1Upgradeable.TagType.GENERIC); vm.prank(caller); - vm.expectRevert(SwarmRegistryL1.NotProviderOwner.selector); + vm.expectRevert(SwarmRegistryL1Upgradeable.NotProviderOwner.selector); swarmRegistry.acceptSwarm(swarmId); } @@ -389,10 +424,10 @@ contract SwarmRegistryL1Test is Test { uint256 fleetId = _registerFleet(fleetOwner, "f1"); uint256 providerId = _registerProvider(providerOwner, "url1"); uint256 swarmId = - _registerSwarm(fleetOwner, fleetId, providerId, new bytes(50), 8, SwarmRegistryL1.TagType.GENERIC); + _registerSwarm(fleetOwner, fleetId, providerId, new bytes(50), 8, SwarmRegistryL1Upgradeable.TagType.GENERIC); vm.prank(fleetOwner); // fleet owner != provider owner - vm.expectRevert(SwarmRegistryL1.NotProviderOwner.selector); + vm.expectRevert(SwarmRegistryL1Upgradeable.NotProviderOwner.selector); swarmRegistry.rejectSwarm(swarmId); } @@ -400,7 +435,7 @@ contract SwarmRegistryL1Test is Test { uint256 fleetId = _registerFleet(fleetOwner, "f1"); uint256 providerId = _registerProvider(providerOwner, "url1"); uint256 swarmId = - _registerSwarm(fleetOwner, fleetId, providerId, new bytes(50), 8, SwarmRegistryL1.TagType.GENERIC); + _registerSwarm(fleetOwner, fleetId, providerId, new bytes(50), 8, SwarmRegistryL1Upgradeable.TagType.GENERIC); vm.prank(providerOwner); swarmRegistry.rejectSwarm(swarmId); @@ -409,8 +444,8 @@ contract SwarmRegistryL1Test is Test { vm.prank(providerOwner); swarmRegistry.acceptSwarm(swarmId); - (,,,,, SwarmRegistryL1.SwarmStatus status) = swarmRegistry.swarms(swarmId); - assertEq(uint8(status), uint8(SwarmRegistryL1.SwarmStatus.ACCEPTED)); + (,,,,, SwarmRegistryL1Upgradeable.SwarmStatus status) = swarmRegistry.swarms(swarmId); + assertEq(uint8(status), uint8(SwarmRegistryL1Upgradeable.SwarmStatus.ACCEPTED)); } // ============================== @@ -437,7 +472,7 @@ contract SwarmRegistryL1Test is Test { _write16Bit(filter, h1, uint16(expectedFp)); uint256 swarmId = - _registerSwarm(fleetOwner, fleetId, providerId, filter, fpSize, SwarmRegistryL1.TagType.GENERIC); + _registerSwarm(fleetOwner, fleetId, providerId, filter, fpSize, SwarmRegistryL1Upgradeable.TagType.GENERIC); // Positive check assertTrue(swarmRegistry.checkMembership(swarmId, keccak256(tagId)), "Valid tag should pass"); @@ -470,14 +505,14 @@ contract SwarmRegistryL1Test is Test { _write8Bit(filter, h1, uint8(expectedFp)); uint256 swarmId = - _registerSwarm(fleetOwner, fleetId, providerId, filter, fpSize, SwarmRegistryL1.TagType.GENERIC); + _registerSwarm(fleetOwner, fleetId, providerId, filter, fpSize, SwarmRegistryL1Upgradeable.TagType.GENERIC); assertTrue(swarmRegistry.checkMembership(swarmId, keccak256(tagId)), "8-bit valid tag should pass"); assertFalse(swarmRegistry.checkMembership(swarmId, keccak256(hex"FFFFFF")), "8-bit invalid tag should fail"); } function test_RevertIf_checkMembership_swarmNotFound() public { - vm.expectRevert(SwarmRegistryL1.SwarmNotFound.selector); + vm.expectRevert(SwarmRegistryL1Upgradeable.SwarmNotFound.selector); swarmRegistry.checkMembership(999, keccak256("anything")); } @@ -488,7 +523,7 @@ contract SwarmRegistryL1Test is Test { // All-zero filter: f1^f2^f3 = 0^0^0 = 0 // Only matches if expectedFp is also 0 bytes memory filter = new bytes(64); - uint256 swarmId = _registerSwarm(fleetOwner, fleetId, providerId, filter, 16, SwarmRegistryL1.TagType.GENERIC); + uint256 swarmId = _registerSwarm(fleetOwner, fleetId, providerId, filter, 16, SwarmRegistryL1Upgradeable.TagType.GENERIC); // Some tags will match (those with expectedFp=0), most won't // The point is it doesn't revert @@ -502,7 +537,7 @@ contract SwarmRegistryL1Test is Test { // 1-byte filter with 16-bit fingerprint: m = (1*8)/16 = 0, returns false immediately bytes memory filter = new bytes(1); - uint256 swarmId = _registerSwarm(fleetOwner, fleetId, providerId, filter, 16, SwarmRegistryL1.TagType.GENERIC); + uint256 swarmId = _registerSwarm(fleetOwner, fleetId, providerId, filter, 16, SwarmRegistryL1Upgradeable.TagType.GENERIC); // Should return false (not revert) because m == 0 assertFalse(swarmRegistry.checkMembership(swarmId, keccak256("test")), "m=0 should return false"); @@ -518,11 +553,11 @@ contract SwarmRegistryL1Test is Test { uint256 providerId2 = _registerProvider(providerOwner, "url2"); uint256 providerId3 = _registerProvider(providerOwner, "url3"); - uint256 s1 = _registerSwarm(fleetOwner, fleetId, providerId1, new bytes(32), 8, SwarmRegistryL1.TagType.GENERIC); + uint256 s1 = _registerSwarm(fleetOwner, fleetId, providerId1, new bytes(32), 8, SwarmRegistryL1Upgradeable.TagType.GENERIC); uint256 s2 = - _registerSwarm(fleetOwner, fleetId, providerId2, new bytes(64), 16, SwarmRegistryL1.TagType.VENDOR_ID); + _registerSwarm(fleetOwner, fleetId, providerId2, new bytes(64), 16, SwarmRegistryL1Upgradeable.TagType.VENDOR_ID); uint256 s3 = _registerSwarm( - fleetOwner, fleetId, providerId3, new bytes(50), 12, SwarmRegistryL1.TagType.IBEACON_PAYLOAD_ONLY + fleetOwner, fleetId, providerId3, new bytes(50), 12, SwarmRegistryL1Upgradeable.TagType.IBEACON_PAYLOAD_ONLY ); // IDs are distinct hashes @@ -552,7 +587,7 @@ contract SwarmRegistryL1Test is Test { uint256 providerId = _registerProvider(providerOwner, string(abi.encodePacked("url-", fpSize))); uint256 swarmId = - _registerSwarm(fleetOwner, fleetId, providerId, new bytes(64), fpSize, SwarmRegistryL1.TagType.GENERIC); + _registerSwarm(fleetOwner, fleetId, providerId, new bytes(64), fpSize, SwarmRegistryL1Upgradeable.TagType.GENERIC); (,,, uint8 storedFp,,) = swarmRegistry.swarms(swarmId); assertEq(storedFp, fpSize); @@ -565,8 +600,8 @@ contract SwarmRegistryL1Test is Test { uint256 providerId = _registerProvider(providerOwner, "url1"); vm.prank(fleetOwner); - vm.expectRevert(SwarmRegistryL1.InvalidFingerprintSize.selector); - swarmRegistry.registerSwarm(_getFleetUuid(fleetId), providerId, new bytes(32), fpSize, SwarmRegistryL1.TagType.GENERIC); + vm.expectRevert(SwarmRegistryL1Upgradeable.InvalidFingerprintSize.selector); + swarmRegistry.registerSwarm(_getFleetUuid(fleetId), providerId, new bytes(32), fpSize, SwarmRegistryL1Upgradeable.TagType.GENERIC); } // ============================== @@ -579,7 +614,7 @@ contract SwarmRegistryL1Test is Test { uint256 providerId2 = _registerProvider(providerOwner, "url2"); uint256 swarmId = - _registerSwarm(fleetOwner, fleetId, providerId1, new bytes(50), 8, SwarmRegistryL1.TagType.GENERIC); + _registerSwarm(fleetOwner, fleetId, providerId1, new bytes(50), 8, SwarmRegistryL1Upgradeable.TagType.GENERIC); // Provider accepts vm.prank(providerOwner); @@ -593,16 +628,16 @@ contract SwarmRegistryL1Test is Test { swarmRegistry.updateSwarmProvider(swarmId, providerId2); // Check new provider and status reset - (, uint256 newProviderId,,,, SwarmRegistryL1.SwarmStatus status) = swarmRegistry.swarms(swarmId); + (, uint256 newProviderId,,,, SwarmRegistryL1Upgradeable.SwarmStatus status) = swarmRegistry.swarms(swarmId); assertEq(newProviderId, providerId2); - assertEq(uint8(status), uint8(SwarmRegistryL1.SwarmStatus.REGISTERED)); + assertEq(uint8(status), uint8(SwarmRegistryL1Upgradeable.SwarmStatus.REGISTERED)); } function test_RevertIf_updateSwarmProvider_swarmNotFound() public { uint256 providerId = _registerProvider(providerOwner, "url1"); vm.prank(fleetOwner); - vm.expectRevert(SwarmRegistryL1.SwarmNotFound.selector); + vm.expectRevert(SwarmRegistryL1Upgradeable.SwarmNotFound.selector); swarmRegistry.updateSwarmProvider(999, providerId); } @@ -612,10 +647,10 @@ contract SwarmRegistryL1Test is Test { uint256 providerId2 = _registerProvider(providerOwner, "url2"); uint256 swarmId = - _registerSwarm(fleetOwner, fleetId, providerId1, new bytes(50), 8, SwarmRegistryL1.TagType.GENERIC); + _registerSwarm(fleetOwner, fleetId, providerId1, new bytes(50), 8, SwarmRegistryL1Upgradeable.TagType.GENERIC); vm.prank(caller); - vm.expectRevert(SwarmRegistryL1.NotUuidOwner.selector); + vm.expectRevert(SwarmRegistryL1Upgradeable.NotUuidOwner.selector); swarmRegistry.updateSwarmProvider(swarmId, providerId2); } @@ -624,10 +659,10 @@ contract SwarmRegistryL1Test is Test { uint256 providerId = _registerProvider(providerOwner, "url1"); uint256 swarmId = - _registerSwarm(fleetOwner, fleetId, providerId, new bytes(50), 8, SwarmRegistryL1.TagType.GENERIC); + _registerSwarm(fleetOwner, fleetId, providerId, new bytes(50), 8, SwarmRegistryL1Upgradeable.TagType.GENERIC); vm.prank(fleetOwner); - vm.expectRevert(SwarmRegistryL1.ProviderDoesNotExist.selector); + vm.expectRevert(SwarmRegistryL1Upgradeable.ProviderDoesNotExist.selector); swarmRegistry.updateSwarmProvider(swarmId, 99999); } @@ -639,7 +674,7 @@ contract SwarmRegistryL1Test is Test { uint256 fleetId = _registerFleet(fleetOwner, "f1"); uint256 providerId = _registerProvider(providerOwner, "url1"); uint256 swarmId = - _registerSwarm(fleetOwner, fleetId, providerId, new bytes(50), 8, SwarmRegistryL1.TagType.GENERIC); + _registerSwarm(fleetOwner, fleetId, providerId, new bytes(50), 8, SwarmRegistryL1Upgradeable.TagType.GENERIC); vm.expectEmit(true, true, true, true); emit SwarmDeleted(swarmId, _getFleetUuid(fleetId), fleetOwner); @@ -664,9 +699,9 @@ contract SwarmRegistryL1Test is Test { filter2[0] = 0x02; uint256 swarm1 = - _registerSwarm(fleetOwner, fleetId, providerId1, filter1, 8, SwarmRegistryL1.TagType.GENERIC); + _registerSwarm(fleetOwner, fleetId, providerId1, filter1, 8, SwarmRegistryL1Upgradeable.TagType.GENERIC); uint256 swarm2 = - _registerSwarm(fleetOwner, fleetId, providerId2, filter2, 8, SwarmRegistryL1.TagType.GENERIC); + _registerSwarm(fleetOwner, fleetId, providerId2, filter2, 8, SwarmRegistryL1Upgradeable.TagType.GENERIC); // Delete first swarm vm.prank(fleetOwner); @@ -693,11 +728,11 @@ contract SwarmRegistryL1Test is Test { filter3[0] = 0x03; uint256 swarm1 = - _registerSwarm(fleetOwner, fleetId, providerId1, filter1, 8, SwarmRegistryL1.TagType.GENERIC); + _registerSwarm(fleetOwner, fleetId, providerId1, filter1, 8, SwarmRegistryL1Upgradeable.TagType.GENERIC); uint256 swarm2 = - _registerSwarm(fleetOwner, fleetId, providerId2, filter2, 8, SwarmRegistryL1.TagType.GENERIC); + _registerSwarm(fleetOwner, fleetId, providerId2, filter2, 8, SwarmRegistryL1Upgradeable.TagType.GENERIC); uint256 swarm3 = - _registerSwarm(fleetOwner, fleetId, providerId3, filter3, 8, SwarmRegistryL1.TagType.GENERIC); + _registerSwarm(fleetOwner, fleetId, providerId3, filter3, 8, SwarmRegistryL1Upgradeable.TagType.GENERIC); // Delete middle swarm vm.prank(fleetOwner); @@ -712,7 +747,7 @@ contract SwarmRegistryL1Test is Test { function test_RevertIf_deleteSwarm_swarmNotFound() public { vm.prank(fleetOwner); - vm.expectRevert(SwarmRegistryL1.SwarmNotFound.selector); + vm.expectRevert(SwarmRegistryL1Upgradeable.SwarmNotFound.selector); swarmRegistry.deleteSwarm(999); } @@ -720,10 +755,10 @@ contract SwarmRegistryL1Test is Test { uint256 fleetId = _registerFleet(fleetOwner, "f1"); uint256 providerId = _registerProvider(providerOwner, "url1"); uint256 swarmId = - _registerSwarm(fleetOwner, fleetId, providerId, new bytes(50), 8, SwarmRegistryL1.TagType.GENERIC); + _registerSwarm(fleetOwner, fleetId, providerId, new bytes(50), 8, SwarmRegistryL1Upgradeable.TagType.GENERIC); vm.prank(caller); - vm.expectRevert(SwarmRegistryL1.NotUuidOwner.selector); + vm.expectRevert(SwarmRegistryL1Upgradeable.NotUuidOwner.selector); swarmRegistry.deleteSwarm(swarmId); } @@ -732,7 +767,7 @@ contract SwarmRegistryL1Test is Test { uint256 providerId1 = _registerProvider(providerOwner, "url1"); uint256 providerId2 = _registerProvider(providerOwner, "url2"); uint256 swarmId = - _registerSwarm(fleetOwner, fleetId, providerId1, new bytes(50), 8, SwarmRegistryL1.TagType.GENERIC); + _registerSwarm(fleetOwner, fleetId, providerId1, new bytes(50), 8, SwarmRegistryL1Upgradeable.TagType.GENERIC); // Update provider then delete vm.prank(fleetOwner); @@ -759,9 +794,9 @@ contract SwarmRegistryL1Test is Test { bytes memory filter3 = new bytes(50); filter3[0] = 0x03; - uint256 s1 = _registerSwarm(fleetOwner, fleetId, p1, filter1, 8, SwarmRegistryL1.TagType.GENERIC); - uint256 s2 = _registerSwarm(fleetOwner, fleetId, p2, filter2, 8, SwarmRegistryL1.TagType.GENERIC); - uint256 s3 = _registerSwarm(fleetOwner, fleetId, p3, filter3, 8, SwarmRegistryL1.TagType.GENERIC); + uint256 s1 = _registerSwarm(fleetOwner, fleetId, p1, filter1, 8, SwarmRegistryL1Upgradeable.TagType.GENERIC); + uint256 s2 = _registerSwarm(fleetOwner, fleetId, p2, filter2, 8, SwarmRegistryL1Upgradeable.TagType.GENERIC); + uint256 s3 = _registerSwarm(fleetOwner, fleetId, p3, filter3, 8, SwarmRegistryL1Upgradeable.TagType.GENERIC); // Verify initial indices assertEq(swarmRegistry.swarmIndexInUuid(s1), 0); @@ -785,7 +820,7 @@ contract SwarmRegistryL1Test is Test { uint256 fleetId = _registerFleet(fleetOwner, "f1"); uint256 providerId = _registerProvider(providerOwner, "url1"); uint256 swarmId = - _registerSwarm(fleetOwner, fleetId, providerId, new bytes(50), 8, SwarmRegistryL1.TagType.GENERIC); + _registerSwarm(fleetOwner, fleetId, providerId, new bytes(50), 8, SwarmRegistryL1Upgradeable.TagType.GENERIC); (bool fleetValid, bool providerValid) = swarmRegistry.isSwarmValid(swarmId); assertTrue(fleetValid); @@ -796,7 +831,7 @@ contract SwarmRegistryL1Test is Test { uint256 fleetId = _registerFleet(fleetOwner, "f1"); uint256 providerId = _registerProvider(providerOwner, "url1"); uint256 swarmId = - _registerSwarm(fleetOwner, fleetId, providerId, new bytes(50), 8, SwarmRegistryL1.TagType.GENERIC); + _registerSwarm(fleetOwner, fleetId, providerId, new bytes(50), 8, SwarmRegistryL1Upgradeable.TagType.GENERIC); // Burn provider vm.prank(providerOwner); @@ -811,7 +846,7 @@ contract SwarmRegistryL1Test is Test { uint256 fleetId = _registerFleet(fleetOwner, "f1"); uint256 providerId = _registerProvider(providerOwner, "url1"); uint256 swarmId = - _registerSwarm(fleetOwner, fleetId, providerId, new bytes(50), 8, SwarmRegistryL1.TagType.GENERIC); + _registerSwarm(fleetOwner, fleetId, providerId, new bytes(50), 8, SwarmRegistryL1Upgradeable.TagType.GENERIC); // Burn registered fleet token (operator = owner for fresh registration) // This mints an owned-only token back to the owner @@ -834,7 +869,7 @@ contract SwarmRegistryL1Test is Test { uint256 fleetId = _registerFleet(fleetOwner, "f1"); uint256 providerId = _registerProvider(providerOwner, "url1"); uint256 swarmId = - _registerSwarm(fleetOwner, fleetId, providerId, new bytes(50), 8, SwarmRegistryL1.TagType.GENERIC); + _registerSwarm(fleetOwner, fleetId, providerId, new bytes(50), 8, SwarmRegistryL1Upgradeable.TagType.GENERIC); // Burn registered fleet token → mints owned-only token vm.prank(fleetOwner); @@ -855,7 +890,7 @@ contract SwarmRegistryL1Test is Test { } function test_RevertIf_isSwarmValid_swarmNotFound() public { - vm.expectRevert(SwarmRegistryL1.SwarmNotFound.selector); + vm.expectRevert(SwarmRegistryL1Upgradeable.SwarmNotFound.selector); swarmRegistry.isSwarmValid(999); } @@ -867,7 +902,7 @@ contract SwarmRegistryL1Test is Test { uint256 fleetId = _registerFleet(fleetOwner, "f1"); uint256 providerId = _registerProvider(providerOwner, "url1"); uint256 swarmId = - _registerSwarm(fleetOwner, fleetId, providerId, new bytes(50), 8, SwarmRegistryL1.TagType.GENERIC); + _registerSwarm(fleetOwner, fleetId, providerId, new bytes(50), 8, SwarmRegistryL1Upgradeable.TagType.GENERIC); // Burn provider vm.prank(providerOwner); @@ -889,7 +924,7 @@ contract SwarmRegistryL1Test is Test { uint256 fleetId = _registerFleet(fleetOwner, "f1"); uint256 providerId = _registerProvider(providerOwner, "url1"); uint256 swarmId = - _registerSwarm(fleetOwner, fleetId, providerId, new bytes(50), 8, SwarmRegistryL1.TagType.GENERIC); + _registerSwarm(fleetOwner, fleetId, providerId, new bytes(50), 8, SwarmRegistryL1Upgradeable.TagType.GENERIC); // Burn registered fleet token → mints owned-only token vm.prank(fleetOwner); @@ -919,8 +954,8 @@ contract SwarmRegistryL1Test is Test { bytes memory filter2 = new bytes(50); filter2[0] = 0x02; - uint256 s1 = _registerSwarm(fleetOwner, fleetId, p1, filter1, 8, SwarmRegistryL1.TagType.GENERIC); - uint256 s2 = _registerSwarm(fleetOwner, fleetId, p2, filter2, 8, SwarmRegistryL1.TagType.GENERIC); + uint256 s1 = _registerSwarm(fleetOwner, fleetId, p1, filter1, 8, SwarmRegistryL1Upgradeable.TagType.GENERIC); + uint256 s2 = _registerSwarm(fleetOwner, fleetId, p2, filter2, 8, SwarmRegistryL1Upgradeable.TagType.GENERIC); // Burn provider of s1 vm.prank(providerOwner); @@ -936,7 +971,7 @@ contract SwarmRegistryL1Test is Test { } function test_RevertIf_purgeOrphanedSwarm_swarmNotFound() public { - vm.expectRevert(SwarmRegistryL1.SwarmNotFound.selector); + vm.expectRevert(SwarmRegistryL1Upgradeable.SwarmNotFound.selector); swarmRegistry.purgeOrphanedSwarm(999); } @@ -944,9 +979,9 @@ contract SwarmRegistryL1Test is Test { uint256 fleetId = _registerFleet(fleetOwner, "f1"); uint256 providerId = _registerProvider(providerOwner, "url1"); uint256 swarmId = - _registerSwarm(fleetOwner, fleetId, providerId, new bytes(50), 8, SwarmRegistryL1.TagType.GENERIC); + _registerSwarm(fleetOwner, fleetId, providerId, new bytes(50), 8, SwarmRegistryL1Upgradeable.TagType.GENERIC); - vm.expectRevert(SwarmRegistryL1.SwarmNotOrphaned.selector); + vm.expectRevert(SwarmRegistryL1Upgradeable.SwarmNotOrphaned.selector); swarmRegistry.purgeOrphanedSwarm(swarmId); } @@ -958,14 +993,14 @@ contract SwarmRegistryL1Test is Test { uint256 fleetId = _registerFleet(fleetOwner, "f1"); uint256 providerId = _registerProvider(providerOwner, "url1"); uint256 swarmId = - _registerSwarm(fleetOwner, fleetId, providerId, new bytes(50), 8, SwarmRegistryL1.TagType.GENERIC); + _registerSwarm(fleetOwner, fleetId, providerId, new bytes(50), 8, SwarmRegistryL1Upgradeable.TagType.GENERIC); // Burn provider vm.prank(providerOwner); providerContract.burn(providerId); vm.prank(providerOwner); - vm.expectRevert(SwarmRegistryL1.SwarmOrphaned.selector); + vm.expectRevert(SwarmRegistryL1Upgradeable.SwarmOrphaned.selector); swarmRegistry.acceptSwarm(swarmId); } @@ -973,7 +1008,7 @@ contract SwarmRegistryL1Test is Test { uint256 fleetId = _registerFleet(fleetOwner, "f1"); uint256 providerId = _registerProvider(providerOwner, "url1"); uint256 swarmId = - _registerSwarm(fleetOwner, fleetId, providerId, new bytes(50), 8, SwarmRegistryL1.TagType.GENERIC); + _registerSwarm(fleetOwner, fleetId, providerId, new bytes(50), 8, SwarmRegistryL1Upgradeable.TagType.GENERIC); // Burn registered fleet token → mints owned-only token vm.prank(fleetOwner); @@ -986,7 +1021,7 @@ contract SwarmRegistryL1Test is Test { fleetContract.burn(ownedTokenId); vm.prank(providerOwner); - vm.expectRevert(SwarmRegistryL1.SwarmOrphaned.selector); + vm.expectRevert(SwarmRegistryL1Upgradeable.SwarmOrphaned.selector); swarmRegistry.rejectSwarm(swarmId); } @@ -994,13 +1029,13 @@ contract SwarmRegistryL1Test is Test { uint256 fleetId = _registerFleet(fleetOwner, "f1"); uint256 providerId = _registerProvider(providerOwner, "url1"); uint256 swarmId = - _registerSwarm(fleetOwner, fleetId, providerId, new bytes(50), 8, SwarmRegistryL1.TagType.GENERIC); + _registerSwarm(fleetOwner, fleetId, providerId, new bytes(50), 8, SwarmRegistryL1Upgradeable.TagType.GENERIC); // Burn provider vm.prank(providerOwner); providerContract.burn(providerId); - vm.expectRevert(SwarmRegistryL1.SwarmOrphaned.selector); + vm.expectRevert(SwarmRegistryL1Upgradeable.SwarmOrphaned.selector); swarmRegistry.checkMembership(swarmId, keccak256("test")); } @@ -1008,7 +1043,7 @@ contract SwarmRegistryL1Test is Test { uint256 fleetId = _registerFleet(fleetOwner, "f1"); uint256 providerId = _registerProvider(providerOwner, "url1"); uint256 swarmId = - _registerSwarm(fleetOwner, fleetId, providerId, new bytes(50), 8, SwarmRegistryL1.TagType.GENERIC); + _registerSwarm(fleetOwner, fleetId, providerId, new bytes(50), 8, SwarmRegistryL1Upgradeable.TagType.GENERIC); // Burn registered fleet token → mints owned-only token vm.prank(fleetOwner); @@ -1021,7 +1056,7 @@ contract SwarmRegistryL1Test is Test { fleetContract.burn(ownedTokenId); vm.prank(providerOwner); - vm.expectRevert(SwarmRegistryL1.SwarmOrphaned.selector); + vm.expectRevert(SwarmRegistryL1Upgradeable.SwarmOrphaned.selector); swarmRegistry.acceptSwarm(swarmId); } @@ -1029,7 +1064,7 @@ contract SwarmRegistryL1Test is Test { uint256 fleetId = _registerFleet(fleetOwner, "f1"); uint256 providerId = _registerProvider(providerOwner, "url1"); uint256 swarmId = - _registerSwarm(fleetOwner, fleetId, providerId, new bytes(50), 8, SwarmRegistryL1.TagType.GENERIC); + _registerSwarm(fleetOwner, fleetId, providerId, new bytes(50), 8, SwarmRegistryL1Upgradeable.TagType.GENERIC); vm.prank(providerOwner); providerContract.burn(providerId); @@ -1039,7 +1074,7 @@ contract SwarmRegistryL1Test is Test { // After purge, swarm no longer exists vm.prank(providerOwner); - vm.expectRevert(SwarmRegistryL1.SwarmNotFound.selector); + vm.expectRevert(SwarmRegistryL1Upgradeable.SwarmNotFound.selector); swarmRegistry.acceptSwarm(swarmId); } } diff --git a/test/SwarmRegistryUniversal.t.sol b/test/SwarmRegistryUniversal.t.sol index 558f3798..a7612bd5 100644 --- a/test/SwarmRegistryUniversal.t.sol +++ b/test/SwarmRegistryUniversal.t.sol @@ -2,9 +2,10 @@ pragma solidity ^0.8.24; import {Test} from "forge-std/Test.sol"; -import "../src/swarms/SwarmRegistryUniversal.sol"; -import {FleetIdentity} from "../src/swarms/FleetIdentity.sol"; -import {ServiceProvider} from "../src/swarms/ServiceProvider.sol"; +import {ERC1967Proxy} from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol"; +import "../src/swarms/SwarmRegistryUniversalUpgradeable.sol"; +import {FleetIdentityUpgradeable} from "../src/swarms/FleetIdentityUpgradeable.sol"; +import {ServiceProviderUpgradeable} from "../src/swarms/ServiceProviderUpgradeable.sol"; import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; contract MockBondTokenUniv is ERC20 { @@ -16,11 +17,12 @@ contract MockBondTokenUniv is ERC20 { } contract SwarmRegistryUniversalTest is Test { - SwarmRegistryUniversal swarmRegistry; - FleetIdentity fleetContract; - ServiceProvider providerContract; + SwarmRegistryUniversalUpgradeable swarmRegistry; + FleetIdentityUpgradeable fleetContract; + ServiceProviderUpgradeable providerContract; MockBondTokenUniv bondToken; + address contractOwner = address(0x1111); address fleetOwner = address(0x1); address providerOwner = address(0x2); address caller = address(0x3); @@ -34,16 +36,37 @@ contract SwarmRegistryUniversalTest is Test { event SwarmRegistered( uint256 indexed swarmId, bytes16 indexed fleetUuid, uint256 indexed providerId, address owner, uint32 filterSize ); - event SwarmStatusChanged(uint256 indexed swarmId, SwarmRegistryUniversal.SwarmStatus status); + event SwarmStatusChanged(uint256 indexed swarmId, SwarmRegistryUniversalUpgradeable.SwarmStatus status); event SwarmProviderUpdated(uint256 indexed swarmId, uint256 indexed oldProviderId, uint256 indexed newProviderId); event SwarmDeleted(uint256 indexed swarmId, bytes16 indexed fleetUuid, address indexed owner); event SwarmPurged(uint256 indexed swarmId, bytes16 indexed fleetUuid, address indexed purgedBy); function setUp() public { bondToken = new MockBondTokenUniv(); - fleetContract = new FleetIdentity(address(bondToken), FLEET_BOND); - providerContract = new ServiceProvider(); - swarmRegistry = new SwarmRegistryUniversal(address(fleetContract), address(providerContract)); + + // Deploy FleetIdentity via proxy + FleetIdentityUpgradeable fleetImpl = new FleetIdentityUpgradeable(); + ERC1967Proxy fleetProxy = new ERC1967Proxy( + address(fleetImpl), + abi.encodeCall(FleetIdentityUpgradeable.initialize, (address(bondToken), FLEET_BOND, contractOwner)) + ); + fleetContract = FleetIdentityUpgradeable(address(fleetProxy)); + + // Deploy ServiceProvider via proxy + ServiceProviderUpgradeable providerImpl = new ServiceProviderUpgradeable(); + ERC1967Proxy providerProxy = new ERC1967Proxy( + address(providerImpl), + abi.encodeCall(ServiceProviderUpgradeable.initialize, (contractOwner)) + ); + providerContract = ServiceProviderUpgradeable(address(providerProxy)); + + // Deploy SwarmRegistry via proxy + SwarmRegistryUniversalUpgradeable registryImpl = new SwarmRegistryUniversalUpgradeable(); + ERC1967Proxy registryProxy = new ERC1967Proxy( + address(registryImpl), + abi.encodeCall(SwarmRegistryUniversalUpgradeable.initialize, (address(fleetContract), address(providerContract), contractOwner)) + ); + swarmRegistry = SwarmRegistryUniversalUpgradeable(address(registryProxy)); // Fund fleet owner and approve bondToken.mint(fleetOwner, 1_000_000 ether); @@ -75,7 +98,7 @@ contract SwarmRegistryUniversalTest is Test { uint256 providerId, bytes memory filter, uint8 fpSize, - SwarmRegistryUniversal.TagType tagType + SwarmRegistryUniversalUpgradeable.TagType tagType ) internal returns (uint256) { bytes16 fleetUuid = _getFleetUuid(fleetId); vm.prank(owner); @@ -109,24 +132,36 @@ contract SwarmRegistryUniversalTest is Test { // Constructor // ============================== - function test_constructor_setsImmutables() public view { + function test_initialize_setsContracts() public view { assertEq(address(swarmRegistry.FLEET_CONTRACT()), address(fleetContract)); assertEq(address(swarmRegistry.PROVIDER_CONTRACT()), address(providerContract)); } - function test_RevertIf_constructor_zeroFleetAddress() public { - vm.expectRevert(SwarmRegistryUniversal.InvalidSwarmData.selector); - new SwarmRegistryUniversal(address(0), address(providerContract)); + function test_RevertIf_initialize_zeroFleetAddress() public { + SwarmRegistryUniversalUpgradeable impl = new SwarmRegistryUniversalUpgradeable(); + vm.expectRevert(SwarmRegistryUniversalUpgradeable.InvalidSwarmData.selector); + new ERC1967Proxy( + address(impl), + abi.encodeCall(SwarmRegistryUniversalUpgradeable.initialize, (address(0), address(providerContract), contractOwner)) + ); } - function test_RevertIf_constructor_zeroProviderAddress() public { - vm.expectRevert(SwarmRegistryUniversal.InvalidSwarmData.selector); - new SwarmRegistryUniversal(address(fleetContract), address(0)); + function test_RevertIf_initialize_zeroProviderAddress() public { + SwarmRegistryUniversalUpgradeable impl = new SwarmRegistryUniversalUpgradeable(); + vm.expectRevert(SwarmRegistryUniversalUpgradeable.InvalidSwarmData.selector); + new ERC1967Proxy( + address(impl), + abi.encodeCall(SwarmRegistryUniversalUpgradeable.initialize, (address(fleetContract), address(0), contractOwner)) + ); } - function test_RevertIf_constructor_bothZero() public { - vm.expectRevert(SwarmRegistryUniversal.InvalidSwarmData.selector); - new SwarmRegistryUniversal(address(0), address(0)); + function test_RevertIf_initialize_bothZero() public { + SwarmRegistryUniversalUpgradeable impl = new SwarmRegistryUniversalUpgradeable(); + vm.expectRevert(SwarmRegistryUniversalUpgradeable.InvalidSwarmData.selector); + new ERC1967Proxy( + address(impl), + abi.encodeCall(SwarmRegistryUniversalUpgradeable.initialize, (address(0), address(0), contractOwner)) + ); } // ============================== @@ -138,11 +173,11 @@ contract SwarmRegistryUniversalTest is Test { uint256 providerId = _registerProvider(providerOwner, "https://api.example.com"); uint256 swarmId = _registerSwarm( - fleetOwner, fleetId, providerId, new bytes(100), 16, SwarmRegistryUniversal.TagType.IBEACON_INCLUDES_MAC + fleetOwner, fleetId, providerId, new bytes(100), 16, SwarmRegistryUniversalUpgradeable.TagType.IBEACON_INCLUDES_MAC ); // Swarm ID is deterministic hash of (fleetUuid, filter, fingerprintSize, tagType) - uint256 expectedId = swarmRegistry.computeSwarmId(_getFleetUuid(fleetId), new bytes(100), 16, SwarmRegistryUniversal.TagType.IBEACON_INCLUDES_MAC); + uint256 expectedId = swarmRegistry.computeSwarmId(_getFleetUuid(fleetId), new bytes(100), 16, SwarmRegistryUniversalUpgradeable.TagType.IBEACON_INCLUDES_MAC); assertEq(swarmId, expectedId); } @@ -151,23 +186,23 @@ contract SwarmRegistryUniversalTest is Test { uint256 providerId = _registerProvider(providerOwner, "url1"); uint256 swarmId = - _registerSwarm(fleetOwner, fleetId, providerId, new bytes(50), 12, SwarmRegistryUniversal.TagType.VENDOR_ID); + _registerSwarm(fleetOwner, fleetId, providerId, new bytes(50), 12, SwarmRegistryUniversalUpgradeable.TagType.VENDOR_ID); ( bytes16 storedFleetUuid, uint256 storedProviderId, uint32 storedFilterLen, uint8 storedFpSize, - SwarmRegistryUniversal.TagType storedTagType, - SwarmRegistryUniversal.SwarmStatus storedStatus + SwarmRegistryUniversalUpgradeable.TagType storedTagType, + SwarmRegistryUniversalUpgradeable.SwarmStatus storedStatus ) = swarmRegistry.swarms(swarmId); assertEq(storedFleetUuid, _getFleetUuid(fleetId)); assertEq(storedProviderId, providerId); assertEq(storedFilterLen, 50); assertEq(storedFpSize, 12); - assertEq(uint8(storedTagType), uint8(SwarmRegistryUniversal.TagType.VENDOR_ID)); - assertEq(uint8(storedStatus), uint8(SwarmRegistryUniversal.SwarmStatus.REGISTERED)); + assertEq(uint8(storedTagType), uint8(SwarmRegistryUniversalUpgradeable.TagType.VENDOR_ID)); + assertEq(uint8(storedStatus), uint8(SwarmRegistryUniversalUpgradeable.SwarmStatus.REGISTERED)); } function test_registerSwarm_storesFilterData() public { @@ -181,7 +216,7 @@ contract SwarmRegistryUniversalTest is Test { filter[99] = 0xEF; uint256 swarmId = - _registerSwarm(fleetOwner, fleetId, providerId, filter, 16, SwarmRegistryUniversal.TagType.GENERIC); + _registerSwarm(fleetOwner, fleetId, providerId, filter, 16, SwarmRegistryUniversalUpgradeable.TagType.GENERIC); bytes memory storedFilter = swarmRegistry.getFilterData(swarmId); assertEq(storedFilter.length, 100); @@ -196,10 +231,10 @@ contract SwarmRegistryUniversalTest is Test { bytes memory filter = new bytes(32); - uint256 expectedId = swarmRegistry.computeSwarmId(_getFleetUuid(fleetId), filter, 8, SwarmRegistryUniversal.TagType.GENERIC); + uint256 expectedId = swarmRegistry.computeSwarmId(_getFleetUuid(fleetId), filter, 8, SwarmRegistryUniversalUpgradeable.TagType.GENERIC); uint256 swarmId = - _registerSwarm(fleetOwner, fleetId, providerId, filter, 8, SwarmRegistryUniversal.TagType.GENERIC); + _registerSwarm(fleetOwner, fleetId, providerId, filter, 8, SwarmRegistryUniversalUpgradeable.TagType.GENERIC); assertEq(swarmId, expectedId); } @@ -207,11 +242,11 @@ contract SwarmRegistryUniversalTest is Test { uint256 fleetId = _registerFleet(fleetOwner, "f1"); uint256 providerId = _registerProvider(providerOwner, "url1"); - _registerSwarm(fleetOwner, fleetId, providerId, new bytes(32), 8, SwarmRegistryUniversal.TagType.GENERIC); + _registerSwarm(fleetOwner, fleetId, providerId, new bytes(32), 8, SwarmRegistryUniversalUpgradeable.TagType.GENERIC); vm.prank(fleetOwner); - vm.expectRevert(SwarmRegistryUniversal.SwarmAlreadyExists.selector); - swarmRegistry.registerSwarm(_getFleetUuid(fleetId), providerId, new bytes(32), 8, SwarmRegistryUniversal.TagType.GENERIC); + vm.expectRevert(SwarmRegistryUniversalUpgradeable.SwarmAlreadyExists.selector); + swarmRegistry.registerSwarm(_getFleetUuid(fleetId), providerId, new bytes(32), 8, SwarmRegistryUniversalUpgradeable.TagType.GENERIC); } function test_registerSwarm_emitsSwarmRegistered() public { @@ -219,12 +254,12 @@ contract SwarmRegistryUniversalTest is Test { uint256 providerId = _registerProvider(providerOwner, "url1"); bytes memory filter = new bytes(50); - uint256 expectedId = swarmRegistry.computeSwarmId(_getFleetUuid(fleetId), filter, 16, SwarmRegistryUniversal.TagType.GENERIC); + uint256 expectedId = swarmRegistry.computeSwarmId(_getFleetUuid(fleetId), filter, 16, SwarmRegistryUniversalUpgradeable.TagType.GENERIC); vm.expectEmit(true, true, true, true); emit SwarmRegistered(expectedId, _getFleetUuid(fleetId), providerId, fleetOwner, 50); - _registerSwarm(fleetOwner, fleetId, providerId, filter, 16, SwarmRegistryUniversal.TagType.GENERIC); + _registerSwarm(fleetOwner, fleetId, providerId, filter, 16, SwarmRegistryUniversalUpgradeable.TagType.GENERIC); } function test_registerSwarm_linksUuidSwarms() public { @@ -239,9 +274,9 @@ contract SwarmRegistryUniversalTest is Test { filter2[0] = 0x02; uint256 s1 = - _registerSwarm(fleetOwner, fleetId, providerId1, filter1, 8, SwarmRegistryUniversal.TagType.GENERIC); + _registerSwarm(fleetOwner, fleetId, providerId1, filter1, 8, SwarmRegistryUniversalUpgradeable.TagType.GENERIC); uint256 s2 = - _registerSwarm(fleetOwner, fleetId, providerId2, filter2, 8, SwarmRegistryUniversal.TagType.GENERIC); + _registerSwarm(fleetOwner, fleetId, providerId2, filter2, 8, SwarmRegistryUniversalUpgradeable.TagType.GENERIC); assertEq(swarmRegistry.uuidSwarms(_getFleetUuid(fleetId), 0), s1); assertEq(swarmRegistry.uuidSwarms(_getFleetUuid(fleetId), 1), s2); @@ -255,25 +290,25 @@ contract SwarmRegistryUniversalTest is Test { uint256 providerId = _registerProvider(providerOwner, "url"); uint256 s1 = _registerSwarm( - fleetOwner, fleetId1, providerId, new bytes(32), 8, SwarmRegistryUniversal.TagType.IBEACON_PAYLOAD_ONLY + fleetOwner, fleetId1, providerId, new bytes(32), 8, SwarmRegistryUniversalUpgradeable.TagType.IBEACON_PAYLOAD_ONLY ); uint256 s2 = _registerSwarm( - fleetOwner, fleetId2, providerId, new bytes(32), 8, SwarmRegistryUniversal.TagType.IBEACON_INCLUDES_MAC + fleetOwner, fleetId2, providerId, new bytes(32), 8, SwarmRegistryUniversalUpgradeable.TagType.IBEACON_INCLUDES_MAC ); uint256 s3 = - _registerSwarm(fleetOwner, fleetId3, providerId, new bytes(32), 8, SwarmRegistryUniversal.TagType.VENDOR_ID); + _registerSwarm(fleetOwner, fleetId3, providerId, new bytes(32), 8, SwarmRegistryUniversalUpgradeable.TagType.VENDOR_ID); uint256 s4 = - _registerSwarm(fleetOwner, fleetId4, providerId, new bytes(32), 8, SwarmRegistryUniversal.TagType.GENERIC); + _registerSwarm(fleetOwner, fleetId4, providerId, new bytes(32), 8, SwarmRegistryUniversalUpgradeable.TagType.GENERIC); - (,,,, SwarmRegistryUniversal.TagType t1,) = swarmRegistry.swarms(s1); - (,,,, SwarmRegistryUniversal.TagType t2,) = swarmRegistry.swarms(s2); - (,,,, SwarmRegistryUniversal.TagType t3,) = swarmRegistry.swarms(s3); - (,,,, SwarmRegistryUniversal.TagType t4,) = swarmRegistry.swarms(s4); + (,,,, SwarmRegistryUniversalUpgradeable.TagType t1,) = swarmRegistry.swarms(s1); + (,,,, SwarmRegistryUniversalUpgradeable.TagType t2,) = swarmRegistry.swarms(s2); + (,,,, SwarmRegistryUniversalUpgradeable.TagType t3,) = swarmRegistry.swarms(s3); + (,,,, SwarmRegistryUniversalUpgradeable.TagType t4,) = swarmRegistry.swarms(s4); - assertEq(uint8(t1), uint8(SwarmRegistryUniversal.TagType.IBEACON_PAYLOAD_ONLY)); - assertEq(uint8(t2), uint8(SwarmRegistryUniversal.TagType.IBEACON_INCLUDES_MAC)); - assertEq(uint8(t3), uint8(SwarmRegistryUniversal.TagType.VENDOR_ID)); - assertEq(uint8(t4), uint8(SwarmRegistryUniversal.TagType.GENERIC)); + assertEq(uint8(t1), uint8(SwarmRegistryUniversalUpgradeable.TagType.IBEACON_PAYLOAD_ONLY)); + assertEq(uint8(t2), uint8(SwarmRegistryUniversalUpgradeable.TagType.IBEACON_INCLUDES_MAC)); + assertEq(uint8(t3), uint8(SwarmRegistryUniversalUpgradeable.TagType.VENDOR_ID)); + assertEq(uint8(t4), uint8(SwarmRegistryUniversalUpgradeable.TagType.GENERIC)); } // ============================== @@ -284,8 +319,8 @@ contract SwarmRegistryUniversalTest is Test { uint256 providerId = _registerProvider(providerOwner, "url1"); vm.prank(fleetOwner); - vm.expectRevert(SwarmRegistryUniversal.InvalidUuid.selector); - swarmRegistry.registerSwarm(bytes16(0), providerId, new bytes(32), 8, SwarmRegistryUniversal.TagType.GENERIC); + vm.expectRevert(SwarmRegistryUniversalUpgradeable.InvalidUuid.selector); + swarmRegistry.registerSwarm(bytes16(0), providerId, new bytes(32), 8, SwarmRegistryUniversalUpgradeable.TagType.GENERIC); } function test_RevertIf_registerSwarm_providerDoesNotExist() public { @@ -293,16 +328,16 @@ contract SwarmRegistryUniversalTest is Test { uint256 nonExistentProvider = 12345; vm.prank(fleetOwner); - vm.expectRevert(SwarmRegistryUniversal.ProviderDoesNotExist.selector); - swarmRegistry.registerSwarm(_getFleetUuid(fleetId), nonExistentProvider, new bytes(32), 8, SwarmRegistryUniversal.TagType.GENERIC); + vm.expectRevert(SwarmRegistryUniversalUpgradeable.ProviderDoesNotExist.selector); + swarmRegistry.registerSwarm(_getFleetUuid(fleetId), nonExistentProvider, new bytes(32), 8, SwarmRegistryUniversalUpgradeable.TagType.GENERIC); } function test_RevertIf_registerSwarm_notFleetOwner() public { uint256 fleetId = _registerFleet(fleetOwner, "my-fleet"); vm.prank(caller); - vm.expectRevert(SwarmRegistryUniversal.NotUuidOwner.selector); - swarmRegistry.registerSwarm(_getFleetUuid(fleetId), 1, new bytes(10), 16, SwarmRegistryUniversal.TagType.GENERIC); + vm.expectRevert(SwarmRegistryUniversalUpgradeable.NotUuidOwner.selector); + swarmRegistry.registerSwarm(_getFleetUuid(fleetId), 1, new bytes(10), 16, SwarmRegistryUniversalUpgradeable.TagType.GENERIC); } function test_RevertIf_registerSwarm_fingerprintSizeZero() public { @@ -310,8 +345,8 @@ contract SwarmRegistryUniversalTest is Test { uint256 providerId = _registerProvider(providerOwner, "url1"); vm.prank(fleetOwner); - vm.expectRevert(SwarmRegistryUniversal.InvalidFingerprintSize.selector); - swarmRegistry.registerSwarm(_getFleetUuid(fleetId), providerId, new bytes(32), 0, SwarmRegistryUniversal.TagType.GENERIC); + vm.expectRevert(SwarmRegistryUniversalUpgradeable.InvalidFingerprintSize.selector); + swarmRegistry.registerSwarm(_getFleetUuid(fleetId), providerId, new bytes(32), 0, SwarmRegistryUniversalUpgradeable.TagType.GENERIC); } function test_RevertIf_registerSwarm_fingerprintSizeExceedsMax() public { @@ -319,8 +354,8 @@ contract SwarmRegistryUniversalTest is Test { uint256 providerId = _registerProvider(providerOwner, "url1"); vm.prank(fleetOwner); - vm.expectRevert(SwarmRegistryUniversal.InvalidFingerprintSize.selector); - swarmRegistry.registerSwarm(_getFleetUuid(fleetId), providerId, new bytes(32), 17, SwarmRegistryUniversal.TagType.GENERIC); + vm.expectRevert(SwarmRegistryUniversalUpgradeable.InvalidFingerprintSize.selector); + swarmRegistry.registerSwarm(_getFleetUuid(fleetId), providerId, new bytes(32), 17, SwarmRegistryUniversalUpgradeable.TagType.GENERIC); } function test_RevertIf_registerSwarm_emptyFilter() public { @@ -328,8 +363,8 @@ contract SwarmRegistryUniversalTest is Test { uint256 providerId = _registerProvider(providerOwner, "url1"); vm.prank(fleetOwner); - vm.expectRevert(SwarmRegistryUniversal.InvalidFilterSize.selector); - swarmRegistry.registerSwarm(_getFleetUuid(fleetId), providerId, new bytes(0), 8, SwarmRegistryUniversal.TagType.GENERIC); + vm.expectRevert(SwarmRegistryUniversalUpgradeable.InvalidFilterSize.selector); + swarmRegistry.registerSwarm(_getFleetUuid(fleetId), providerId, new bytes(0), 8, SwarmRegistryUniversalUpgradeable.TagType.GENERIC); } function test_RevertIf_registerSwarm_filterTooLarge() public { @@ -337,8 +372,8 @@ contract SwarmRegistryUniversalTest is Test { uint256 providerId = _registerProvider(providerOwner, "url1"); vm.prank(fleetOwner); - vm.expectRevert(SwarmRegistryUniversal.FilterTooLarge.selector); - swarmRegistry.registerSwarm(_getFleetUuid(fleetId), providerId, new bytes(24577), 8, SwarmRegistryUniversal.TagType.GENERIC); + vm.expectRevert(SwarmRegistryUniversalUpgradeable.FilterTooLarge.selector); + swarmRegistry.registerSwarm(_getFleetUuid(fleetId), providerId, new bytes(24577), 8, SwarmRegistryUniversalUpgradeable.TagType.GENERIC); } function test_registerSwarm_maxFingerprintSize() public { @@ -346,7 +381,7 @@ contract SwarmRegistryUniversalTest is Test { uint256 providerId = _registerProvider(providerOwner, "url1"); uint256 swarmId = - _registerSwarm(fleetOwner, fleetId, providerId, new bytes(100), 16, SwarmRegistryUniversal.TagType.GENERIC); + _registerSwarm(fleetOwner, fleetId, providerId, new bytes(100), 16, SwarmRegistryUniversalUpgradeable.TagType.GENERIC); assertTrue(swarmId != 0); } @@ -356,7 +391,7 @@ contract SwarmRegistryUniversalTest is Test { // Exactly MAX_FILTER_SIZE (24576) should succeed uint256 swarmId = - _registerSwarm(fleetOwner, fleetId, providerId, new bytes(24576), 8, SwarmRegistryUniversal.TagType.GENERIC); + _registerSwarm(fleetOwner, fleetId, providerId, new bytes(24576), 8, SwarmRegistryUniversalUpgradeable.TagType.GENERIC); assertTrue(swarmId != 0); } @@ -366,7 +401,7 @@ contract SwarmRegistryUniversalTest is Test { // 1 byte filter uint256 swarmId = - _registerSwarm(fleetOwner, fleetId, providerId, new bytes(1), 8, SwarmRegistryUniversal.TagType.GENERIC); + _registerSwarm(fleetOwner, fleetId, providerId, new bytes(1), 8, SwarmRegistryUniversalUpgradeable.TagType.GENERIC); assertTrue(swarmId != 0); } @@ -378,42 +413,42 @@ contract SwarmRegistryUniversalTest is Test { uint256 fleetId = _registerFleet(fleetOwner, "f1"); uint256 providerId = _registerProvider(providerOwner, "url1"); uint256 swarmId = - _registerSwarm(fleetOwner, fleetId, providerId, new bytes(50), 8, SwarmRegistryUniversal.TagType.GENERIC); + _registerSwarm(fleetOwner, fleetId, providerId, new bytes(50), 8, SwarmRegistryUniversalUpgradeable.TagType.GENERIC); vm.expectEmit(true, true, true, true); - emit SwarmStatusChanged(swarmId, SwarmRegistryUniversal.SwarmStatus.ACCEPTED); + emit SwarmStatusChanged(swarmId, SwarmRegistryUniversalUpgradeable.SwarmStatus.ACCEPTED); vm.prank(providerOwner); swarmRegistry.acceptSwarm(swarmId); - (,,,,, SwarmRegistryUniversal.SwarmStatus status) = swarmRegistry.swarms(swarmId); - assertEq(uint8(status), uint8(SwarmRegistryUniversal.SwarmStatus.ACCEPTED)); + (,,,,, SwarmRegistryUniversalUpgradeable.SwarmStatus status) = swarmRegistry.swarms(swarmId); + assertEq(uint8(status), uint8(SwarmRegistryUniversalUpgradeable.SwarmStatus.ACCEPTED)); } function test_rejectSwarm_setsStatusAndEmits() public { uint256 fleetId = _registerFleet(fleetOwner, "f1"); uint256 providerId = _registerProvider(providerOwner, "url1"); uint256 swarmId = - _registerSwarm(fleetOwner, fleetId, providerId, new bytes(50), 8, SwarmRegistryUniversal.TagType.GENERIC); + _registerSwarm(fleetOwner, fleetId, providerId, new bytes(50), 8, SwarmRegistryUniversalUpgradeable.TagType.GENERIC); vm.expectEmit(true, true, true, true); - emit SwarmStatusChanged(swarmId, SwarmRegistryUniversal.SwarmStatus.REJECTED); + emit SwarmStatusChanged(swarmId, SwarmRegistryUniversalUpgradeable.SwarmStatus.REJECTED); vm.prank(providerOwner); swarmRegistry.rejectSwarm(swarmId); - (,,,,, SwarmRegistryUniversal.SwarmStatus status) = swarmRegistry.swarms(swarmId); - assertEq(uint8(status), uint8(SwarmRegistryUniversal.SwarmStatus.REJECTED)); + (,,,,, SwarmRegistryUniversalUpgradeable.SwarmStatus status) = swarmRegistry.swarms(swarmId); + assertEq(uint8(status), uint8(SwarmRegistryUniversalUpgradeable.SwarmStatus.REJECTED)); } function test_RevertIf_acceptSwarm_notProviderOwner() public { uint256 fleetId = _registerFleet(fleetOwner, "f1"); uint256 providerId = _registerProvider(providerOwner, "url1"); uint256 swarmId = - _registerSwarm(fleetOwner, fleetId, providerId, new bytes(50), 8, SwarmRegistryUniversal.TagType.GENERIC); + _registerSwarm(fleetOwner, fleetId, providerId, new bytes(50), 8, SwarmRegistryUniversalUpgradeable.TagType.GENERIC); vm.prank(caller); - vm.expectRevert(SwarmRegistryUniversal.NotProviderOwner.selector); + vm.expectRevert(SwarmRegistryUniversalUpgradeable.NotProviderOwner.selector); swarmRegistry.acceptSwarm(swarmId); } @@ -421,10 +456,10 @@ contract SwarmRegistryUniversalTest is Test { uint256 fleetId = _registerFleet(fleetOwner, "f1"); uint256 providerId = _registerProvider(providerOwner, "url1"); uint256 swarmId = - _registerSwarm(fleetOwner, fleetId, providerId, new bytes(50), 8, SwarmRegistryUniversal.TagType.GENERIC); + _registerSwarm(fleetOwner, fleetId, providerId, new bytes(50), 8, SwarmRegistryUniversalUpgradeable.TagType.GENERIC); vm.prank(fleetOwner); // fleet owner != provider owner - vm.expectRevert(SwarmRegistryUniversal.NotProviderOwner.selector); + vm.expectRevert(SwarmRegistryUniversalUpgradeable.NotProviderOwner.selector); swarmRegistry.rejectSwarm(swarmId); } @@ -432,10 +467,10 @@ contract SwarmRegistryUniversalTest is Test { uint256 fleetId = _registerFleet(fleetOwner, "f1"); uint256 providerId = _registerProvider(providerOwner, "url1"); uint256 swarmId = - _registerSwarm(fleetOwner, fleetId, providerId, new bytes(50), 8, SwarmRegistryUniversal.TagType.GENERIC); + _registerSwarm(fleetOwner, fleetId, providerId, new bytes(50), 8, SwarmRegistryUniversalUpgradeable.TagType.GENERIC); vm.prank(fleetOwner); - vm.expectRevert(SwarmRegistryUniversal.NotProviderOwner.selector); + vm.expectRevert(SwarmRegistryUniversalUpgradeable.NotProviderOwner.selector); swarmRegistry.acceptSwarm(swarmId); } @@ -443,7 +478,7 @@ contract SwarmRegistryUniversalTest is Test { uint256 fleetId = _registerFleet(fleetOwner, "f1"); uint256 providerId = _registerProvider(providerOwner, "url1"); uint256 swarmId = - _registerSwarm(fleetOwner, fleetId, providerId, new bytes(50), 8, SwarmRegistryUniversal.TagType.GENERIC); + _registerSwarm(fleetOwner, fleetId, providerId, new bytes(50), 8, SwarmRegistryUniversalUpgradeable.TagType.GENERIC); vm.prank(providerOwner); swarmRegistry.rejectSwarm(swarmId); @@ -451,15 +486,15 @@ contract SwarmRegistryUniversalTest is Test { vm.prank(providerOwner); swarmRegistry.acceptSwarm(swarmId); - (,,,,, SwarmRegistryUniversal.SwarmStatus status) = swarmRegistry.swarms(swarmId); - assertEq(uint8(status), uint8(SwarmRegistryUniversal.SwarmStatus.ACCEPTED)); + (,,,,, SwarmRegistryUniversalUpgradeable.SwarmStatus status) = swarmRegistry.swarms(swarmId); + assertEq(uint8(status), uint8(SwarmRegistryUniversalUpgradeable.SwarmStatus.ACCEPTED)); } function test_rejectSwarm_afterAccept() public { uint256 fleetId = _registerFleet(fleetOwner, "f1"); uint256 providerId = _registerProvider(providerOwner, "url1"); uint256 swarmId = - _registerSwarm(fleetOwner, fleetId, providerId, new bytes(50), 8, SwarmRegistryUniversal.TagType.GENERIC); + _registerSwarm(fleetOwner, fleetId, providerId, new bytes(50), 8, SwarmRegistryUniversalUpgradeable.TagType.GENERIC); vm.prank(providerOwner); swarmRegistry.acceptSwarm(swarmId); @@ -467,8 +502,8 @@ contract SwarmRegistryUniversalTest is Test { vm.prank(providerOwner); swarmRegistry.rejectSwarm(swarmId); - (,,,,, SwarmRegistryUniversal.SwarmStatus status) = swarmRegistry.swarms(swarmId); - assertEq(uint8(status), uint8(SwarmRegistryUniversal.SwarmStatus.REJECTED)); + (,,,,, SwarmRegistryUniversalUpgradeable.SwarmStatus status) = swarmRegistry.swarms(swarmId); + assertEq(uint8(status), uint8(SwarmRegistryUniversalUpgradeable.SwarmStatus.REJECTED)); } // ============================== @@ -494,7 +529,7 @@ contract SwarmRegistryUniversalTest is Test { _write16Bit(filter, h1, uint16(expectedFp)); uint256 swarmId = - _registerSwarm(fleetOwner, fleetId, providerId, filter, fpSize, SwarmRegistryUniversal.TagType.GENERIC); + _registerSwarm(fleetOwner, fleetId, providerId, filter, fpSize, SwarmRegistryUniversalUpgradeable.TagType.GENERIC); bytes32 tagHash = keccak256(tagId); assertTrue(swarmRegistry.checkMembership(swarmId, tagHash), "Tag should be member"); @@ -522,14 +557,14 @@ contract SwarmRegistryUniversalTest is Test { _write8Bit(filter, h1, uint8(expectedFp)); uint256 swarmId = - _registerSwarm(fleetOwner, fleetId, providerId, filter, fpSize, SwarmRegistryUniversal.TagType.GENERIC); + _registerSwarm(fleetOwner, fleetId, providerId, filter, fpSize, SwarmRegistryUniversalUpgradeable.TagType.GENERIC); assertTrue(swarmRegistry.checkMembership(swarmId, keccak256(tagId)), "8-bit valid tag should pass"); assertFalse(swarmRegistry.checkMembership(swarmId, keccak256(hex"FFFFFF")), "8-bit invalid tag should fail"); } function test_RevertIf_checkMembership_swarmNotFound() public { - vm.expectRevert(SwarmRegistryUniversal.SwarmNotFound.selector); + vm.expectRevert(SwarmRegistryUniversalUpgradeable.SwarmNotFound.selector); swarmRegistry.checkMembership(999, keccak256("anything")); } @@ -540,7 +575,7 @@ contract SwarmRegistryUniversalTest is Test { // All-zero filter: f1^f2^f3 = 0^0^0 = 0 bytes memory filter = new bytes(64); uint256 swarmId = - _registerSwarm(fleetOwner, fleetId, providerId, filter, 16, SwarmRegistryUniversal.TagType.GENERIC); + _registerSwarm(fleetOwner, fleetId, providerId, filter, 16, SwarmRegistryUniversalUpgradeable.TagType.GENERIC); // Should not revert regardless of result swarmRegistry.checkMembership(swarmId, keccak256("test1")); @@ -553,7 +588,7 @@ contract SwarmRegistryUniversalTest is Test { // 1-byte filter with 16-bit fingerprint: m = (1*8)/16 = 0, returns false immediately bytes memory filter = new bytes(1); - uint256 swarmId = _registerSwarm(fleetOwner, fleetId, providerId, filter, 16, SwarmRegistryUniversal.TagType.GENERIC); + uint256 swarmId = _registerSwarm(fleetOwner, fleetId, providerId, filter, 16, SwarmRegistryUniversalUpgradeable.TagType.GENERIC); // Should return false (not revert) because m == 0 assertFalse(swarmRegistry.checkMembership(swarmId, keccak256("test")), "m=0 should return false"); @@ -572,7 +607,7 @@ contract SwarmRegistryUniversalTest is Test { filter[99] = 0x01; uint256 swarmId = - _registerSwarm(fleetOwner, fleetId, providerId, filter, 16, SwarmRegistryUniversal.TagType.GENERIC); + _registerSwarm(fleetOwner, fleetId, providerId, filter, 16, SwarmRegistryUniversalUpgradeable.TagType.GENERIC); bytes memory stored = swarmRegistry.getFilterData(swarmId); assertEq(stored.length, 100); @@ -581,7 +616,7 @@ contract SwarmRegistryUniversalTest is Test { } function test_RevertIf_getFilterData_swarmNotFound() public { - vm.expectRevert(SwarmRegistryUniversal.SwarmNotFound.selector); + vm.expectRevert(SwarmRegistryUniversalUpgradeable.SwarmNotFound.selector); swarmRegistry.getFilterData(999); } @@ -596,12 +631,12 @@ contract SwarmRegistryUniversalTest is Test { uint256 providerId3 = _registerProvider(providerOwner, "url3"); uint256 s1 = - _registerSwarm(fleetOwner, fleetId, providerId1, new bytes(32), 8, SwarmRegistryUniversal.TagType.GENERIC); + _registerSwarm(fleetOwner, fleetId, providerId1, new bytes(32), 8, SwarmRegistryUniversalUpgradeable.TagType.GENERIC); uint256 s2 = _registerSwarm( - fleetOwner, fleetId, providerId2, new bytes(64), 16, SwarmRegistryUniversal.TagType.VENDOR_ID + fleetOwner, fleetId, providerId2, new bytes(64), 16, SwarmRegistryUniversalUpgradeable.TagType.VENDOR_ID ); uint256 s3 = _registerSwarm( - fleetOwner, fleetId, providerId3, new bytes(50), 12, SwarmRegistryUniversal.TagType.IBEACON_PAYLOAD_ONLY + fleetOwner, fleetId, providerId3, new bytes(50), 12, SwarmRegistryUniversalUpgradeable.TagType.IBEACON_PAYLOAD_ONLY ); // IDs are distinct hashes @@ -632,7 +667,7 @@ contract SwarmRegistryUniversalTest is Test { uint256 providerId = _registerProvider(providerOwner, string(abi.encodePacked("url-", fpSize))); uint256 swarmId = _registerSwarm( - fleetOwner, fleetId, providerId, new bytes(64), fpSize, SwarmRegistryUniversal.TagType.GENERIC + fleetOwner, fleetId, providerId, new bytes(64), fpSize, SwarmRegistryUniversalUpgradeable.TagType.GENERIC ); (,,, uint8 storedFp,,) = swarmRegistry.swarms(swarmId); @@ -646,8 +681,8 @@ contract SwarmRegistryUniversalTest is Test { uint256 providerId = _registerProvider(providerOwner, "url1"); vm.prank(fleetOwner); - vm.expectRevert(SwarmRegistryUniversal.InvalidFingerprintSize.selector); - swarmRegistry.registerSwarm(_getFleetUuid(fleetId), providerId, new bytes(32), fpSize, SwarmRegistryUniversal.TagType.GENERIC); + vm.expectRevert(SwarmRegistryUniversalUpgradeable.InvalidFingerprintSize.selector); + swarmRegistry.registerSwarm(_getFleetUuid(fleetId), providerId, new bytes(32), fpSize, SwarmRegistryUniversalUpgradeable.TagType.GENERIC); } function testFuzz_registerSwarm_filterSizeRange(uint256 size) public { @@ -657,7 +692,7 @@ contract SwarmRegistryUniversalTest is Test { uint256 providerId = _registerProvider(providerOwner, string(abi.encodePacked("url-", size))); uint256 swarmId = - _registerSwarm(fleetOwner, fleetId, providerId, new bytes(size), 8, SwarmRegistryUniversal.TagType.GENERIC); + _registerSwarm(fleetOwner, fleetId, providerId, new bytes(size), 8, SwarmRegistryUniversalUpgradeable.TagType.GENERIC); (,, uint32 storedLen,,,) = swarmRegistry.swarms(swarmId); assertEq(storedLen, uint32(size)); @@ -673,7 +708,7 @@ contract SwarmRegistryUniversalTest is Test { uint256 providerId2 = _registerProvider(providerOwner, "url2"); uint256 swarmId = - _registerSwarm(fleetOwner, fleetId, providerId1, new bytes(50), 8, SwarmRegistryUniversal.TagType.GENERIC); + _registerSwarm(fleetOwner, fleetId, providerId1, new bytes(50), 8, SwarmRegistryUniversalUpgradeable.TagType.GENERIC); // Provider accepts vm.prank(providerOwner); @@ -687,16 +722,16 @@ contract SwarmRegistryUniversalTest is Test { swarmRegistry.updateSwarmProvider(swarmId, providerId2); // Check new provider and status reset - (, uint256 newProviderId,,,, SwarmRegistryUniversal.SwarmStatus status) = swarmRegistry.swarms(swarmId); + (, uint256 newProviderId,,,, SwarmRegistryUniversalUpgradeable.SwarmStatus status) = swarmRegistry.swarms(swarmId); assertEq(newProviderId, providerId2); - assertEq(uint8(status), uint8(SwarmRegistryUniversal.SwarmStatus.REGISTERED)); + assertEq(uint8(status), uint8(SwarmRegistryUniversalUpgradeable.SwarmStatus.REGISTERED)); } function test_RevertIf_updateSwarmProvider_swarmNotFound() public { uint256 providerId = _registerProvider(providerOwner, "url1"); vm.prank(fleetOwner); - vm.expectRevert(SwarmRegistryUniversal.SwarmNotFound.selector); + vm.expectRevert(SwarmRegistryUniversalUpgradeable.SwarmNotFound.selector); swarmRegistry.updateSwarmProvider(999, providerId); } @@ -706,10 +741,10 @@ contract SwarmRegistryUniversalTest is Test { uint256 providerId2 = _registerProvider(providerOwner, "url2"); uint256 swarmId = - _registerSwarm(fleetOwner, fleetId, providerId1, new bytes(50), 8, SwarmRegistryUniversal.TagType.GENERIC); + _registerSwarm(fleetOwner, fleetId, providerId1, new bytes(50), 8, SwarmRegistryUniversalUpgradeable.TagType.GENERIC); vm.prank(caller); - vm.expectRevert(SwarmRegistryUniversal.NotUuidOwner.selector); + vm.expectRevert(SwarmRegistryUniversalUpgradeable.NotUuidOwner.selector); swarmRegistry.updateSwarmProvider(swarmId, providerId2); } @@ -718,10 +753,10 @@ contract SwarmRegistryUniversalTest is Test { uint256 providerId = _registerProvider(providerOwner, "url1"); uint256 swarmId = - _registerSwarm(fleetOwner, fleetId, providerId, new bytes(50), 8, SwarmRegistryUniversal.TagType.GENERIC); + _registerSwarm(fleetOwner, fleetId, providerId, new bytes(50), 8, SwarmRegistryUniversalUpgradeable.TagType.GENERIC); vm.prank(fleetOwner); - vm.expectRevert(SwarmRegistryUniversal.ProviderDoesNotExist.selector); + vm.expectRevert(SwarmRegistryUniversalUpgradeable.ProviderDoesNotExist.selector); swarmRegistry.updateSwarmProvider(swarmId, 99999); } @@ -733,7 +768,7 @@ contract SwarmRegistryUniversalTest is Test { uint256 fleetId = _registerFleet(fleetOwner, "f1"); uint256 providerId = _registerProvider(providerOwner, "url1"); uint256 swarmId = - _registerSwarm(fleetOwner, fleetId, providerId, new bytes(50), 8, SwarmRegistryUniversal.TagType.GENERIC); + _registerSwarm(fleetOwner, fleetId, providerId, new bytes(50), 8, SwarmRegistryUniversalUpgradeable.TagType.GENERIC); vm.expectEmit(true, true, true, true); emit SwarmDeleted(swarmId, _getFleetUuid(fleetId), fleetOwner); @@ -759,9 +794,9 @@ contract SwarmRegistryUniversalTest is Test { filter2[0] = 0x02; uint256 swarm1 = - _registerSwarm(fleetOwner, fleetId, providerId1, filter1, 8, SwarmRegistryUniversal.TagType.GENERIC); + _registerSwarm(fleetOwner, fleetId, providerId1, filter1, 8, SwarmRegistryUniversalUpgradeable.TagType.GENERIC); uint256 swarm2 = - _registerSwarm(fleetOwner, fleetId, providerId2, filter2, 8, SwarmRegistryUniversal.TagType.GENERIC); + _registerSwarm(fleetOwner, fleetId, providerId2, filter2, 8, SwarmRegistryUniversalUpgradeable.TagType.GENERIC); // Delete first swarm vm.prank(fleetOwner); @@ -788,11 +823,11 @@ contract SwarmRegistryUniversalTest is Test { filter3[0] = 0x03; uint256 swarm1 = - _registerSwarm(fleetOwner, fleetId, providerId1, filter1, 8, SwarmRegistryUniversal.TagType.GENERIC); + _registerSwarm(fleetOwner, fleetId, providerId1, filter1, 8, SwarmRegistryUniversalUpgradeable.TagType.GENERIC); uint256 swarm2 = - _registerSwarm(fleetOwner, fleetId, providerId2, filter2, 8, SwarmRegistryUniversal.TagType.GENERIC); + _registerSwarm(fleetOwner, fleetId, providerId2, filter2, 8, SwarmRegistryUniversalUpgradeable.TagType.GENERIC); uint256 swarm3 = - _registerSwarm(fleetOwner, fleetId, providerId3, filter3, 8, SwarmRegistryUniversal.TagType.GENERIC); + _registerSwarm(fleetOwner, fleetId, providerId3, filter3, 8, SwarmRegistryUniversalUpgradeable.TagType.GENERIC); // Delete middle swarm vm.prank(fleetOwner); @@ -815,7 +850,7 @@ contract SwarmRegistryUniversalTest is Test { } uint256 swarmId = - _registerSwarm(fleetOwner, fleetId, providerId, filterData, 8, SwarmRegistryUniversal.TagType.GENERIC); + _registerSwarm(fleetOwner, fleetId, providerId, filterData, 8, SwarmRegistryUniversalUpgradeable.TagType.GENERIC); // Delete swarm vm.prank(fleetOwner); @@ -828,7 +863,7 @@ contract SwarmRegistryUniversalTest is Test { function test_RevertIf_deleteSwarm_swarmNotFound() public { vm.prank(fleetOwner); - vm.expectRevert(SwarmRegistryUniversal.SwarmNotFound.selector); + vm.expectRevert(SwarmRegistryUniversalUpgradeable.SwarmNotFound.selector); swarmRegistry.deleteSwarm(999); } @@ -836,10 +871,10 @@ contract SwarmRegistryUniversalTest is Test { uint256 fleetId = _registerFleet(fleetOwner, "f1"); uint256 providerId = _registerProvider(providerOwner, "url1"); uint256 swarmId = - _registerSwarm(fleetOwner, fleetId, providerId, new bytes(50), 8, SwarmRegistryUniversal.TagType.GENERIC); + _registerSwarm(fleetOwner, fleetId, providerId, new bytes(50), 8, SwarmRegistryUniversalUpgradeable.TagType.GENERIC); vm.prank(caller); - vm.expectRevert(SwarmRegistryUniversal.NotUuidOwner.selector); + vm.expectRevert(SwarmRegistryUniversalUpgradeable.NotUuidOwner.selector); swarmRegistry.deleteSwarm(swarmId); } @@ -848,7 +883,7 @@ contract SwarmRegistryUniversalTest is Test { uint256 providerId1 = _registerProvider(providerOwner, "url1"); uint256 providerId2 = _registerProvider(providerOwner, "url2"); uint256 swarmId = - _registerSwarm(fleetOwner, fleetId, providerId1, new bytes(50), 8, SwarmRegistryUniversal.TagType.GENERIC); + _registerSwarm(fleetOwner, fleetId, providerId1, new bytes(50), 8, SwarmRegistryUniversalUpgradeable.TagType.GENERIC); // Update provider then delete vm.prank(fleetOwner); @@ -875,9 +910,9 @@ contract SwarmRegistryUniversalTest is Test { bytes memory filter3 = new bytes(50); filter3[0] = 0x03; - uint256 s1 = _registerSwarm(fleetOwner, fleetId, p1, filter1, 8, SwarmRegistryUniversal.TagType.GENERIC); - uint256 s2 = _registerSwarm(fleetOwner, fleetId, p2, filter2, 8, SwarmRegistryUniversal.TagType.GENERIC); - uint256 s3 = _registerSwarm(fleetOwner, fleetId, p3, filter3, 8, SwarmRegistryUniversal.TagType.GENERIC); + uint256 s1 = _registerSwarm(fleetOwner, fleetId, p1, filter1, 8, SwarmRegistryUniversalUpgradeable.TagType.GENERIC); + uint256 s2 = _registerSwarm(fleetOwner, fleetId, p2, filter2, 8, SwarmRegistryUniversalUpgradeable.TagType.GENERIC); + uint256 s3 = _registerSwarm(fleetOwner, fleetId, p3, filter3, 8, SwarmRegistryUniversalUpgradeable.TagType.GENERIC); // Verify initial indices assertEq(swarmRegistry.swarmIndexInUuid(s1), 0); @@ -901,7 +936,7 @@ contract SwarmRegistryUniversalTest is Test { uint256 fleetId = _registerFleet(fleetOwner, "f1"); uint256 providerId = _registerProvider(providerOwner, "url1"); uint256 swarmId = - _registerSwarm(fleetOwner, fleetId, providerId, new bytes(50), 8, SwarmRegistryUniversal.TagType.GENERIC); + _registerSwarm(fleetOwner, fleetId, providerId, new bytes(50), 8, SwarmRegistryUniversalUpgradeable.TagType.GENERIC); (bool fleetValid, bool providerValid) = swarmRegistry.isSwarmValid(swarmId); assertTrue(fleetValid); @@ -912,7 +947,7 @@ contract SwarmRegistryUniversalTest is Test { uint256 fleetId = _registerFleet(fleetOwner, "f1"); uint256 providerId = _registerProvider(providerOwner, "url1"); uint256 swarmId = - _registerSwarm(fleetOwner, fleetId, providerId, new bytes(50), 8, SwarmRegistryUniversal.TagType.GENERIC); + _registerSwarm(fleetOwner, fleetId, providerId, new bytes(50), 8, SwarmRegistryUniversalUpgradeable.TagType.GENERIC); vm.prank(providerOwner); providerContract.burn(providerId); @@ -926,7 +961,7 @@ contract SwarmRegistryUniversalTest is Test { uint256 fleetId = _registerFleet(fleetOwner, "f1"); uint256 providerId = _registerProvider(providerOwner, "url1"); uint256 swarmId = - _registerSwarm(fleetOwner, fleetId, providerId, new bytes(50), 8, SwarmRegistryUniversal.TagType.GENERIC); + _registerSwarm(fleetOwner, fleetId, providerId, new bytes(50), 8, SwarmRegistryUniversalUpgradeable.TagType.GENERIC); // Burn registered fleet token (operator = owner for fresh registration) // This mints an owned-only token back to the owner @@ -949,7 +984,7 @@ contract SwarmRegistryUniversalTest is Test { uint256 fleetId = _registerFleet(fleetOwner, "f1"); uint256 providerId = _registerProvider(providerOwner, "url1"); uint256 swarmId = - _registerSwarm(fleetOwner, fleetId, providerId, new bytes(50), 8, SwarmRegistryUniversal.TagType.GENERIC); + _registerSwarm(fleetOwner, fleetId, providerId, new bytes(50), 8, SwarmRegistryUniversalUpgradeable.TagType.GENERIC); // Burn registered fleet token → mints owned-only token vm.prank(fleetOwner); @@ -970,7 +1005,7 @@ contract SwarmRegistryUniversalTest is Test { } function test_RevertIf_isSwarmValid_swarmNotFound() public { - vm.expectRevert(SwarmRegistryUniversal.SwarmNotFound.selector); + vm.expectRevert(SwarmRegistryUniversalUpgradeable.SwarmNotFound.selector); swarmRegistry.isSwarmValid(999); } @@ -982,7 +1017,7 @@ contract SwarmRegistryUniversalTest is Test { uint256 fleetId = _registerFleet(fleetOwner, "f1"); uint256 providerId = _registerProvider(providerOwner, "url1"); uint256 swarmId = - _registerSwarm(fleetOwner, fleetId, providerId, new bytes(50), 8, SwarmRegistryUniversal.TagType.GENERIC); + _registerSwarm(fleetOwner, fleetId, providerId, new bytes(50), 8, SwarmRegistryUniversalUpgradeable.TagType.GENERIC); vm.prank(providerOwner); providerContract.burn(providerId); @@ -1001,7 +1036,7 @@ contract SwarmRegistryUniversalTest is Test { uint256 fleetId = _registerFleet(fleetOwner, "f1"); uint256 providerId = _registerProvider(providerOwner, "url1"); uint256 swarmId = - _registerSwarm(fleetOwner, fleetId, providerId, new bytes(50), 8, SwarmRegistryUniversal.TagType.GENERIC); + _registerSwarm(fleetOwner, fleetId, providerId, new bytes(50), 8, SwarmRegistryUniversalUpgradeable.TagType.GENERIC); // Burn registered fleet token → mints owned-only token vm.prank(fleetOwner); @@ -1032,8 +1067,8 @@ contract SwarmRegistryUniversalTest is Test { bytes memory filter2 = new bytes(50); filter2[0] = 0x02; - uint256 s1 = _registerSwarm(fleetOwner, fleetId, p1, filter1, 8, SwarmRegistryUniversal.TagType.GENERIC); - uint256 s2 = _registerSwarm(fleetOwner, fleetId, p2, filter2, 8, SwarmRegistryUniversal.TagType.GENERIC); + uint256 s1 = _registerSwarm(fleetOwner, fleetId, p1, filter1, 8, SwarmRegistryUniversalUpgradeable.TagType.GENERIC); + uint256 s2 = _registerSwarm(fleetOwner, fleetId, p2, filter2, 8, SwarmRegistryUniversalUpgradeable.TagType.GENERIC); // Burn provider of s1 vm.prank(providerOwner); @@ -1058,7 +1093,7 @@ contract SwarmRegistryUniversalTest is Test { } uint256 swarmId = - _registerSwarm(fleetOwner, fleetId, providerId, filter, 8, SwarmRegistryUniversal.TagType.GENERIC); + _registerSwarm(fleetOwner, fleetId, providerId, filter, 8, SwarmRegistryUniversalUpgradeable.TagType.GENERIC); vm.prank(providerOwner); providerContract.burn(providerId); @@ -1072,7 +1107,7 @@ contract SwarmRegistryUniversalTest is Test { } function test_RevertIf_purgeOrphanedSwarm_swarmNotFound() public { - vm.expectRevert(SwarmRegistryUniversal.SwarmNotFound.selector); + vm.expectRevert(SwarmRegistryUniversalUpgradeable.SwarmNotFound.selector); swarmRegistry.purgeOrphanedSwarm(999); } @@ -1080,9 +1115,9 @@ contract SwarmRegistryUniversalTest is Test { uint256 fleetId = _registerFleet(fleetOwner, "f1"); uint256 providerId = _registerProvider(providerOwner, "url1"); uint256 swarmId = - _registerSwarm(fleetOwner, fleetId, providerId, new bytes(50), 8, SwarmRegistryUniversal.TagType.GENERIC); + _registerSwarm(fleetOwner, fleetId, providerId, new bytes(50), 8, SwarmRegistryUniversalUpgradeable.TagType.GENERIC); - vm.expectRevert(SwarmRegistryUniversal.SwarmNotOrphaned.selector); + vm.expectRevert(SwarmRegistryUniversalUpgradeable.SwarmNotOrphaned.selector); swarmRegistry.purgeOrphanedSwarm(swarmId); } @@ -1094,13 +1129,13 @@ contract SwarmRegistryUniversalTest is Test { uint256 fleetId = _registerFleet(fleetOwner, "f1"); uint256 providerId = _registerProvider(providerOwner, "url1"); uint256 swarmId = - _registerSwarm(fleetOwner, fleetId, providerId, new bytes(50), 8, SwarmRegistryUniversal.TagType.GENERIC); + _registerSwarm(fleetOwner, fleetId, providerId, new bytes(50), 8, SwarmRegistryUniversalUpgradeable.TagType.GENERIC); vm.prank(providerOwner); providerContract.burn(providerId); vm.prank(providerOwner); - vm.expectRevert(SwarmRegistryUniversal.SwarmOrphaned.selector); + vm.expectRevert(SwarmRegistryUniversalUpgradeable.SwarmOrphaned.selector); swarmRegistry.acceptSwarm(swarmId); } @@ -1108,7 +1143,7 @@ contract SwarmRegistryUniversalTest is Test { uint256 fleetId = _registerFleet(fleetOwner, "f1"); uint256 providerId = _registerProvider(providerOwner, "url1"); uint256 swarmId = - _registerSwarm(fleetOwner, fleetId, providerId, new bytes(50), 8, SwarmRegistryUniversal.TagType.GENERIC); + _registerSwarm(fleetOwner, fleetId, providerId, new bytes(50), 8, SwarmRegistryUniversalUpgradeable.TagType.GENERIC); // Burn registered fleet token → mints owned-only token vm.prank(fleetOwner); @@ -1121,7 +1156,7 @@ contract SwarmRegistryUniversalTest is Test { fleetContract.burn(ownedTokenId); vm.prank(providerOwner); - vm.expectRevert(SwarmRegistryUniversal.SwarmOrphaned.selector); + vm.expectRevert(SwarmRegistryUniversalUpgradeable.SwarmOrphaned.selector); swarmRegistry.rejectSwarm(swarmId); } @@ -1129,12 +1164,12 @@ contract SwarmRegistryUniversalTest is Test { uint256 fleetId = _registerFleet(fleetOwner, "f1"); uint256 providerId = _registerProvider(providerOwner, "url1"); uint256 swarmId = - _registerSwarm(fleetOwner, fleetId, providerId, new bytes(50), 8, SwarmRegistryUniversal.TagType.GENERIC); + _registerSwarm(fleetOwner, fleetId, providerId, new bytes(50), 8, SwarmRegistryUniversalUpgradeable.TagType.GENERIC); vm.prank(providerOwner); providerContract.burn(providerId); - vm.expectRevert(SwarmRegistryUniversal.SwarmOrphaned.selector); + vm.expectRevert(SwarmRegistryUniversalUpgradeable.SwarmOrphaned.selector); swarmRegistry.checkMembership(swarmId, keccak256("test")); } @@ -1142,7 +1177,7 @@ contract SwarmRegistryUniversalTest is Test { uint256 fleetId = _registerFleet(fleetOwner, "f1"); uint256 providerId = _registerProvider(providerOwner, "url1"); uint256 swarmId = - _registerSwarm(fleetOwner, fleetId, providerId, new bytes(50), 8, SwarmRegistryUniversal.TagType.GENERIC); + _registerSwarm(fleetOwner, fleetId, providerId, new bytes(50), 8, SwarmRegistryUniversalUpgradeable.TagType.GENERIC); // Burn registered fleet token → mints owned-only token vm.prank(fleetOwner); @@ -1155,7 +1190,7 @@ contract SwarmRegistryUniversalTest is Test { fleetContract.burn(ownedTokenId); vm.prank(providerOwner); - vm.expectRevert(SwarmRegistryUniversal.SwarmOrphaned.selector); + vm.expectRevert(SwarmRegistryUniversalUpgradeable.SwarmOrphaned.selector); swarmRegistry.acceptSwarm(swarmId); } @@ -1163,7 +1198,7 @@ contract SwarmRegistryUniversalTest is Test { uint256 fleetId = _registerFleet(fleetOwner, "f1"); uint256 providerId = _registerProvider(providerOwner, "url1"); uint256 swarmId = - _registerSwarm(fleetOwner, fleetId, providerId, new bytes(50), 8, SwarmRegistryUniversal.TagType.GENERIC); + _registerSwarm(fleetOwner, fleetId, providerId, new bytes(50), 8, SwarmRegistryUniversalUpgradeable.TagType.GENERIC); vm.prank(providerOwner); providerContract.burn(providerId); @@ -1173,7 +1208,7 @@ contract SwarmRegistryUniversalTest is Test { // After purge, swarm no longer exists vm.prank(providerOwner); - vm.expectRevert(SwarmRegistryUniversal.SwarmNotFound.selector); + vm.expectRevert(SwarmRegistryUniversalUpgradeable.SwarmNotFound.selector); swarmRegistry.acceptSwarm(swarmId); } } diff --git a/test/__helpers__/MockERC20.sol b/test/__helpers__/MockERC20.sol new file mode 100644 index 00000000..15f8604d --- /dev/null +++ b/test/__helpers__/MockERC20.sol @@ -0,0 +1,22 @@ +// SPDX-License-Identifier: BSD-3-Clause-Clear + +pragma solidity ^0.8.24; + +import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; + +/// @dev Minimal ERC-20 mock with public mint for testing. +contract MockERC20 is ERC20 { + uint8 private immutable _decimals; + + constructor(string memory name_, string memory symbol_, uint8 decimals_) ERC20(name_, symbol_) { + _decimals = decimals_; + } + + function decimals() public view override returns (uint8) { + return _decimals; + } + + function mint(address to, uint256 amount) external { + _mint(to, amount); + } +} diff --git a/test/upgrade-demo/README.md b/test/upgrade-demo/README.md new file mode 100644 index 00000000..1f68f053 --- /dev/null +++ b/test/upgrade-demo/README.md @@ -0,0 +1,189 @@ +# Upgrade Demo + +This folder contains a self-contained script demonstrating the UUPS upgrade process for the Swarm contracts on a local chain. + +## Contents + +| File | Description | +| -------------------------- | ---------------------------------------------------------------------------------------------- | +| `TestUpgradeOnAnvil.s.sol` | Forge script with inline V2 mocks that deploys V1, creates state, upgrades to V2, and verifies | + +The V2 contracts (`FleetIdentityUpgradeableV2`, `ServiceProviderUpgradeableV2`, `SwarmRegistryL1UpgradeableV2`) are defined **inline** in the script file for simplicity. They inherit from their V1 counterparts and add a `version()` function. + +## Prerequisites + +1. **anvil-zksync** installed (comes with foundry-zksync) +2. Contracts compiled with optimizer enabled (see `foundry.toml`) + +--- + +## Managing Anvil Instances + +### Check if Anvil is Running + +```bash +# Quick health check - returns chain ID if running +cast chain-id --rpc-url http://127.0.0.1:8545 + +# Check what process is using port 8545 +lsof -i :8545 +``` + +### Stop Existing Anvil + +```bash +# Kill any process on port 8545 +lsof -ti:8545 | xargs kill -9 + +# Or find and kill by process name +pkill -f anvil-zksync +pkill -f anvil +``` + +### Verify Node is Healthy + +```bash +# Get current block number +cast block-number --rpc-url http://127.0.0.1:8545 + +# Get gas price (confirms RPC responding) +cast gas-price --rpc-url http://127.0.0.1:8545 + +# Check test account balance (should be 10000 ETH) +cast balance 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266 --rpc-url http://127.0.0.1:8545 +``` + +--- + +## Running the Upgrade Test + +### Option A: L1 Mode (Ethereum Mainnet Simulation) + +Use this for testing `SwarmRegistryL1Upgradeable` which uses SSTORE2. + +```bash +# 1. Stop any existing anvil +lsof -ti:8545 | xargs kill -9 2>/dev/null || true + +# 2. Start anvil-zksync in L1 mode +~/.foundry/bin/anvil-zksync --host 127.0.0.1 --port 8545 + +# 3. Verify it's running +cast chain-id --rpc-url http://127.0.0.1:8545 + +# 4. Run the test (in another terminal) +forge script test/upgrade-demo/TestUpgradeOnAnvil.s.sol:TestUpgradeOnAnvil \ + --rpc-url http://127.0.0.1:8545 \ + --broadcast + +# 5. Stop anvil when done +lsof -ti:8545 | xargs kill -9 +``` + +### Option B: ZkSync Mode (ZkSync Era Simulation) + +Use this for testing with full ZkSync system contracts. + +```bash +# 1. Stop any existing anvil +lsof -ti:8545 | xargs kill -9 2>/dev/null || true + +# 2. Start anvil-zksync with ZkSync OS enabled +~/.foundry/bin/anvil-zksync --host 127.0.0.1 --port 8545 --zksync + +# 3. Verify it's running (chain ID will be 260 for ZkSync) +cast chain-id --rpc-url http://127.0.0.1:8545 + +# 4. Run the test with --zksync flag (in another terminal) +forge script test/upgrade-demo/TestUpgradeOnAnvil.s.sol:TestUpgradeOnAnvil \ + --rpc-url http://127.0.0.1:8545 \ + --broadcast \ + --zksync + +# 5. Stop anvil when done +lsof -ti:8545 | xargs kill -9 +``` + +### Option C: Full L1 + L2 Mode (Bridge Testing) + +Use this for testing L1↔L2 interactions (not needed for basic upgrade demo). + +```bash +# Start with both L1 and ZkSync +~/.foundry/bin/anvil-zksync --host 127.0.0.1 --port 8545 --l1 --zksync +``` + +--- + +## Quick Reference + +| Task | Command | +| ----------------- | ------------------------------------------------------------------- | +| Check if running | `cast chain-id --rpc-url http://127.0.0.1:8545` | +| Check port usage | `lsof -i :8545` | +| Kill on port 8545 | `lsof -ti:8545 \| xargs kill -9` | +| Kill all anvil | `pkill -f anvil` | +| Start L1 mode | `~/.foundry/bin/anvil-zksync --host 127.0.0.1 --port 8545` | +| Start ZkSync mode | `~/.foundry/bin/anvil-zksync --host 127.0.0.1 --port 8545 --zksync` | +| Health check | `cast block-number --rpc-url http://127.0.0.1:8545` | + +--- + +## Expected Output + +The script will: + +1. **Deploy V1 contracts** - ServiceProvider, FleetIdentity, SwarmRegistryL1 (all via ERC1967 proxies) +2. **Create state** - Register a provider URL and a fleet with bond +3. **Upgrade to V2** - Deploy V2 implementations and call `upgradeToAndCall()` +4. **Verify success** - Check `version()` returns "2.0.0" and all state is preserved + +``` +=== PHASE 1: Deploy V1 Contracts === + Bond Token: 0x... + ServiceProvider Proxy: 0x... + FleetIdentity Proxy: 0x... + SwarmRegistry Proxy: 0x... + +=== PHASE 2: Create State === + Registered Provider: Token ID: ... + Registered Fleet: Token ID: ... + +=== PHASE 3: Upgrade to V2 === + Upgraded! (x3) + +=== PHASE 4: Verify Upgrade Success === + ServiceProvider version: 2.0.0 + FleetIdentity version: 2.0.0 + SwarmRegistry version: 2.0.0 + Provider URL still valid: true + Fleet bond preserved: true + + UPGRADE TEST COMPLETED SUCCESSFULLY +``` + +--- + +## Contract Size Consideration + +`FleetIdentityUpgradeable` is a large contract. Ensure optimizer is enabled in `foundry.toml`: + +```toml +via_ir = true +optimizer = true +optimizer_runs = 200 +``` + +Without the optimizer, the contract exceeds the EIP-170 size limit (24,576 bytes) and cannot deploy to L1 Ethereum. + +--- + +## Troubleshooting + +| Issue | Cause | Solution | +| ------------------------------ | --------------------------- | ------------------------------------- | +| `failed to connect to network` | Anvil not running | Start anvil first | +| `address already in use` | Port 8545 occupied | `lsof -ti:8545 \| xargs kill -9` | +| `contract size limit exceeded` | Optimizer disabled | Enable in `foundry.toml` | +| `EXTCODECOPY not supported` | Using L1 contract on ZkSync | Use L1 mode or SwarmRegistryUniversal | +| Script hangs | Previous anvil state | Restart anvil fresh | diff --git a/test/upgrade-demo/TestUpgradeOnAnvil.s.sol b/test/upgrade-demo/TestUpgradeOnAnvil.s.sol new file mode 100644 index 00000000..be36b0bf --- /dev/null +++ b/test/upgrade-demo/TestUpgradeOnAnvil.s.sol @@ -0,0 +1,248 @@ +// SPDX-License-Identifier: BSD-3-Clause-Clear + +pragma solidity ^0.8.24; + +import {Script, console} from "forge-std/Script.sol"; +import {ERC1967Proxy} from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol"; +import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; + +// V1 contracts +import {ServiceProviderUpgradeable} from "../../src/swarms/ServiceProviderUpgradeable.sol"; +import {FleetIdentityUpgradeable} from "../../src/swarms/FleetIdentityUpgradeable.sol"; +import {SwarmRegistryL1Upgradeable} from "../../src/swarms/SwarmRegistryL1Upgradeable.sol"; + +// ═══════════════════════════════════════════════════════════════════════════════ +// Mock V2 Contracts (inline for testing purposes only) +// ═══════════════════════════════════════════════════════════════════════════════ + +/// @dev Mock V2 that adds version() - inherits from V1 to preserve storage layout +contract FleetIdentityUpgradeableV2 is FleetIdentityUpgradeable { + function version() external pure returns (string memory) { + return "2.0.0"; + } +} + +/// @dev Mock V2 that adds version() - inherits from V1 to preserve storage layout +contract ServiceProviderUpgradeableV2 is ServiceProviderUpgradeable { + function version() external pure returns (string memory) { + return "2.0.0"; + } +} + +/// @dev Mock V2 that adds version() - inherits from V1 to preserve storage layout +contract SwarmRegistryL1UpgradeableV2 is SwarmRegistryL1Upgradeable { + function version() external pure returns (string memory) { + return "2.0.0"; + } +} + +/// @dev Simple ERC20 for testing bond deposits +contract MockBondToken is ERC20 { + constructor() ERC20("Mock Bond", "MBOND") { + _mint(msg.sender, 1_000_000 ether); + } + + function mint(address to, uint256 amount) external { + _mint(to, amount); + } +} + +// ═══════════════════════════════════════════════════════════════════════════════ +// Main Test Script +// ═══════════════════════════════════════════════════════════════════════════════ + +/** + * @title TestUpgradeOnAnvil + * @notice End-to-end script to deploy, use, upgrade, and verify swarm contracts on anvil. + * + * Usage: + * 1. Start anvil-zksync in a separate terminal: + * ~/.foundry/bin/anvil-zksync --host 127.0.0.1 --port 8545 + * + * 2. Run this script: + * forge script test/upgrade-demo/TestUpgradeOnAnvil.s.sol:TestUpgradeOnAnvil \ + * --rpc-url http://127.0.0.1:8545 \ + * --broadcast + */ +contract TestUpgradeOnAnvil is Script { + // Use anvil's first default account + uint256 constant ANVIL_PRIVATE_KEY = 0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80; + + // Proxy addresses + address public serviceProviderProxy; + address public fleetIdentityProxy; + address public swarmRegistryProxy; + + // Test state + uint256 public providerTokenId; + uint256 public fleetTokenId; + + function run() external { + address deployer = vm.addr(ANVIL_PRIVATE_KEY); + console.log("Deployer:", deployer); + console.log("Balance:", deployer.balance); + + vm.startBroadcast(ANVIL_PRIVATE_KEY); + + // ═══════════════════════════════════════════ + // PHASE 1: Deploy Mock Token & V1 Contracts + // ═══════════════════════════════════════════ + console.log("\n=== PHASE 1: Deploy V1 Contracts ===\n"); + + // Deploy mock bond token + MockBondToken bondToken = new MockBondToken(); + console.log("Bond Token:", address(bondToken)); + + uint256 baseBond = 100 ether; + + // Deploy ServiceProvider V1 + console.log("\nDeploying ServiceProviderUpgradeable V1..."); + ServiceProviderUpgradeable spImpl = new ServiceProviderUpgradeable(); + ERC1967Proxy spProxy = new ERC1967Proxy( + address(spImpl), + abi.encodeCall(ServiceProviderUpgradeable.initialize, (deployer)) + ); + serviceProviderProxy = address(spProxy); + console.log(" ServiceProvider Proxy:", serviceProviderProxy); + console.log(" ServiceProvider Impl V1:", address(spImpl)); + + // Deploy FleetIdentity V1 + console.log("\nDeploying FleetIdentityUpgradeable V1..."); + FleetIdentityUpgradeable fiImpl = new FleetIdentityUpgradeable(); + ERC1967Proxy fiProxy = new ERC1967Proxy( + address(fiImpl), + abi.encodeCall(FleetIdentityUpgradeable.initialize, (address(bondToken), baseBond, deployer)) + ); + fleetIdentityProxy = address(fiProxy); + console.log(" FleetIdentity Proxy:", fleetIdentityProxy); + console.log(" FleetIdentity Impl V1:", address(fiImpl)); + + // Deploy SwarmRegistryL1 V1 + console.log("\nDeploying SwarmRegistryL1Upgradeable V1..."); + SwarmRegistryL1Upgradeable srImpl = new SwarmRegistryL1Upgradeable(); + ERC1967Proxy srProxy = new ERC1967Proxy( + address(srImpl), + abi.encodeCall(SwarmRegistryL1Upgradeable.initialize, (fleetIdentityProxy, serviceProviderProxy, deployer)) + ); + swarmRegistryProxy = address(srProxy); + console.log(" SwarmRegistry Proxy:", swarmRegistryProxy); + console.log(" SwarmRegistry Impl V1:", address(srImpl)); + + // ═══════════════════════════════════════════ + // PHASE 2: Create State (register provider & fleet) + // ═══════════════════════════════════════════ + console.log("\n=== PHASE 2: Create State ===\n"); + + // Register a provider + ServiceProviderUpgradeable sp = ServiceProviderUpgradeable(serviceProviderProxy); + string memory providerUrl = "https://api.example.com"; + providerTokenId = sp.registerProvider(providerUrl); + console.log("Registered Provider:"); + console.log(" Token ID:", providerTokenId); + console.log(" URL:", sp.providerUrls(providerTokenId)); + console.log(" Owner:", sp.ownerOf(providerTokenId)); + + // Approve bond token for fleet + FleetIdentityUpgradeable fi = FleetIdentityUpgradeable(fleetIdentityProxy); + bondToken.approve(fleetIdentityProxy, type(uint256).max); + console.log("\nBond token approved for FleetIdentity"); + + // Register a fleet + bytes16 fleetUuid = bytes16(keccak256("test-fleet-uuid")); + uint16 countryCode = 840; // US + uint16 adminCode = 6; // CA + fleetTokenId = fi.registerFleetLocal(fleetUuid, countryCode, adminCode, 0); + console.log("\nRegistered Fleet:"); + console.log(" Token ID:", fleetTokenId); + console.log(" Bond deposited:", fi.bonds(fleetTokenId)); + + // Verify state before upgrade + console.log("\n--- Pre-Upgrade State Verification ---"); + console.log("ServiceProvider owner:", sp.owner()); + console.log("FleetIdentity BASE_BOND:", fi.BASE_BOND()); + console.log("Provider token exists:", sp.ownerOf(providerTokenId) == deployer); + console.log("Fleet token exists:", fi.ownerOf(fleetTokenId) == deployer); + + // ═══════════════════════════════════════════ + // PHASE 3: Upgrade to V2 + // ═══════════════════════════════════════════ + console.log("\n=== PHASE 3: Upgrade to V2 ===\n"); + + // Deploy V2 implementations (defined inline above) + console.log("Deploying V2 implementations..."); + ServiceProviderUpgradeableV2 spImplV2 = new ServiceProviderUpgradeableV2(); + FleetIdentityUpgradeableV2 fiImplV2 = new FleetIdentityUpgradeableV2(); + SwarmRegistryL1UpgradeableV2 srImplV2 = new SwarmRegistryL1UpgradeableV2(); + + console.log(" ServiceProvider Impl V2:", address(spImplV2)); + console.log(" FleetIdentity Impl V2:", address(fiImplV2)); + console.log(" SwarmRegistry Impl V2:", address(srImplV2)); + + // Upgrade ServiceProvider + console.log("\nUpgrading ServiceProvider to V2..."); + ServiceProviderUpgradeable(serviceProviderProxy).upgradeToAndCall(address(spImplV2), ""); + console.log(" Upgraded!"); + + // Upgrade FleetIdentity + console.log("Upgrading FleetIdentity to V2..."); + FleetIdentityUpgradeable(fleetIdentityProxy).upgradeToAndCall(address(fiImplV2), ""); + console.log(" Upgraded!"); + + // Upgrade SwarmRegistry + console.log("Upgrading SwarmRegistry to V2..."); + SwarmRegistryL1Upgradeable(swarmRegistryProxy).upgradeToAndCall(address(srImplV2), ""); + console.log(" Upgraded!"); + + // ═══════════════════════════════════════════ + // PHASE 4: Verify Upgrade Success + // ═══════════════════════════════════════════ + console.log("\n=== PHASE 4: Verify Upgrade Success ===\n"); + + // Cast proxies to V2 interfaces + ServiceProviderUpgradeableV2 spV2 = ServiceProviderUpgradeableV2(serviceProviderProxy); + FleetIdentityUpgradeableV2 fiV2 = FleetIdentityUpgradeableV2(fleetIdentityProxy); + SwarmRegistryL1UpgradeableV2 srV2 = SwarmRegistryL1UpgradeableV2(swarmRegistryProxy); + + // Check versions + console.log("--- Version Check ---"); + console.log("ServiceProvider version:", spV2.version()); + console.log("FleetIdentity version:", fiV2.version()); + console.log("SwarmRegistry version:", srV2.version()); + + // Verify state preserved + console.log("\n--- State Preservation Check ---"); + console.log("Provider URL still valid:", keccak256(bytes(spV2.providerUrls(providerTokenId))) == keccak256(bytes(providerUrl))); + console.log("Provider owner unchanged:", spV2.ownerOf(providerTokenId) == deployer); + console.log("Fleet bond preserved:", fiV2.bonds(fleetTokenId) == baseBond); + console.log("Fleet owner unchanged:", fiV2.ownerOf(fleetTokenId) == deployer); + console.log("Contract owner unchanged:", spV2.owner() == deployer); + + // Test that contracts still work after upgrade + console.log("\n--- Post-Upgrade Functionality Test ---"); + + // Register another provider + uint256 newProviderId = spV2.registerProvider("https://api2.example.com"); + console.log("New provider registered after upgrade, ID:", newProviderId); + + // Register another fleet + bytes16 fleetUuid2 = bytes16(keccak256("test-fleet-uuid-2")); + uint256 newFleetId = fiV2.registerFleetLocal(fleetUuid2, countryCode, adminCode, 0); + console.log("New fleet registered after upgrade, ID:", newFleetId); + + vm.stopBroadcast(); + + // ═══════════════════════════════════════════ + // Final Summary + // ═══════════════════════════════════════════ + console.log("\n========================================"); + console.log(" UPGRADE TEST COMPLETED SUCCESSFULLY"); + console.log("========================================"); + console.log("- All V1 contracts deployed"); + console.log("- State created (provider + fleet)"); + console.log("- Upgraded to V2 implementations"); + console.log("- Version functions return '2.0.0'"); + console.log("- All state preserved after upgrade"); + console.log("- Post-upgrade operations work"); + console.log("========================================\n"); + } +} diff --git a/test/upgradeable/UpgradeableContracts.t.sol b/test/upgradeable/UpgradeableContracts.t.sol new file mode 100644 index 00000000..b6ea49c5 --- /dev/null +++ b/test/upgradeable/UpgradeableContracts.t.sol @@ -0,0 +1,440 @@ +// SPDX-License-Identifier: BSD-3-Clause-Clear + +pragma solidity ^0.8.24; + +import {Test, console} from "forge-std/Test.sol"; +import {ERC1967Proxy} from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol"; +import {Initializable} from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol"; +import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; + +import {ServiceProviderUpgradeable} from "../../src/swarms/ServiceProviderUpgradeable.sol"; +import {FleetIdentityUpgradeable} from "../../src/swarms/FleetIdentityUpgradeable.sol"; +import {SwarmRegistryUniversalUpgradeable} from "../../src/swarms/SwarmRegistryUniversalUpgradeable.sol"; + +import {MockERC20} from "../__helpers__/MockERC20.sol"; + +/** + * @title ServiceProviderUpgradeableV2Mock + * @notice Mock V2 implementation for testing upgrades + */ +contract ServiceProviderUpgradeableV2Mock is ServiceProviderUpgradeable { + // New V2 storage + mapping(uint256 => uint256) public providerScores; + uint256 public v2InitializedAt; + + // Reduce gap from 49 to 47 (added 2 slots) + uint256[47] private __gap_v2; + + function initializeV2() external reinitializer(2) { + v2InitializedAt = block.timestamp; + } + + function setProviderScore(uint256 tokenId, uint256 score) external { + if (ownerOf(tokenId) != msg.sender) revert("Not owner"); + providerScores[tokenId] = score; + } + + function version() external pure returns (string memory) { + return "V2"; + } +} + +/** + * @title FleetIdentityUpgradeableV2Mock + * @notice Mock V2 implementation for testing upgrades + */ +contract FleetIdentityUpgradeableV2Mock is FleetIdentityUpgradeable { + // New V2 storage + mapping(uint256 => string) public fleetMetadata; + uint256 public v2InitializedAt; + + // Reduce gap from 40 to 38 (added 2 slots) + uint256[38] private __gap_v2; + + function initializeV2() external reinitializer(2) { + v2InitializedAt = block.timestamp; + } + + function setFleetMetadata(uint256 tokenId, string calldata metadata) external { + if (ownerOf(tokenId) != msg.sender) revert("Not owner"); + fleetMetadata[tokenId] = metadata; + } + + function version() external pure returns (string memory) { + return "V2"; + } +} + +/** + * @title SwarmRegistryUniversalUpgradeableV2Mock + * @notice Mock V2 implementation for testing upgrades + */ +contract SwarmRegistryUniversalUpgradeableV2Mock is SwarmRegistryUniversalUpgradeable { + // New V2 storage + mapping(bytes32 => bool) public swarmPaused; + uint256 public v2InitializedAt; + + // Reduce gap from 44 to 42 (added 2 slots) + uint256[42] private __gap_v2; + + function initializeV2() external reinitializer(2) { + v2InitializedAt = block.timestamp; + } + + function pauseSwarm(bytes32 swarmId) external { + swarmPaused[swarmId] = true; + } + + function version() external pure returns (string memory) { + return "V2"; + } +} + +/** + * @title UpgradeableContractsTest + * @notice Tests for UUPS upgradeable swarm contracts + */ +contract UpgradeableContractsTest is Test { + // Contracts + ServiceProviderUpgradeable public serviceProviderImpl; + ServiceProviderUpgradeable public serviceProvider; + address public serviceProviderProxy; + + FleetIdentityUpgradeable public fleetIdentityImpl; + FleetIdentityUpgradeable public fleetIdentity; + address public fleetIdentityProxy; + + SwarmRegistryUniversalUpgradeable public swarmRegistryImpl; + SwarmRegistryUniversalUpgradeable public swarmRegistry; + address public swarmRegistryProxy; + + // Mock token + MockERC20 public bondToken; + + // Actors + address public owner = address(0x1111); + address public alice = address(0xA11CE); + address public bob = address(0xB0B); + address public attacker = address(0xBAD); + + // Constants + uint256 constant BASE_BOND = 1000e18; + + function setUp() public { + // Deploy mock token + bondToken = new MockERC20("Mock Token", "MOCK", 18); + bondToken.mint(alice, 1_000_000e18); + bondToken.mint(bob, 1_000_000e18); + + // Deploy ServiceProvider + serviceProviderImpl = new ServiceProviderUpgradeable(); + serviceProviderProxy = address( + new ERC1967Proxy( + address(serviceProviderImpl), + abi.encodeCall(ServiceProviderUpgradeable.initialize, (owner)) + ) + ); + serviceProvider = ServiceProviderUpgradeable(serviceProviderProxy); + + // Deploy FleetIdentity + fleetIdentityImpl = new FleetIdentityUpgradeable(); + fleetIdentityProxy = address( + new ERC1967Proxy( + address(fleetIdentityImpl), + abi.encodeCall(FleetIdentityUpgradeable.initialize, (address(bondToken), BASE_BOND, owner)) + ) + ); + fleetIdentity = FleetIdentityUpgradeable(fleetIdentityProxy); + + // Deploy SwarmRegistry + swarmRegistryImpl = new SwarmRegistryUniversalUpgradeable(); + swarmRegistryProxy = address( + new ERC1967Proxy( + address(swarmRegistryImpl), + abi.encodeCall( + SwarmRegistryUniversalUpgradeable.initialize, + (fleetIdentityProxy, serviceProviderProxy, owner) + ) + ) + ); + swarmRegistry = SwarmRegistryUniversalUpgradeable(swarmRegistryProxy); + } + + // ========================================================================= + // ServiceProvider Initialization Tests + // ========================================================================= + + function test_ServiceProvider_InitializesCorrectly() public view { + assertEq(serviceProvider.owner(), owner); + assertEq(serviceProvider.name(), "Swarm Service Provider"); + assertEq(serviceProvider.symbol(), "SSV"); + } + + function test_ServiceProvider_CannotReinitialize() public { + vm.expectRevert(Initializable.InvalidInitialization.selector); + serviceProvider.initialize(attacker); + } + + function test_ServiceProvider_ImplementationCannotBeInitialized() public { + vm.expectRevert(Initializable.InvalidInitialization.selector); + serviceProviderImpl.initialize(attacker); + } + + // ========================================================================= + // ServiceProvider Upgrade Tests + // ========================================================================= + + function test_ServiceProvider_OwnerCanUpgrade() public { + // Create some state before upgrade + vm.startPrank(alice); + uint256 tokenId = serviceProvider.registerProvider("https://alice.example.com/api"); + vm.stopPrank(); + + // Deploy V2 and upgrade + ServiceProviderUpgradeableV2Mock v2Impl = new ServiceProviderUpgradeableV2Mock(); + + vm.prank(owner); + serviceProvider.upgradeToAndCall( + address(v2Impl), abi.encodeCall(ServiceProviderUpgradeableV2Mock.initializeV2, ()) + ); + + // Verify upgrade + ServiceProviderUpgradeableV2Mock v2 = ServiceProviderUpgradeableV2Mock(serviceProviderProxy); + assertEq(v2.version(), "V2"); + assertGt(v2.v2InitializedAt(), 0); + + // Verify old state preserved + assertEq(v2.ownerOf(tokenId), alice); + assertEq(v2.providerUrls(tokenId), "https://alice.example.com/api"); + + // New V2 functionality works + vm.prank(alice); + v2.setProviderScore(tokenId, 100); + assertEq(v2.providerScores(tokenId), 100); + } + + function test_ServiceProvider_NonOwnerCannotUpgrade() public { + ServiceProviderUpgradeableV2Mock v2Impl = new ServiceProviderUpgradeableV2Mock(); + + vm.prank(attacker); + vm.expectRevert(); + serviceProvider.upgradeToAndCall(address(v2Impl), ""); + } + + function test_ServiceProvider_StoragePersistsAfterUpgrade() public { + // Create multiple tokens + vm.startPrank(alice); + uint256 aliceToken = serviceProvider.registerProvider("https://alice.example.com"); + vm.stopPrank(); + + vm.startPrank(bob); + uint256 bobToken = serviceProvider.registerProvider("https://bob.example.com"); + vm.stopPrank(); + + // Upgrade + ServiceProviderUpgradeableV2Mock v2Impl = new ServiceProviderUpgradeableV2Mock(); + vm.prank(owner); + serviceProvider.upgradeToAndCall(address(v2Impl), ""); + + ServiceProviderUpgradeableV2Mock v2 = ServiceProviderUpgradeableV2Mock(serviceProviderProxy); + + // Verify all state + assertEq(v2.ownerOf(aliceToken), alice); + assertEq(v2.ownerOf(bobToken), bob); + assertEq(v2.providerUrls(aliceToken), "https://alice.example.com"); + assertEq(v2.providerUrls(bobToken), "https://bob.example.com"); + assertEq(v2.owner(), owner); + } + + // ========================================================================= + // FleetIdentity Initialization Tests + // ========================================================================= + + function test_FleetIdentity_InitializesCorrectly() public view { + assertEq(fleetIdentity.owner(), owner); + assertEq(address(fleetIdentity.BOND_TOKEN()), address(bondToken)); + assertEq(fleetIdentity.BASE_BOND(), BASE_BOND); + assertEq(fleetIdentity.name(), "Swarm Fleet Identity"); + assertEq(fleetIdentity.symbol(), "SFID"); + } + + function test_FleetIdentity_CannotReinitialize() public { + vm.expectRevert(Initializable.InvalidInitialization.selector); + fleetIdentity.initialize(address(bondToken), BASE_BOND, attacker); + } + + function test_FleetIdentity_ImplementationCannotBeInitialized() public { + vm.expectRevert(Initializable.InvalidInitialization.selector); + fleetIdentityImpl.initialize(address(bondToken), BASE_BOND, attacker); + } + + // ========================================================================= + // FleetIdentity Upgrade Tests + // ========================================================================= + + function test_FleetIdentity_OwnerCanUpgrade() public { + // Approve and claim a UUID (simplest operation) + vm.startPrank(alice); + bondToken.approve(address(fleetIdentity), BASE_BOND); + bytes16 uuid1 = bytes16(keccak256("test-fleet-1")); + uint256 tokenId = fleetIdentity.claimUuid(uuid1, address(0)); + vm.stopPrank(); + + // Deploy V2 and upgrade + FleetIdentityUpgradeableV2Mock v2Impl = new FleetIdentityUpgradeableV2Mock(); + + vm.prank(owner); + fleetIdentity.upgradeToAndCall(address(v2Impl), abi.encodeCall(FleetIdentityUpgradeableV2Mock.initializeV2, ())); + + // Verify upgrade + FleetIdentityUpgradeableV2Mock v2 = FleetIdentityUpgradeableV2Mock(fleetIdentityProxy); + assertEq(v2.version(), "V2"); + assertGt(v2.v2InitializedAt(), 0); + + // Verify old state preserved + assertEq(v2.ownerOf(tokenId), alice); + + // Both old and new functionality work + vm.prank(alice); + v2.setFleetMetadata(tokenId, "metadata://test"); + assertEq(v2.fleetMetadata(tokenId), "metadata://test"); + } + + function test_FleetIdentity_NonOwnerCannotUpgrade() public { + FleetIdentityUpgradeableV2Mock v2Impl = new FleetIdentityUpgradeableV2Mock(); + + vm.prank(attacker); + vm.expectRevert(); + fleetIdentity.upgradeToAndCall(address(v2Impl), ""); + } + + function test_FleetIdentity_BondStatePersistsAfterUpgrade() public { + // Claim UUIDs (each costs BASE_BOND) + vm.startPrank(alice); + bondToken.approve(address(fleetIdentity), BASE_BOND * 2); + bytes16 uuid1 = bytes16(keccak256("fleet-1")); + bytes16 uuid2 = bytes16(keccak256("fleet-2")); + uint256 token1 = fleetIdentity.claimUuid(uuid1, address(0)); + uint256 token2 = fleetIdentity.claimUuid(uuid2, address(0)); + vm.stopPrank(); + + uint256 bond1 = fleetIdentity.bonds(token1); + uint256 bond2 = fleetIdentity.bonds(token2); + + // Upgrade + FleetIdentityUpgradeableV2Mock v2Impl = new FleetIdentityUpgradeableV2Mock(); + vm.prank(owner); + fleetIdentity.upgradeToAndCall(address(v2Impl), ""); + + FleetIdentityUpgradeableV2Mock v2 = FleetIdentityUpgradeableV2Mock(fleetIdentityProxy); + + // Verify bonds match + assertEq(v2.bonds(token1), bond1); + assertEq(v2.bonds(token2), bond2); + assertEq(address(v2.BOND_TOKEN()), address(bondToken)); + assertEq(v2.BASE_BOND(), BASE_BOND); + } + + // ========================================================================= + // SwarmRegistry Initialization Tests + // ========================================================================= + + function test_SwarmRegistry_InitializesCorrectly() public view { + assertEq(swarmRegistry.owner(), owner); + assertEq(address(swarmRegistry.FLEET_CONTRACT()), fleetIdentityProxy); + assertEq(address(swarmRegistry.PROVIDER_CONTRACT()), serviceProviderProxy); + } + + function test_SwarmRegistry_CannotReinitialize() public { + vm.expectRevert(Initializable.InvalidInitialization.selector); + swarmRegistry.initialize(address(fleetIdentity), address(serviceProvider), attacker); + } + + function test_SwarmRegistry_ImplementationCannotBeInitialized() public { + vm.expectRevert(Initializable.InvalidInitialization.selector); + swarmRegistryImpl.initialize(address(fleetIdentity), address(serviceProvider), attacker); + } + + // ========================================================================= + // SwarmRegistry Upgrade Tests + // ========================================================================= + + function test_SwarmRegistry_OwnerCanUpgrade() public { + // Claim UUID first to get a fleet ID + vm.startPrank(alice); + bondToken.approve(address(fleetIdentity), BASE_BOND); + bytes16 uuid = bytes16(keccak256("swarm-test-fleet")); + uint256 fleetId = fleetIdentity.claimUuid(uuid, address(0)); + vm.stopPrank(); + + vm.startPrank(bob); + uint256 providerId = serviceProvider.registerProvider("https://bob.example.com"); + vm.stopPrank(); + + // Register swarm with correct parameters + bytes memory filterData = hex"0102030405"; + uint8 fingerprintSize = 16; + SwarmRegistryUniversalUpgradeable.TagType tagType = SwarmRegistryUniversalUpgradeable.TagType.IBEACON_PAYLOAD_ONLY; + + vm.prank(alice); + uint256 swarmId = swarmRegistry.registerSwarm(uuid, providerId, filterData, fingerprintSize, tagType); + + // Deploy V2 and upgrade + SwarmRegistryUniversalUpgradeableV2Mock v2Impl = new SwarmRegistryUniversalUpgradeableV2Mock(); + + vm.prank(owner); + swarmRegistry.upgradeToAndCall( + address(v2Impl), abi.encodeCall(SwarmRegistryUniversalUpgradeableV2Mock.initializeV2, ()) + ); + + // Verify upgrade + SwarmRegistryUniversalUpgradeableV2Mock v2 = SwarmRegistryUniversalUpgradeableV2Mock(swarmRegistryProxy); + assertEq(v2.version(), "V2"); + assertGt(v2.v2InitializedAt(), 0); + + // Verify old state preserved - swarm still exists via public mapping + (bytes16 storedUuid, uint256 storedProviderId,,,,) = v2.swarms(swarmId); + assertEq(storedUuid, uuid); + assertEq(storedProviderId, providerId); + + // New V2 functionality works + v2.pauseSwarm(bytes32(swarmId)); + assertTrue(v2.swarmPaused(bytes32(swarmId))); + } + + function test_SwarmRegistry_NonOwnerCannotUpgrade() public { + SwarmRegistryUniversalUpgradeableV2Mock v2Impl = new SwarmRegistryUniversalUpgradeableV2Mock(); + + vm.prank(attacker); + vm.expectRevert(); + swarmRegistry.upgradeToAndCall(address(v2Impl), ""); + } + + // ========================================================================= + // Fuzz Tests + // ========================================================================= + + function testFuzz_ServiceProvider_MultipleUpgradesPreserveState(uint8 registrations) public { + vm.assume(registrations > 0 && registrations < 10); + + // Register multiple providers + uint256[] memory tokenIds = new uint256[](registrations); + for (uint256 i = 0; i < registrations; i++) { + address user = address(uint160(0x1000 + i)); + vm.prank(user); + tokenIds[i] = serviceProvider.registerProvider(string(abi.encodePacked("https://", i, ".example.com"))); + } + + // Upgrade to V2 + ServiceProviderUpgradeableV2Mock v2Impl = new ServiceProviderUpgradeableV2Mock(); + vm.prank(owner); + serviceProvider.upgradeToAndCall(address(v2Impl), ""); + + // Verify all tokens still exist with correct owners + ServiceProviderUpgradeableV2Mock v2 = ServiceProviderUpgradeableV2Mock(serviceProviderProxy); + for (uint256 i = 0; i < registrations; i++) { + address expectedOwner = address(uint160(0x1000 + i)); + assertEq(v2.ownerOf(tokenIds[i]), expectedOwner); + } + } +} From d92fef10d00d684d0fc530cf2c61c96109cd7d28 Mon Sep 17 00:00:00 2001 From: Alex Sedighi Date: Fri, 6 Mar 2026 16:16:28 +1300 Subject: [PATCH 08/15] feat(swarms): simplify initialize with 0-defaults for bond params - Reorder params: (owner, bondToken, baseBond, countryMultiplier) - bondToken required; baseBond=0 uses DEFAULT_BASE_BOND (1e18) - countryMultiplier=0 uses DEFAULT_COUNTRY_BOND_MULTIPLIER (16) - Simplify countryBondMultiplier() getter (no more fallback logic) - Update all test files with new signature - Coverage: 96.77% line, 95.58% branch --- src/swarms/FleetIdentityUpgradeable.sol | 152 +++++++-- src/swarms/doc/assistant-guide.md | 42 ++- src/swarms/doc/swarm-operations.md | 2 +- test/FleetIdentity.t.sol | 322 ++++++++++++++++++-- test/FleetIdentityFairness.t.sol | 6 +- test/SwarmRegistryL1.t.sol | 2 +- test/SwarmRegistryUniversal.t.sol | 2 +- test/upgrade-demo/TestUpgradeOnAnvil.s.sol | 2 +- test/upgradeable/UpgradeableContracts.t.sol | 6 +- 9 files changed, 473 insertions(+), 63 deletions(-) diff --git a/src/swarms/FleetIdentityUpgradeable.sol b/src/swarms/FleetIdentityUpgradeable.sol index c9a2a640..4c18089b 100644 --- a/src/swarms/FleetIdentityUpgradeable.sol +++ b/src/swarms/FleetIdentityUpgradeable.sol @@ -78,6 +78,9 @@ contract FleetIdentityUpgradeable is error NotUuidOwner(); error NotOperator(); error NotOwnerOrOperator(); + error InvalidBaseBond(); + error InvalidMultiplier(); + error InvalidBondToken(); // ────────────────────────────────────────────── // Enums @@ -98,8 +101,11 @@ contract FleetIdentityUpgradeable is /// @notice Unified tier capacity for all levels. uint256 public constant TIER_CAPACITY = 10; - /// @notice Bond multiplier for country-level registration (16× local). - uint256 public constant COUNTRY_BOND_MULTIPLIER = 16; + /// @notice Default country bond multiplier when not explicitly set (16× local). + uint256 public constant DEFAULT_COUNTRY_BOND_MULTIPLIER = 16; + + /// @notice Default base bond for tier 0. + uint256 public constant DEFAULT_BASE_BOND = 1e18; /// @notice Hard cap on tier count per region. uint256 public constant MAX_TIERS = 24; @@ -172,6 +178,23 @@ contract FleetIdentityUpgradeable is /// @notice UUID -> total tier bonds across all registered regions. mapping(bytes16 => uint256) public uuidTotalTierBonds; + // ────────────────────────────────────────────── + // Bond Snapshots (for safe parameter reconfiguration) + // ────────────────────────────────────────────── + + /// @notice Configurable country bond multiplier. 0 = use DEFAULT_COUNTRY_BOND_MULTIPLIER (16). + /// @dev Can be updated by owner via setCountryBondMultiplier(). + uint256 private _countryBondMultiplier; + + /// @notice tokenId -> tier-0 equivalent bond paid at registration. + /// @dev Stores baseBond (for local) or baseBond*multiplier (for country). + /// Actual tier K bond = tokenTier0Bond[tokenId] << K. + mapping(uint256 => uint256) public tokenTier0Bond; + + /// @notice UUID -> ownership bond paid at claim/first-registration. + /// @dev Refunded when owned-only token is burned. + mapping(bytes16 => uint256) public uuidOwnershipBondPaid; + // ────────────────────────────────────────────── // On-chain region indexes // ────────────────────────────────────────────── @@ -217,6 +240,8 @@ contract FleetIdentityUpgradeable is address indexed owner, uint256 indexed tokenId, uint32 indexed regionKey, uint256 tierIndex, uint256 bondRefund ); event UuidClaimed(address indexed owner, bytes16 indexed uuid, address indexed operator); + event BaseBondUpdated(uint256 indexed oldBaseBond, uint256 indexed newBaseBond); + event CountryMultiplierUpdated(uint256 indexed oldMultiplier, uint256 indexed newMultiplier); // ────────────────────────────────────────────── // Constructor (disables initializers on implementation) @@ -232,17 +257,69 @@ contract FleetIdentityUpgradeable is // ────────────────────────────────────────────── /// @notice Initializes the contract. Must be called once via proxy. - /// @param bondToken_ Address of the ERC-20 token used for bonds. - /// @param baseBond_ Base bond for tier 0 in any region. /// @param owner_ The address that will own this contract and can authorize upgrades. - function initialize(address bondToken_, uint256 baseBond_, address owner_) external initializer { + /// @param bondToken_ Address of the ERC-20 token used for bonds (required). + /// @param baseBond_ Base bond for tier 0 (0 = DEFAULT_BASE_BOND = 1M NODL). + /// @param countryMultiplier_ Country bond multiplier (0 = DEFAULT_COUNTRY_BOND_MULTIPLIER = 16). + function initialize(address owner_, address bondToken_, uint256 baseBond_, uint256 countryMultiplier_) + external + initializer + { + if (bondToken_ == address(0)) revert InvalidBondToken(); + __ERC721_init("Swarm Fleet Identity", "SFID"); __ERC721Enumerable_init(); __Ownable_init(owner_); __Ownable2Step_init(); _bondToken = IERC20(bondToken_); - _baseBond = baseBond_; + _baseBond = baseBond_ == 0 ? DEFAULT_BASE_BOND : baseBond_; + _countryBondMultiplier = countryMultiplier_ == 0 ? DEFAULT_COUNTRY_BOND_MULTIPLIER : countryMultiplier_; + } + + // ────────────────────────────────────────────── + // Admin Functions + // ────────────────────────────────────────────── + + /// @notice Updates the base bond amount for future registrations. + /// @dev Existing tokens use their stored snapshots for refunds. + /// **IMPORTANT**: Verify contract solvency before increasing. + /// @param newBaseBond The new base bond amount (must be non-zero). + function setBaseBond(uint256 newBaseBond) external onlyOwner { + if (newBaseBond == 0) revert InvalidBaseBond(); + uint256 oldBaseBond = _baseBond; + _baseBond = newBaseBond; + emit BaseBondUpdated(oldBaseBond, newBaseBond); + } + + /// @notice Updates the country bond multiplier for future registrations. + /// @dev Existing tokens use their stored snapshots for refunds. + /// **IMPORTANT**: Verify contract solvency before increasing. + /// @param newMultiplier The new multiplier (must be non-zero). + function setCountryBondMultiplier(uint256 newMultiplier) external onlyOwner { + if (newMultiplier == 0) revert InvalidMultiplier(); + uint256 oldMultiplier = countryBondMultiplier(); + _countryBondMultiplier = newMultiplier; + emit CountryMultiplierUpdated(oldMultiplier, newMultiplier); + } + + /// @notice Updates both bond parameters atomically for future registrations. + /// @dev Existing tokens use their stored snapshots for refunds. + /// **IMPORTANT**: Verify contract solvency before increasing. + /// @param newBaseBond The new base bond amount (must be non-zero). + /// @param newMultiplier The new country multiplier (must be non-zero). + function setBondParameters(uint256 newBaseBond, uint256 newMultiplier) external onlyOwner { + if (newBaseBond == 0) revert InvalidBaseBond(); + if (newMultiplier == 0) revert InvalidMultiplier(); + + uint256 oldBaseBond = _baseBond; + uint256 oldMultiplier = countryBondMultiplier(); + + _baseBond = newBaseBond; + _countryBondMultiplier = newMultiplier; + + emit BaseBondUpdated(oldBaseBond, newBaseBond); + emit CountryMultiplierUpdated(oldMultiplier, newMultiplier); } // ────────────────────────────────────────────── @@ -259,6 +336,11 @@ contract FleetIdentityUpgradeable is return _baseBond; } + /// @notice Returns the country bond multiplier. + function countryBondMultiplier() public view returns (uint256) { + return _countryBondMultiplier; + } + // ══════════════════════════════════════════════ // Registration: Country (operator-only with tier) // ══════════════════════════════════════════════ @@ -351,35 +433,40 @@ contract FleetIdentityUpgradeable is uint32 region = tokenRegion(tokenId); bytes16 uuid = tokenUuid(tokenId); - address owner_ = uuidOwner[uuid]; + address owner = uuidOwner[uuid]; address operator = operatorOf(uuid); bool isLastToken = uuidTokenCount[uuid] == 1; if (region == OWNED_REGION_KEY) { if (tokenHolder != msg.sender) revert NotTokenOwner(); + // Use snapshot for accurate refund + uint256 ownershipBond = uuidOwnershipBondPaid[uuid]; + _burn(tokenId); _clearUuidOwnership(uuid); - _refundBond(owner_, _baseBond); + _refundBond(owner, ownershipBond); - emit FleetBurned(tokenHolder, tokenId, region, 0, _baseBond); + emit FleetBurned(tokenHolder, tokenId, region, 0, ownershipBond); } else { if (msg.sender != operator) { revert NotOperator(); } uint256 tier = fleetTier[tokenId]; - uint256 tierBondAmount = tierBond(tier, _isCountryRegion(region)); + // Use snapshot for accurate refund + uint256 tierBondAmount = _tokenTierBond(tokenId, tier); uuidTotalTierBonds[uuid] -= tierBondAmount; _cleanupFleetFromTier(tokenId, region, tier); + delete tokenTier0Bond[tokenId]; _burn(tokenId); if (isLastToken) { uuidLevel[uuid] = RegistrationLevel.Owned; uint256 ownedTokenId = uint256(uint128(uuid)); - _mint(owner_, ownedTokenId); + _mint(owner, ownedTokenId); } else { uuidTokenCount[uuid]--; } @@ -407,6 +494,7 @@ contract FleetIdentityUpgradeable is tokenId = uint256(uint128(uuid)); _mint(msg.sender, tokenId); + uuidOwnershipBondPaid[uuid] = _baseBond; _pullBond(msg.sender, _baseBond); emit UuidClaimed(msg.sender, uuid, operatorOf(uuid)); @@ -416,10 +504,17 @@ contract FleetIdentityUpgradeable is // Views: Bond & tier helpers // ══════════════════════════════════════════════ - /// @notice Bond required for tier K. + /// @notice Bond required for tier K at current parameters. + /// @dev Use _tokenTierBond for refund calculations on existing tokens. function tierBond(uint256 tier, bool isCountry) public view returns (uint256) { uint256 base = _baseBond << tier; - return isCountry ? base * COUNTRY_BOND_MULTIPLIER : base; + return isCountry ? base * countryBondMultiplier() : base; + } + + /// @notice Bond for a token at a given tier based on registration-time parameters. + /// @dev Uses tier-0 bond stored at registration; returns 0 for non-existent tokens. + function _tokenTierBond(uint256 tokenId, uint256 tier) internal view returns (uint256) { + return tokenTier0Bond[tokenId] << tier; } /// @notice Returns the cheapest tier for local inclusion. @@ -549,10 +644,14 @@ contract FleetIdentityUpgradeable is // Views: Region indexes // ══════════════════════════════════════════════ + /// @notice Returns all country codes that have at least one active fleet. + /// @return Array of ISO 3166-1 numeric country codes. function getActiveCountries() external view returns (uint16[] memory) { return _activeCountries; } + /// @notice Returns all admin-area region keys across all countries. + /// @return Array of encoded region keys (countryCode << 10 | adminCode). function getActiveAdminAreas() external view returns (uint32[] memory) { uint256 total = 0; uint256 countryCount = _activeCountries.length; @@ -572,10 +671,17 @@ contract FleetIdentityUpgradeable is return result; } + /// @notice Returns all active admin-area region keys for a specific country. + /// @param countryCode ISO 3166-1 numeric country code. + /// @return Array of encoded region keys for that country. function getCountryAdminAreas(uint16 countryCode) external view returns (uint32[] memory) { return _countryAdminAreas[countryCode]; } + /// @notice Encodes a country code and admin code into a region key. + /// @param countryCode ISO 3166-1 numeric country code (1-999). + /// @param adminCode Admin-area code within the country (1-255). + /// @return Encoded region key: (countryCode << 10) | adminCode. function makeAdminRegion(uint16 countryCode, uint16 adminCode) public pure returns (uint32) { return (uint32(countryCode) << uint32(ADMIN_SHIFT)) | uint32(adminCode); } @@ -621,6 +727,7 @@ contract FleetIdentityUpgradeable is delete uuidLevel[uuid]; delete uuidOperator[uuid]; delete uuidTotalTierBonds[uuid]; + delete uuidOwnershipBondPaid[uuid]; } function _decrementUuidCount(bytes16 uuid) internal returns (uint256 newCount) { @@ -665,6 +772,9 @@ contract FleetIdentityUpgradeable is RegistrationLevel targetLevel = isCountry ? RegistrationLevel.Country : RegistrationLevel.Local; uint256 targetTierBond = tierBond(targetTier, isCountry); + // Store tier-0 equivalent bond for accurate refunds when parameters change + uint256 tier0Bond = isCountry ? _baseBond * countryBondMultiplier() : _baseBond; + if (existingLevel == RegistrationLevel.Owned) { address operator = operatorOf(uuid); if (operator != msg.sender) revert NotOperator(); @@ -675,6 +785,7 @@ contract FleetIdentityUpgradeable is uuidTotalTierBonds[uuid] = targetTierBond; tokenId = _mintFleetTokenTo(owner_, uuid, region, targetTier); + tokenTier0Bond[tokenId] = tier0Bond; _pullBond(operator, targetTierBond); @@ -686,6 +797,8 @@ contract FleetIdentityUpgradeable is uuidTotalTierBonds[uuid] = targetTierBond; tokenId = _mintFleetToken(uuid, region, targetTier); + tokenTier0Bond[tokenId] = tier0Bond; + uuidOwnershipBondPaid[uuid] = _baseBond; _pullBond(msg.sender, _baseBond + targetTierBond); @@ -700,6 +813,7 @@ contract FleetIdentityUpgradeable is uuidTotalTierBonds[uuid] += targetTierBond; tokenId = _mintFleetTokenTo(owner_, uuid, region, targetTier); + tokenTier0Bond[tokenId] = tier0Bond; _pullBond(operator, targetTierBond); @@ -719,10 +833,14 @@ contract FleetIdentityUpgradeable is if (_regionTierMembers[region][targetTier].length >= TIER_CAPACITY) revert TierFull(); bool isCountry = _isCountryRegion(region); - uint256 currentBond = tierBond(currentTier, isCountry); + // Use stored tier-0 bond for current, current rate for target + uint256 currentBond = _tokenTierBond(tokenId, currentTier); uint256 targetBond = tierBond(targetTier, isCountry); uint256 additionalBond = targetBond - currentBond; + // Update tier-0 bond to current parameters since they're paying at current rates + tokenTier0Bond[tokenId] = isCountry ? _baseBond * countryBondMultiplier() : _baseBond; + uuidTotalTierBonds[uuid] += additionalBond; _removeFromTier(tokenId, region, currentTier); fleetTier[tokenId] = targetTier; @@ -743,9 +861,9 @@ contract FleetIdentityUpgradeable is if (targetTier >= currentTier) revert TargetTierNotLower(); if (_regionTierMembers[region][targetTier].length >= TIER_CAPACITY) revert TierFull(); - bool isCountry = _isCountryRegion(region); - uint256 currentBond = tierBond(currentTier, isCountry); - uint256 targetBond = tierBond(targetTier, isCountry); + // Use snapshot for accurate refund based on what was paid + uint256 currentBond = _tokenTierBond(tokenId, currentTier); + uint256 targetBond = _tokenTierBond(tokenId, targetTier); uint256 refund = currentBond - targetBond; uuidTotalTierBonds[uuid] -= refund; diff --git a/src/swarms/doc/assistant-guide.md b/src/swarms/doc/assistant-guide.md index 4699a618..f664e2ef 100644 --- a/src/swarms/doc/assistant-guide.md +++ b/src/swarms/doc/assistant-guide.md @@ -13,12 +13,12 @@ Two registry variants exist for different deployment targets: ### Core Components -| Contract | Role | Key Identity | Token | -| :--------------------------- | :---------------------------------- | :----------------------------------------- | :---- | -| **`FleetIdentity`** | Fleet Registry (ERC-721 Enumerable) | `(regionKey << 128) \| uint128(uuid)` | SFID | -| **`ServiceProvider`** | Service Registry (ERC-721) | `keccak256(url)` | SSV | -| **`SwarmRegistryL1`** | Swarm Registry (L1) | `keccak256(fleetUuid, providerId, filter)` | — | -| **`SwarmRegistryUniversal`** | Swarm Registry (Universal) | `keccak256(fleetUuid, providerId, filter)` | — | +| Contract | Role | Key Identity | Token | +| :--------------------------- | :---------------------------------- | :-------------------------------------------- | :---- | +| **`FleetIdentity`** | Fleet Registry (ERC-721 Enumerable) | `(regionKey << 128) \| uint128(uuid)` | SFID | +| **`ServiceProvider`** | Service Registry (ERC-721) | `keccak256(url)` | SSV | +| **`SwarmRegistryL1`** | Swarm Registry (L1) | `keccak256(fleetUuid, filter, fpSize, tagType)` | — | +| **`SwarmRegistryUniversal`** | Swarm Registry (Universal) | `keccak256(fleetUuid, filter, fpSize, tagType)` | — | All contracts are **permissionless** — access control is enforced through NFT ownership rather than admin roles. `FleetIdentity` additionally requires an ERC-20 bond (e.g. NODL) to register a fleet, acting as an anti-spam / anti-abuse mechanism. @@ -56,6 +56,36 @@ This allows the same UUID to be registered in multiple regions, each with a dist Country fleets pay 16× more but appear in all admin-area bundles within their country. This economic difference provides locals a significant advantage: a local can reach tier 4 for the same cost a country player pays for tier 0. +### Runtime Bond Parameter Configuration + +The contract owner can adjust bond parameters at runtime: + +```solidity +// Update base bond for future registrations +fleetIdentity.setBaseBond(newBaseBond); + +// Update country multiplier for future registrations +fleetIdentity.setCountryBondMultiplier(newMultiplier); + +// Update both parameters atomically +fleetIdentity.setBondParameters(newBaseBond, newMultiplier); +``` + +**Tier-0 Bond Tracking:** + +To ensure fair refunds when parameters change, each token stores its "tier-0 equivalent bond" at registration time: + +- **For local tokens**: `tokenTier0Bond[tokenId] = baseBond` +- **For country tokens**: `tokenTier0Bond[tokenId] = baseBond * countryBondMultiplier()` +- **Bond at tier K**: `tokenTier0Bond[tokenId] << K` (bitshift = multiply by 2^K) + +This simplified approach: +- Stores a single uint256 per token (not a struct) +- No need to track country/local distinction or multiplier separately +- O(1) operations for promote/demote/burn with accurate refunds + +For UUID ownership bonds, `uuidOwnershipBondPaid[uuid]` tracks what was paid at claim/first-registration. + ### UUID Ownership Model UUIDs have an ownership model with registration levels: diff --git a/src/swarms/doc/swarm-operations.md b/src/swarms/doc/swarm-operations.md index 6dbffe7a..624b0323 100644 --- a/src/swarms/doc/swarm-operations.md +++ b/src/swarms/doc/swarm-operations.md @@ -19,7 +19,7 @@ sequenceDiagram FO->>+SR: registerSwarm(fleetUuid, providerId, filter, fpSize, tagType) SR->>FI: uuidOwner(fleetUuid) SR->>SP: ownerOf(providerId) - Note over SR: swarmId = keccak256(fleetUuid, providerId, filter) + Note over SR: swarmId = keccak256(fleetUuid, filter, fpSize, tagType) SR-->>-FO: swarmId (status: REGISTERED) PRV->>+SR: acceptSwarm(swarmId) diff --git a/test/FleetIdentity.t.sol b/test/FleetIdentity.t.sol index 18fee313..36654de1 100644 --- a/test/FleetIdentity.t.sol +++ b/test/FleetIdentity.t.sol @@ -94,7 +94,7 @@ contract FleetIdentityTest is Test { // Deploy proxy with initialize call ERC1967Proxy proxy = new ERC1967Proxy( address(impl), - abi.encodeCall(FleetIdentityUpgradeable.initialize, (address(bondToken), BASE_BOND, owner)) + abi.encodeCall(FleetIdentityUpgradeable.initialize, (owner, address(bondToken), BASE_BOND, 0)) ); // Cast proxy to contract type @@ -219,7 +219,7 @@ contract FleetIdentityTest is Test { assertEq(fleet.TIER_CAPACITY(), 10); assertEq(fleet.MAX_TIERS(), 24); assertEq(fleet.MAX_BONDED_UUID_BUNDLE_SIZE(), 20); - assertEq(fleet.COUNTRY_BOND_MULTIPLIER(), 16); + assertEq(fleet.countryBondMultiplier(), 16); } // --- tierBond --- @@ -231,7 +231,7 @@ contract FleetIdentityTest is Test { function test_tierBond_country_tier0() public view { // Country regions get 16x multiplier - assertEq(fleet.tierBond(0, true), BASE_BOND * fleet.COUNTRY_BOND_MULTIPLIER()); + assertEq(fleet.tierBond(0, true), BASE_BOND * fleet.countryBondMultiplier()); } function test_tierBond_local_tier1() public view { @@ -239,7 +239,7 @@ contract FleetIdentityTest is Test { } function test_tierBond_country_tier1() public view { - assertEq(fleet.tierBond(1, true), BASE_BOND * fleet.COUNTRY_BOND_MULTIPLIER() * 2); + assertEq(fleet.tierBond(1, true), BASE_BOND * fleet.countryBondMultiplier() * 2); } function test_tierBond_geometricProgression() public view { @@ -257,7 +257,7 @@ contract FleetIdentityTest is Test { assertEq(fleet.tokenRegion(tokenId), _regionUS()); assertEq(fleet.fleetTier(tokenId), 0); - assertEq(fleet.bonds(tokenId), BASE_BOND * fleet.COUNTRY_BOND_MULTIPLIER()); // Country gets 16x multiplier + assertEq(fleet.bonds(tokenId), BASE_BOND * fleet.countryBondMultiplier()); // Country gets 16x multiplier assertEq(fleet.regionTierCount(_regionUS()), 1); } @@ -315,7 +315,7 @@ contract FleetIdentityTest is Test { assertEq(fleet.fleetTier(c1), 0); assertEq(fleet.fleetTier(l1), 0); - assertEq(fleet.bonds(c1), BASE_BOND * fleet.COUNTRY_BOND_MULTIPLIER()); // Country gets 16× multiplier + assertEq(fleet.bonds(c1), BASE_BOND * fleet.countryBondMultiplier()); // Country gets 16× multiplier assertEq(fleet.bonds(l1), BASE_BOND); // Local gets 1× multiplier } @@ -329,13 +329,13 @@ contract FleetIdentityTest is Test { vm.prank(bob); uint256 us21 = fleet.registerFleetCountry(_uuid(100), US, 1); assertEq(fleet.fleetTier(us21), 1); - assertEq(fleet.bonds(us21), BASE_BOND * fleet.COUNTRY_BOND_MULTIPLIER() * 2); // Country tier 1: 16× * 2^1 + assertEq(fleet.bonds(us21), BASE_BOND * fleet.countryBondMultiplier() * 2); // Country tier 1: 16× * 2^1 // DE country is independent - can still join tier 0 vm.prank(bob); uint256 de1 = fleet.registerFleetCountry(_uuid(200), DE, 0); assertEq(fleet.fleetTier(de1), 0); - assertEq(fleet.bonds(de1), BASE_BOND * fleet.COUNTRY_BOND_MULTIPLIER()); + assertEq(fleet.bonds(de1), BASE_BOND * fleet.countryBondMultiplier()); assertEq(fleet.regionTierCount(_regionDE()), 1); // US local is independent - can still join tier 0 @@ -354,13 +354,13 @@ contract FleetIdentityTest is Test { vm.prank(bob); uint256 us21 = fleet.registerFleetCountry(_uuid(500), US, 1); assertEq(fleet.fleetTier(us21), 1); - assertEq(fleet.bonds(us21), BASE_BOND * fleet.COUNTRY_BOND_MULTIPLIER() * 2); // Country tier 1: 16× * 2^1 + assertEq(fleet.bonds(us21), BASE_BOND * fleet.countryBondMultiplier() * 2); // Country tier 1: 16× * 2^1 // DE country is independent - can still join tier 0 vm.prank(bob); uint256 de1 = fleet.registerFleetCountry(_uuid(600), DE, 0); assertEq(fleet.fleetTier(de1), 0); - assertEq(fleet.bonds(de1), BASE_BOND * fleet.COUNTRY_BOND_MULTIPLIER()); // Country tier 0: 16× * 2^0 + assertEq(fleet.bonds(de1), BASE_BOND * fleet.countryBondMultiplier()); // Country tier 0: 16× * 2^0 } function test_perRegionTiers_twoAdminAreasIndependent() public { @@ -991,7 +991,7 @@ contract FleetIdentityTest is Test { // Deploy proxy with initialize call ERC1967Proxy proxy = new ERC1967Proxy( address(impl), - abi.encodeCall(FleetIdentityUpgradeable.initialize, (address(badToken), BASE_BOND, owner)) + abi.encodeCall(FleetIdentityUpgradeable.initialize, (owner, address(badToken), BASE_BOND, 0)) ); FleetIdentityUpgradeable f = FleetIdentityUpgradeable(address(proxy)); @@ -1057,25 +1057,39 @@ contract FleetIdentityTest is Test { // --- Edge cases --- - function test_zeroBaseBond_allowsRegistration() public { + function test_initialize_zeroBaseBond_usesDefault() public { // Deploy implementation FleetIdentityUpgradeable impl = new FleetIdentityUpgradeable(); - // Deploy proxy with initialize call (zero bond) + // Deploy proxy with zero base bond - should use DEFAULT_BASE_BOND ERC1967Proxy proxy = new ERC1967Proxy( address(impl), - abi.encodeCall(FleetIdentityUpgradeable.initialize, (address(bondToken), 0, owner)) + abi.encodeCall(FleetIdentityUpgradeable.initialize, (owner, address(bondToken), 0, 0)) ); FleetIdentityUpgradeable f = FleetIdentityUpgradeable(address(proxy)); - - vm.prank(alice); - bondToken.approve(address(f), type(uint256).max); + assertEq(f.BASE_BOND(), f.DEFAULT_BASE_BOND()); + } - vm.prank(alice); - uint256 tokenId = f.registerFleetLocal(UUID_1, US, ADMIN_CA, 0); - assertEq(f.bonds(tokenId), 0); + function test_initialize_zeroMultiplier_usesDefault() public { + // Deploy implementation + FleetIdentityUpgradeable impl = new FleetIdentityUpgradeable(); + // Deploy proxy with zero multiplier - should use DEFAULT_COUNTRY_BOND_MULTIPLIER + ERC1967Proxy proxy = new ERC1967Proxy( + address(impl), + abi.encodeCall(FleetIdentityUpgradeable.initialize, (owner, address(bondToken), BASE_BOND, 0)) + ); + FleetIdentityUpgradeable f = FleetIdentityUpgradeable(address(proxy)); + assertEq(f.countryBondMultiplier(), f.DEFAULT_COUNTRY_BOND_MULTIPLIER()); + } - vm.prank(alice); - f.burn(tokenId); + function test_initialize_revertsOnZeroBondToken() public { + // Deploy implementation + FleetIdentityUpgradeable impl = new FleetIdentityUpgradeable(); + // Attempt to deploy proxy with zero bond token - should revert + vm.expectRevert(FleetIdentityUpgradeable.InvalidBondToken.selector); + new ERC1967Proxy( + address(impl), + abi.encodeCall(FleetIdentityUpgradeable.initialize, (owner, address(0), BASE_BOND, 0)) + ); } // --- Fuzz Tests --- @@ -1088,7 +1102,7 @@ contract FleetIdentityTest is Test { assertEq(fleet.tokenRegion(tokenId), uint32(cc)); assertEq(fleet.fleetTier(tokenId), 0); - assertEq(fleet.bonds(tokenId), BASE_BOND * fleet.COUNTRY_BOND_MULTIPLIER()); // Country gets 16x multiplier + assertEq(fleet.bonds(tokenId), BASE_BOND * fleet.countryBondMultiplier()); // Country gets 16x multiplier } function testFuzz_registerFleetLocal_validCodes(uint16 cc, uint16 admin) public { @@ -1861,7 +1875,7 @@ contract FleetIdentityTest is Test { // Local regions get 1× multiplier assertEq(fleet.tierBond(tier, false), expected); // Country regions get 16x multiplier - assertEq(fleet.tierBond(tier, true), expected * fleet.COUNTRY_BOND_MULTIPLIER()); + assertEq(fleet.tierBond(tier, true), expected * fleet.countryBondMultiplier()); } function testFuzz_perRegionTiers_newRegionAlwaysStartsAtTier0(uint16 cc) public { @@ -1878,7 +1892,7 @@ contract FleetIdentityTest is Test { vm.prank(bob); uint256 tokenId = fleet.registerFleetCountry(_uuid(999), cc, 0); assertEq(fleet.fleetTier(tokenId), 0); - assertEq(fleet.bonds(tokenId), BASE_BOND * fleet.COUNTRY_BOND_MULTIPLIER()); // Country gets 16x multiplier + assertEq(fleet.bonds(tokenId), BASE_BOND * fleet.countryBondMultiplier()); // Country gets 16x multiplier } function testFuzz_tierAssignment_autoFillsSequentiallyPerRegion(uint8 count) public { @@ -1966,7 +1980,7 @@ contract FleetIdentityTest is Test { function test_countryInclusionHint_emptyReturnsZero() public view { (uint256 tier, uint256 bond) = fleet.countryInclusionHint(US); assertEq(tier, 0); - assertEq(bond, BASE_BOND * fleet.COUNTRY_BOND_MULTIPLIER()); // Country pays 16x multiplier + assertEq(bond, BASE_BOND * fleet.countryBondMultiplier()); // Country pays 16x multiplier } function test_countryInclusionHint_onlyCountryFleets() public { @@ -1977,7 +1991,7 @@ contract FleetIdentityTest is Test { // Tier 0 is full → cheapest inclusion = tier 1. (uint256 tier, uint256 bond) = fleet.countryInclusionHint(US); assertEq(tier, 1); - assertEq(bond, BASE_BOND * fleet.COUNTRY_BOND_MULTIPLIER() * 2); // Country pays 16x multiplier, tier 1 = 2× base + assertEq(bond, BASE_BOND * fleet.countryBondMultiplier() * 2); // Country pays 16x multiplier, tier 1 = 2× base } function test_countryInclusionHint_adminAreaCreatesPressure() public { @@ -2656,11 +2670,11 @@ contract FleetIdentityTest is Test { } function test_buildBundle_sharedCursor_sameTierIndex_differentBondByRegion() public view { - // Local tier 0 = BASE_BOND, Country tier 0 = BASE_BOND * fleet.COUNTRY_BOND_MULTIPLIER() (multiplier) + // Local tier 0 = BASE_BOND, Country tier 0 = BASE_BOND * fleet.countryBondMultiplier() (multiplier) assertEq(fleet.tierBond(0, false), BASE_BOND); - assertEq(fleet.tierBond(0, true), BASE_BOND * fleet.COUNTRY_BOND_MULTIPLIER()); + assertEq(fleet.tierBond(0, true), BASE_BOND * fleet.countryBondMultiplier()); assertEq(fleet.tierBond(1, false), BASE_BOND * 2); - assertEq(fleet.tierBond(1, true), BASE_BOND * fleet.COUNTRY_BOND_MULTIPLIER() * 2); + assertEq(fleet.tierBond(1, true), BASE_BOND * fleet.countryBondMultiplier() * 2); } // ── Lifecycle ── @@ -3818,4 +3832,252 @@ contract FleetIdentityTest is Test { assertEq(fleet.ownerOf(ownedTokenId), alice); assertTrue(fleet.isOwnedOnly(UUID_1)); } + + // ══════════════════════════════════════════════ + // Bond Parameter Tests (setBaseBond, setCountryBondMultiplier) + // ══════════════════════════════════════════════ + + function test_setBaseBond_onlyOwner() public { + vm.prank(alice); + vm.expectRevert(); + fleet.setBaseBond(200 ether); + } + + function test_setBaseBond_emitsEvent() public { + vm.prank(owner); + vm.expectEmit(true, true, false, false, address(fleet)); + emit BaseBondUpdated(BASE_BOND, 200 ether); + fleet.setBaseBond(200 ether); + } + + function test_setBaseBond_revertsOnZero() public { + vm.prank(owner); + vm.expectRevert(FleetIdentityUpgradeable.InvalidBaseBond.selector); + fleet.setBaseBond(0); + } + + function test_setBaseBond_updatesValue() public { + vm.prank(owner); + fleet.setBaseBond(200 ether); + assertEq(fleet.BASE_BOND(), 200 ether); + } + + function test_setCountryBondMultiplier_onlyOwner() public { + vm.prank(alice); + vm.expectRevert(); + fleet.setCountryBondMultiplier(32); + } + + function test_setCountryBondMultiplier_emitsEvent() public { + vm.prank(owner); + vm.expectEmit(true, true, false, false, address(fleet)); + emit CountryMultiplierUpdated(16, 32); + fleet.setCountryBondMultiplier(32); + } + + function test_setCountryBondMultiplier_revertsOnZero() public { + vm.prank(owner); + vm.expectRevert(FleetIdentityUpgradeable.InvalidMultiplier.selector); + fleet.setCountryBondMultiplier(0); + } + + function test_setCountryBondMultiplier_updatesValue() public { + vm.prank(owner); + fleet.setCountryBondMultiplier(32); + assertEq(fleet.countryBondMultiplier(), 32); + } + + function test_countryBondMultiplier_defaultsTo16() public view { + // Fresh contract should return default 16 + assertEq(fleet.countryBondMultiplier(), 16); + } + + function test_setBondParameters_updatesBoth() public { + vm.prank(owner); + fleet.setBondParameters(200 ether, 32); + assertEq(fleet.BASE_BOND(), 200 ether); + assertEq(fleet.countryBondMultiplier(), 32); + } + + function test_setBondParameters_onlyOwner() public { + vm.prank(alice); + vm.expectRevert(); + fleet.setBondParameters(200 ether, 32); + } + + function test_setBondParameters_revertsOnZeroBaseBond() public { + vm.prank(owner); + vm.expectRevert(FleetIdentityUpgradeable.InvalidBaseBond.selector); + fleet.setBondParameters(0, 32); + } + + function test_setBondParameters_revertsOnZeroMultiplier() public { + vm.prank(owner); + vm.expectRevert(FleetIdentityUpgradeable.InvalidMultiplier.selector); + fleet.setBondParameters(200 ether, 0); + } + + function test_setBondParameters_emitsBothEvents() public { + vm.prank(owner); + vm.expectEmit(true, true, false, false, address(fleet)); + emit BaseBondUpdated(BASE_BOND, 200 ether); + vm.expectEmit(true, true, false, false, address(fleet)); + emit CountryMultiplierUpdated(16, 32); + fleet.setBondParameters(200 ether, 32); + } + + event BaseBondUpdated(uint256 indexed oldBaseBond, uint256 indexed newBaseBond); + event CountryMultiplierUpdated(uint256 indexed oldMultiplier, uint256 indexed newMultiplier); + + // ══════════════════════════════════════════════ + // Bond Snapshot Tests + // ══════════════════════════════════════════════ + + function test_snapshot_burnRefundsAtOriginalRate() public { + // Register at original BASE_BOND (100 ether) + vm.prank(alice); + uint256 tokenId = fleet.registerFleetLocal(UUID_1, US, ADMIN_CA, 0); + + // Change baseBond to 200 ether + vm.prank(owner); + fleet.setBaseBond(200 ether); + + // Burn should refund at original rate (100 ether tier 0 bond) + uint256 balanceBefore = bondToken.balanceOf(alice); + vm.prank(alice); + fleet.burn(tokenId); + + uint256 expectedRefund = 100 ether; // Original tier 0 bond + assertEq(bondToken.balanceOf(alice), balanceBefore + expectedRefund); + } + + function test_snapshot_promoteAfterBaseBondIncrease() public { + // Register at tier 0 with BASE_BOND = 100 ether + vm.prank(alice); + uint256 tokenId = fleet.registerFleetLocal(UUID_1, US, ADMIN_CA, 0); + + // Change baseBond to 200 ether + vm.prank(owner); + fleet.setBaseBond(200 ether); + + // Promote to tier 1 using reassignTier + // Current tier 1 bond at new rate = 200 << 1 = 400 ether + // Old tier 0 bond from snapshot = 100 ether + // Additional = 400 - 100 = 300 ether + uint256 balanceBefore = bondToken.balanceOf(alice); + vm.prank(alice); + fleet.reassignTier(tokenId, 1); + + assertEq(bondToken.balanceOf(alice), balanceBefore - 300 ether); + } + + function test_snapshot_demoteAfterPromoteUsesNewSnapshot() public { + // Register at tier 0 with BASE_BOND = 100 ether + vm.prank(alice); + uint256 tokenId = fleet.registerFleetLocal(UUID_1, US, ADMIN_CA, 0); + + // Change baseBond to 200 ether + vm.prank(owner); + fleet.setBaseBond(200 ether); + + // Promote to tier 1 - this updates the snapshot to new params + vm.prank(alice); + fleet.reassignTier(tokenId, 1); + + // Demote back to tier 0 + // Snapshot was updated on promote, so: + // Current (snapshot) tier 1 = 200 << 1 = 400 ether + // Target (snapshot) tier 0 = 200 << 0 = 200 ether + // Refund = 400 - 200 = 200 ether + uint256 balanceBefore = bondToken.balanceOf(alice); + vm.prank(alice); + fleet.reassignTier(tokenId, 0); + + assertEq(bondToken.balanceOf(alice), balanceBefore + 200 ether); + } + + function test_snapshot_claimUuidRefundsOriginalRate() public { + // Claim UUID at original BASE_BOND (100 ether) + vm.prank(alice); + uint256 tokenId = fleet.claimUuid(UUID_1, address(0)); + + // Change baseBond to 200 ether + vm.prank(owner); + fleet.setBaseBond(200 ether); + + // Burn owned token should refund at original rate + uint256 balanceBefore = bondToken.balanceOf(alice); + vm.prank(alice); + fleet.burn(tokenId); + + assertEq(bondToken.balanceOf(alice), balanceBefore + 100 ether); + } + + function test_snapshot_countryMultiplierChangeRefund() public { + // Register country fleet at original multiplier (16) + vm.prank(alice); + uint256 tokenId = fleet.registerFleetCountry(UUID_1, US, 0); + + // Original bond: 100 ether * 16 = 1600 ether + 100 ownership = 1700 total + // Change multiplier to 32 + vm.prank(owner); + fleet.setCountryBondMultiplier(32); + + // Burn should refund at original multiplier + uint256 balanceBefore = bondToken.balanceOf(alice); + vm.prank(alice); + fleet.burn(tokenId); + + // Tier bond refund should be 1600 ether (original) + assertEq(bondToken.balanceOf(alice), balanceBefore + 1600 ether); + } + + function test_snapshot_newRegistrationUsesCurrentParams() public { + // Change params before registration + vm.prank(owner); + fleet.setBaseBond(200 ether); + vm.prank(owner); + fleet.setCountryBondMultiplier(32); + + uint256 balanceBefore = bondToken.balanceOf(alice); + + vm.prank(alice); + fleet.registerFleetLocal(UUID_1, US, ADMIN_CA, 0); + + // Should pull: 200 ether (ownership) + 200 ether (tier 0) = 400 ether + assertEq(bondToken.balanceOf(alice), balanceBefore - 400 ether); + } + + // ══════════════════════════════════════════════ + // Region Index Tests + // ══════════════════════════════════════════════ + + function test_getCountryAdminAreas_returnsRegisteredAreas() public { + vm.prank(alice); + fleet.registerFleetLocal(UUID_1, US, ADMIN_CA, 0); + + vm.prank(alice); + fleet.registerFleetLocal(UUID_2, US, ADMIN_NY, 0); + + uint32[] memory areas = fleet.getCountryAdminAreas(US); + assertEq(areas.length, 2); + + // Areas should contain both admin area region keys + uint32 caRegion = fleet.makeAdminRegion(US, ADMIN_CA); + uint32 nyRegion = fleet.makeAdminRegion(US, ADMIN_NY); + + bool foundCA = false; + bool foundNY = false; + for (uint256 i = 0; i < areas.length; i++) { + if (areas[i] == caRegion) foundCA = true; + if (areas[i] == nyRegion) foundNY = true; + } + assertTrue(foundCA); + assertTrue(foundNY); + } + + function test_getCountryAdminAreas_emptyForNoRegistrations() public view { + uint32[] memory areas = fleet.getCountryAdminAreas(JP); + assertEq(areas.length, 0); + } } diff --git a/test/FleetIdentityFairness.t.sol b/test/FleetIdentityFairness.t.sol index 73bbbb03..bcc1bca7 100644 --- a/test/FleetIdentityFairness.t.sol +++ b/test/FleetIdentityFairness.t.sol @@ -114,7 +114,7 @@ contract FleetIdentityFairnessTest is Test { // Deploy proxy with initialize call ERC1967Proxy proxy = new ERC1967Proxy( address(impl), - abi.encodeCall(FleetIdentityUpgradeable.initialize, (address(bondToken), BASE_BOND, fleetOwner)) + abi.encodeCall(FleetIdentityUpgradeable.initialize, (fleetOwner, address(bondToken), BASE_BOND, 0)) ); // Cast proxy to contract type @@ -368,7 +368,7 @@ contract FleetIdentityFairnessTest is Test { FleetIdentityUpgradeable fleet = _deployFleet(); // Verify multiplier - assertEq(fleet.COUNTRY_BOND_MULTIPLIER(), 16, "Multiplier should be 16"); + assertEq(fleet.countryBondMultiplier(), 16, "Multiplier should be 16"); // At every tier, country pays exactly 16× local for (uint256 tier = 0; tier < 6; tier++) { @@ -469,7 +469,7 @@ contract FleetIdentityFairnessTest is Test { FleetIdentityUpgradeable fleet = _deployFleet(); // Invariant 1: Country multiplier is exactly 16 - assertEq(fleet.COUNTRY_BOND_MULTIPLIER(), 16, "INVARIANT: Country multiplier must be 16"); + assertEq(fleet.countryBondMultiplier(), 16, "INVARIANT: Country multiplier must be 16"); // Invariant 2: Tier capacity allows fair competition assertEq(fleet.TIER_CAPACITY(), 10, "INVARIANT: Tier capacity must be 10"); diff --git a/test/SwarmRegistryL1.t.sol b/test/SwarmRegistryL1.t.sol index 126fe38c..7733607a 100644 --- a/test/SwarmRegistryL1.t.sol +++ b/test/SwarmRegistryL1.t.sol @@ -46,7 +46,7 @@ contract SwarmRegistryL1Test is Test { FleetIdentityUpgradeable fleetImpl = new FleetIdentityUpgradeable(); ERC1967Proxy fleetProxy = new ERC1967Proxy( address(fleetImpl), - abi.encodeCall(FleetIdentityUpgradeable.initialize, (address(bondToken), FLEET_BOND, contractOwner)) + abi.encodeCall(FleetIdentityUpgradeable.initialize, (contractOwner, address(bondToken), FLEET_BOND, 0)) ); fleetContract = FleetIdentityUpgradeable(address(fleetProxy)); diff --git a/test/SwarmRegistryUniversal.t.sol b/test/SwarmRegistryUniversal.t.sol index a7612bd5..99f27b24 100644 --- a/test/SwarmRegistryUniversal.t.sol +++ b/test/SwarmRegistryUniversal.t.sol @@ -48,7 +48,7 @@ contract SwarmRegistryUniversalTest is Test { FleetIdentityUpgradeable fleetImpl = new FleetIdentityUpgradeable(); ERC1967Proxy fleetProxy = new ERC1967Proxy( address(fleetImpl), - abi.encodeCall(FleetIdentityUpgradeable.initialize, (address(bondToken), FLEET_BOND, contractOwner)) + abi.encodeCall(FleetIdentityUpgradeable.initialize, (contractOwner, address(bondToken), FLEET_BOND, 0)) ); fleetContract = FleetIdentityUpgradeable(address(fleetProxy)); diff --git a/test/upgrade-demo/TestUpgradeOnAnvil.s.sol b/test/upgrade-demo/TestUpgradeOnAnvil.s.sol index be36b0bf..77001a3b 100644 --- a/test/upgrade-demo/TestUpgradeOnAnvil.s.sol +++ b/test/upgrade-demo/TestUpgradeOnAnvil.s.sol @@ -111,7 +111,7 @@ contract TestUpgradeOnAnvil is Script { FleetIdentityUpgradeable fiImpl = new FleetIdentityUpgradeable(); ERC1967Proxy fiProxy = new ERC1967Proxy( address(fiImpl), - abi.encodeCall(FleetIdentityUpgradeable.initialize, (address(bondToken), baseBond, deployer)) + abi.encodeCall(FleetIdentityUpgradeable.initialize, (deployer, address(bondToken), baseBond, 0)) ); fleetIdentityProxy = address(fiProxy); console.log(" FleetIdentity Proxy:", fleetIdentityProxy); diff --git a/test/upgradeable/UpgradeableContracts.t.sol b/test/upgradeable/UpgradeableContracts.t.sol index b6ea49c5..c8a2f9dd 100644 --- a/test/upgradeable/UpgradeableContracts.t.sol +++ b/test/upgradeable/UpgradeableContracts.t.sol @@ -141,7 +141,7 @@ contract UpgradeableContractsTest is Test { fleetIdentityProxy = address( new ERC1967Proxy( address(fleetIdentityImpl), - abi.encodeCall(FleetIdentityUpgradeable.initialize, (address(bondToken), BASE_BOND, owner)) + abi.encodeCall(FleetIdentityUpgradeable.initialize, (owner, address(bondToken), BASE_BOND, 0)) ) ); fleetIdentity = FleetIdentityUpgradeable(fleetIdentityProxy); @@ -260,12 +260,12 @@ contract UpgradeableContractsTest is Test { function test_FleetIdentity_CannotReinitialize() public { vm.expectRevert(Initializable.InvalidInitialization.selector); - fleetIdentity.initialize(address(bondToken), BASE_BOND, attacker); + fleetIdentity.initialize(attacker, address(bondToken), BASE_BOND, 0); } function test_FleetIdentity_ImplementationCannotBeInitialized() public { vm.expectRevert(Initializable.InvalidInitialization.selector); - fleetIdentityImpl.initialize(address(bondToken), BASE_BOND, attacker); + fleetIdentityImpl.initialize(attacker, address(bondToken), BASE_BOND, 0); } // ========================================================================= From e3e23433c344bda1a10796b8682cffa01a53e926 Mon Sep 17 00:00:00 2001 From: Alex Sedighi Date: Fri, 6 Mar 2026 16:20:25 +1300 Subject: [PATCH 09/15] ci: add lcov coverage report to PR --- .github/workflows/checks.yml | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/.github/workflows/checks.yml b/.github/workflows/checks.yml index cff85beb..bfebae9d 100644 --- a/.github/workflows/checks.yml +++ b/.github/workflows/checks.yml @@ -62,6 +62,15 @@ jobs: path: coverage.lcov retention-days: 30 + - name: Report coverage to PR + uses: zgosalvez/github-actions-report-lcov@v4 + with: + coverage-files: coverage.lcov + minimum-coverage: 95 + github-token: ${{ secrets.GITHUB_TOKEN }} + update-comment: true + working-directory: ./ + - name: Check line coverage threshold run: | # Extract line coverage from lcov report for src/swarms/ contracts only From 25945670430d61b2b9c5fa0117fb41f6d068ee00 Mon Sep 17 00:00:00 2001 From: Alex Sedighi Date: Fri, 6 Mar 2026 17:00:06 +1300 Subject: [PATCH 10/15] refactor(swarms): use FingerprintSize enum for optimized _readFingerprint - Replace uint8 fingerprintSize with FingerprintSize enum (BITS_8, BITS_16) - Optimize _readFingerprint: direct byte access without loops/divisions - Simplify checkMembership m calculation with ternary operator - Remove InvalidFingerprintSize error (enum enforces valid values) - Update tests for both SwarmRegistryL1 and SwarmRegistryUniversal - Add missing technical terms to cspell dictionary --- .cspell.json | 13 +- src/swarms/SwarmRegistryL1Upgradeable.sol | 76 ++-- .../SwarmRegistryUniversalUpgradeable.sol | 72 ++-- test/SwarmRegistryL1.t.sol | 296 ++++++------- test/SwarmRegistryUniversal.t.sol | 388 ++++++++++-------- test/upgradeable/UpgradeableContracts.t.sol | 4 +- 6 files changed, 461 insertions(+), 388 deletions(-) diff --git a/.cspell.json b/.cspell.json index 04ce2bc4..72bcd163 100644 --- a/.cspell.json +++ b/.cspell.json @@ -76,6 +76,17 @@ "MBOND", "USCA", "USNY", - "usca" + "usca", + "UUPS", + "reinitializer", + "Reinitializer", + "Initializable", + "bitshift", + "timelock", + "iface", + "pkill", + "Blockscout", + "REINIT", + "reinit" ] } diff --git a/src/swarms/SwarmRegistryL1Upgradeable.sol b/src/swarms/SwarmRegistryL1Upgradeable.sol index 31ed8399..182474c5 100644 --- a/src/swarms/SwarmRegistryL1Upgradeable.sol +++ b/src/swarms/SwarmRegistryL1Upgradeable.sol @@ -43,7 +43,6 @@ contract SwarmRegistryL1Upgradeable is Initializable, Ownable2StepUpgradeable, U // ────────────────────────────────────────────── // Errors // ────────────────────────────────────────────── - error InvalidFingerprintSize(); error InvalidFilterSize(); error InvalidUuid(); error NotUuidOwner(); @@ -71,11 +70,17 @@ contract SwarmRegistryL1Upgradeable is Initializable, Ownable2StepUpgradeable, U GENERIC // 0x03 } + /// @notice Fingerprint size for XOR filter (8-bit or 16-bit only for gas efficiency) + enum FingerprintSize { + BITS_8, // 8-bit fingerprints (1 byte each) + BITS_16 // 16-bit fingerprints (2 bytes each) + } + struct Swarm { bytes16 fleetUuid; uint256 providerId; address filterPointer; // SSTORE2 pointer - uint8 fingerprintSize; + FingerprintSize fpSize; TagType tagType; SwarmStatus status; } @@ -83,7 +88,6 @@ contract SwarmRegistryL1Upgradeable is Initializable, Ownable2StepUpgradeable, U // ────────────────────────────────────────────── // Constants // ────────────────────────────────────────────── - uint8 public constant MAX_FINGERPRINT_SIZE = 16; // ────────────────────────────────────────────── // Storage (V1) - Order matters for upgrades! @@ -171,12 +175,12 @@ contract SwarmRegistryL1Upgradeable is Initializable, Ownable2StepUpgradeable, U // ────────────────────────────────────────────── /// @notice Derives a deterministic swarm ID. - function computeSwarmId(bytes16 fleetUuid, bytes calldata filterData_, uint8 fingerprintSize, TagType tagType) + function computeSwarmId(bytes16 fleetUuid, bytes calldata filterData_, FingerprintSize fpSize, TagType tagType) public pure returns (uint256) { - return uint256(keccak256(abi.encode(fleetUuid, filterData_, fingerprintSize, tagType))); + return uint256(keccak256(abi.encode(fleetUuid, filterData_, fpSize, tagType))); } // ────────────────────────────────────────────── @@ -184,19 +188,21 @@ contract SwarmRegistryL1Upgradeable is Initializable, Ownable2StepUpgradeable, U // ────────────────────────────────────────────── /// @notice Registers a new swarm. Caller must own the fleet UUID. + /// @param fleetUuid The fleet UUID (must be owned by caller) + /// @param providerId The service provider NFT ID + /// @param filterData_ The XOR filter data + /// @param fpSize Fingerprint size (BITS_8 or BITS_16) + /// @param tagType The tag type for this swarm function registerSwarm( bytes16 fleetUuid, uint256 providerId, bytes calldata filterData_, - uint8 fingerprintSize, + FingerprintSize fpSize, TagType tagType ) external nonReentrant returns (uint256 swarmId) { if (fleetUuid == bytes16(0)) { revert InvalidUuid(); } - if (fingerprintSize == 0 || fingerprintSize > MAX_FINGERPRINT_SIZE) { - revert InvalidFingerprintSize(); - } if (filterData_.length == 0 || filterData_.length > 24576) { revert InvalidFilterSize(); } @@ -209,7 +215,7 @@ contract SwarmRegistryL1Upgradeable is Initializable, Ownable2StepUpgradeable, U revert ProviderDoesNotExist(); } - swarmId = computeSwarmId(fleetUuid, filterData_, fingerprintSize, tagType); + swarmId = computeSwarmId(fleetUuid, filterData_, fpSize, tagType); if (swarms[swarmId].filterPointer != address(0)) { revert SwarmAlreadyExists(); @@ -218,7 +224,7 @@ contract SwarmRegistryL1Upgradeable is Initializable, Ownable2StepUpgradeable, U Swarm storage s = swarms[swarmId]; s.fleetUuid = fleetUuid; s.providerId = providerId; - s.fingerprintSize = fingerprintSize; + s.fpSize = fpSize; s.tagType = tagType; s.status = SwarmStatus.REGISTERED; @@ -355,7 +361,9 @@ contract SwarmRegistryL1Upgradeable is Initializable, Ownable2StepUpgradeable, U } } - uint256 m = (dataLen * 8) / s.fingerprintSize; + // For BITS_8: m = dataLen (each byte is one fingerprint) + // For BITS_16: m = dataLen / 2 (each 2 bytes is one fingerprint) + uint256 m = s.fpSize == FingerprintSize.BITS_8 ? dataLen : dataLen >> 1; if (m == 0) return false; bytes32 h = tagHash; @@ -364,12 +372,13 @@ contract SwarmRegistryL1Upgradeable is Initializable, Ownable2StepUpgradeable, U uint32 h2 = uint32(uint256(h) >> 32) % uint32(m); uint32 h3 = uint32(uint256(h) >> 64) % uint32(m); - uint256 fpMask = (uint256(1) << s.fingerprintSize) - 1; + // fpMask: 0xFF for BITS_8, 0xFFFF for BITS_16 + uint256 fpMask = s.fpSize == FingerprintSize.BITS_8 ? 0xFF : 0xFFFF; uint256 expectedFp = (uint256(h) >> 96) & fpMask; - uint256 f1 = _readFingerprint(pointer, h1, s.fingerprintSize); - uint256 f2 = _readFingerprint(pointer, h2, s.fingerprintSize); - uint256 f3 = _readFingerprint(pointer, h3, s.fingerprintSize); + uint256 f1 = _readFingerprint(pointer, h1, s.fpSize); + uint256 f2 = _readFingerprint(pointer, h2, s.fpSize); + uint256 f3 = _readFingerprint(pointer, h3, s.fpSize); return (f1 ^ f2 ^ f3) == expectedFp; } @@ -396,25 +405,22 @@ contract SwarmRegistryL1Upgradeable is Initializable, Ownable2StepUpgradeable, U delete swarmIndexInUuid[swarmId]; } - function _readFingerprint(address pointer, uint256 index, uint8 bits) internal view returns (uint256) { - uint256 bitOffset = index * bits; - uint256 startByte = bitOffset / 8; - uint256 endByte = (bitOffset + bits - 1) / 8; - - bytes memory chunk = SSTORE2.read(pointer, startByte, endByte + 1); - - uint256 raw; - for (uint256 i = 0; i < chunk.length;) { - raw = (raw << 8) | uint8(chunk[i]); - unchecked { - ++i; - } + /// @dev Reads a fingerprint from the SSTORE2 filter at the given index. + /// Optimized for 8-bit and 16-bit fingerprints (no loops, no variable shifts). + function _readFingerprint(address pointer, uint256 index, FingerprintSize fpSize) + internal + view + returns (uint256) + { + if (fpSize == FingerprintSize.BITS_8) { + // 8-bit: read single byte + bytes memory chunk = SSTORE2.read(pointer, index, index + 1); + return uint256(uint8(chunk[0])); + } else { + // 16-bit: read two consecutive bytes + uint256 byteIndex = index << 1; // index * 2 + bytes memory chunk = SSTORE2.read(pointer, byteIndex, byteIndex + 2); + return (uint256(uint8(chunk[0])) << 8) | uint256(uint8(chunk[1])); } - - uint256 totalBitsRead = chunk.length * 8; - uint256 localStart = bitOffset % 8; - uint256 shiftRight = totalBitsRead - (localStart + bits); - - return (raw >> shiftRight) & ((uint256(1) << bits) - 1); } } diff --git a/src/swarms/SwarmRegistryUniversalUpgradeable.sol b/src/swarms/SwarmRegistryUniversalUpgradeable.sol index a3486282..ff0baa8e 100644 --- a/src/swarms/SwarmRegistryUniversalUpgradeable.sol +++ b/src/swarms/SwarmRegistryUniversalUpgradeable.sol @@ -45,7 +45,6 @@ contract SwarmRegistryUniversalUpgradeable is // ────────────────────────────────────────────── // Errors // ────────────────────────────────────────────── - error InvalidFingerprintSize(); error InvalidFilterSize(); error InvalidUuid(); error NotUuidOwner(); @@ -74,11 +73,17 @@ contract SwarmRegistryUniversalUpgradeable is GENERIC // 0x03 } + /// @notice Fingerprint size for XOR filter (8-bit or 16-bit only for gas efficiency) + enum FingerprintSize { + BITS_8, // 8-bit fingerprints (1 byte each) + BITS_16 // 16-bit fingerprints (2 bytes each) + } + struct Swarm { bytes16 fleetUuid; uint256 providerId; uint32 filterLength; - uint8 fingerprintSize; + FingerprintSize fpSize; TagType tagType; SwarmStatus status; } @@ -86,7 +91,6 @@ contract SwarmRegistryUniversalUpgradeable is // ────────────────────────────────────────────── // Constants // ────────────────────────────────────────────── - uint8 public constant MAX_FINGERPRINT_SIZE = 16; /// @notice Maximum filter size per swarm (24KB) uint32 public constant MAX_FILTER_SIZE = 24576; @@ -182,12 +186,12 @@ contract SwarmRegistryUniversalUpgradeable is // ────────────────────────────────────────────── /// @notice Derives a deterministic swarm ID. - function computeSwarmId(bytes16 fleetUuid, bytes calldata filter, uint8 fingerprintSize, TagType tagType) + function computeSwarmId(bytes16 fleetUuid, bytes calldata filter, FingerprintSize fpSize, TagType tagType) public pure returns (uint256) { - return uint256(keccak256(abi.encode(fleetUuid, filter, fingerprintSize, tagType))); + return uint256(keccak256(abi.encode(fleetUuid, filter, fpSize, tagType))); } // ────────────────────────────────────────────── @@ -195,19 +199,21 @@ contract SwarmRegistryUniversalUpgradeable is // ────────────────────────────────────────────── /// @notice Registers a new swarm. Caller must own the fleet UUID. + /// @param fleetUuid The fleet UUID (must be owned by caller) + /// @param providerId The service provider NFT ID + /// @param filter The XOR filter data + /// @param fpSize Fingerprint size (BITS_8 or BITS_16) + /// @param tagType The tag type for this swarm function registerSwarm( bytes16 fleetUuid, uint256 providerId, bytes calldata filter, - uint8 fingerprintSize, + FingerprintSize fpSize, TagType tagType ) external nonReentrant returns (uint256 swarmId) { if (fleetUuid == bytes16(0)) { revert InvalidUuid(); } - if (fingerprintSize == 0 || fingerprintSize > MAX_FINGERPRINT_SIZE) { - revert InvalidFingerprintSize(); - } if (filter.length == 0) { revert InvalidFilterSize(); } @@ -223,7 +229,7 @@ contract SwarmRegistryUniversalUpgradeable is revert ProviderDoesNotExist(); } - swarmId = computeSwarmId(fleetUuid, filter, fingerprintSize, tagType); + swarmId = computeSwarmId(fleetUuid, filter, fpSize, tagType); if (swarms[swarmId].filterLength != 0) { revert SwarmAlreadyExists(); @@ -233,7 +239,7 @@ contract SwarmRegistryUniversalUpgradeable is s.fleetUuid = fleetUuid; s.providerId = providerId; s.filterLength = uint32(filter.length); - s.fingerprintSize = fingerprintSize; + s.fpSize = fpSize; s.tagType = tagType; s.status = SwarmStatus.REGISTERED; @@ -362,19 +368,22 @@ contract SwarmRegistryUniversalUpgradeable is bytes storage filter = filterData[swarmId]; uint256 dataLen = s.filterLength; - uint256 m = (dataLen * 8) / s.fingerprintSize; + // For BITS_8: m = dataLen (each byte is one fingerprint) + // For BITS_16: m = dataLen / 2 (each 2 bytes is one fingerprint) + uint256 m = s.fpSize == FingerprintSize.BITS_8 ? dataLen : dataLen >> 1; if (m == 0) return false; uint32 h1 = uint32(uint256(tagHash)) % uint32(m); uint32 h2 = uint32(uint256(tagHash) >> 32) % uint32(m); uint32 h3 = uint32(uint256(tagHash) >> 64) % uint32(m); - uint256 fpMask = (uint256(1) << s.fingerprintSize) - 1; + // fpMask: 0xFF for BITS_8, 0xFFFF for BITS_16 + uint256 fpMask = s.fpSize == FingerprintSize.BITS_8 ? 0xFF : 0xFFFF; uint256 expectedFp = (uint256(tagHash) >> 96) & fpMask; - uint256 f1 = _readFingerprint(filter, h1, s.fingerprintSize); - uint256 f2 = _readFingerprint(filter, h2, s.fingerprintSize); - uint256 f3 = _readFingerprint(filter, h3, s.fingerprintSize); + uint256 f1 = _readFingerprint(filter, h1, s.fpSize); + uint256 f2 = _readFingerprint(filter, h2, s.fpSize); + uint256 f3 = _readFingerprint(filter, h3, s.fpSize); return (f1 ^ f2 ^ f3) == expectedFp; } @@ -409,23 +418,20 @@ contract SwarmRegistryUniversalUpgradeable is delete swarmIndexInUuid[swarmId]; } - function _readFingerprint(bytes storage filter, uint256 index, uint8 bits) internal view returns (uint256) { - uint256 bitOffset = index * bits; - uint256 startByte = bitOffset / 8; - uint256 endByte = (bitOffset + bits - 1) / 8; - - uint256 raw; - for (uint256 i = startByte; i <= endByte;) { - raw = (raw << 8) | uint8(filter[i]); - unchecked { - ++i; - } + /// @dev Reads a fingerprint from the filter at the given index. + /// Optimized for 8-bit and 16-bit fingerprints (no loops, no variable shifts). + function _readFingerprint(bytes storage filter, uint256 index, FingerprintSize fpSize) + internal + view + returns (uint256) + { + if (fpSize == FingerprintSize.BITS_8) { + // 8-bit: direct byte access + return uint256(uint8(filter[index])); + } else { + // 16-bit: two consecutive bytes + uint256 byteIndex = index << 1; // index * 2 + return (uint256(uint8(filter[byteIndex])) << 8) | uint256(uint8(filter[byteIndex + 1])); } - - uint256 totalBitsRead = (endByte - startByte + 1) * 8; - uint256 localStart = bitOffset % 8; - uint256 shiftRight = totalBitsRead - (localStart + bits); - - return (raw >> shiftRight) & ((uint256(1) << bits) - 1); } } diff --git a/test/SwarmRegistryL1.t.sol b/test/SwarmRegistryL1.t.sol index 7733607a..d8ae263e 100644 --- a/test/SwarmRegistryL1.t.sol +++ b/test/SwarmRegistryL1.t.sol @@ -33,6 +33,10 @@ contract SwarmRegistryL1Test is Test { uint16 constant US = 840; uint16 constant ADMIN_CA = 6; // California + // Alias for FingerprintSize enum + SwarmRegistryL1Upgradeable.FingerprintSize constant BITS_8 = SwarmRegistryL1Upgradeable.FingerprintSize.BITS_8; + SwarmRegistryL1Upgradeable.FingerprintSize constant BITS_16 = SwarmRegistryL1Upgradeable.FingerprintSize.BITS_16; + event SwarmRegistered(uint256 indexed swarmId, bytes16 indexed fleetUuid, uint256 indexed providerId, address owner); event SwarmStatusChanged(uint256 indexed swarmId, SwarmRegistryL1Upgradeable.SwarmStatus status); event SwarmProviderUpdated(uint256 indexed swarmId, uint256 indexed oldProvider, uint256 indexed newProvider); @@ -95,7 +99,7 @@ contract SwarmRegistryL1Test is Test { uint256 fleetId, uint256 providerId, bytes memory filter, - uint8 fpSize, + SwarmRegistryL1Upgradeable.FingerprintSize fpSize, SwarmRegistryL1Upgradeable.TagType tagType ) internal returns (uint256) { bytes16 fleetUuid = _getFleetUuid(fleetId); @@ -103,22 +107,22 @@ contract SwarmRegistryL1Test is Test { return swarmRegistry.registerSwarm(fleetUuid, providerId, filter, fpSize, tagType); } - function getExpectedValues(bytes memory tagId, uint256 m, uint8 fpSize) - public - pure - returns (uint32 h1, uint32 h2, uint32 h3, uint256 fp) - { + /// @dev Get expected hash indices and fingerprint for XOR filter verification + function getExpectedValues( + bytes memory tagId, + uint256 m, + SwarmRegistryL1Upgradeable.FingerprintSize fpSize + ) public pure returns (uint32 h1, uint32 h2, uint32 h3, uint256 fp) { bytes32 h = keccak256(tagId); h1 = uint32(uint256(h)) % uint32(m); h2 = uint32(uint256(h) >> 32) % uint32(m); h3 = uint32(uint256(h) >> 64) % uint32(m); - uint256 fpMask = (uint256(1) << fpSize) - 1; + uint256 fpMask = fpSize == SwarmRegistryL1Upgradeable.FingerprintSize.BITS_8 ? 0xFF : 0xFFFF; fp = (uint256(h) >> 96) & fpMask; } function _write16Bit(bytes memory data, uint256 slotIndex, uint16 value) internal pure { - uint256 bitOffset = slotIndex * 16; - uint256 byteOffset = bitOffset / 8; + uint256 byteOffset = slotIndex * 2; data[byteOffset] = bytes1(uint8(value >> 8)); data[byteOffset + 1] = bytes1(uint8(value)); } @@ -172,11 +176,13 @@ contract SwarmRegistryL1Test is Test { uint256 providerId = _registerProvider(providerOwner, "https://api.example.com"); uint256 swarmId = _registerSwarm( - fleetOwner, fleetId, providerId, new bytes(100), 16, SwarmRegistryL1Upgradeable.TagType.IBEACON_INCLUDES_MAC + fleetOwner, fleetId, providerId, new bytes(100), BITS_16, SwarmRegistryL1Upgradeable.TagType.IBEACON_INCLUDES_MAC ); - // Swarm ID is deterministic hash of (fleetUuid, filter, fingerprintSize, tagType) - uint256 expectedId = swarmRegistry.computeSwarmId(_getFleetUuid(fleetId), new bytes(100), 16, SwarmRegistryL1Upgradeable.TagType.IBEACON_INCLUDES_MAC); + // Swarm ID is deterministic hash of (fleetUuid, filter, fpSize, tagType) + uint256 expectedId = swarmRegistry.computeSwarmId( + _getFleetUuid(fleetId), new bytes(100), BITS_16, SwarmRegistryL1Upgradeable.TagType.IBEACON_INCLUDES_MAC + ); assertEq(swarmId, expectedId); } @@ -185,13 +191,13 @@ contract SwarmRegistryL1Test is Test { uint256 providerId = _registerProvider(providerOwner, "url1"); uint256 swarmId = - _registerSwarm(fleetOwner, fleetId, providerId, new bytes(50), 8, SwarmRegistryL1Upgradeable.TagType.VENDOR_ID); + _registerSwarm(fleetOwner, fleetId, providerId, new bytes(50), BITS_8, SwarmRegistryL1Upgradeable.TagType.VENDOR_ID); ( bytes16 storedFleetUuid, uint256 storedProviderId, address filterPointer, - uint8 storedFpSize, + SwarmRegistryL1Upgradeable.FingerprintSize storedFpSize, SwarmRegistryL1Upgradeable.TagType storedTagType, SwarmRegistryL1Upgradeable.SwarmStatus storedStatus ) = swarmRegistry.swarms(swarmId); @@ -199,7 +205,7 @@ contract SwarmRegistryL1Test is Test { assertEq(storedFleetUuid, _getFleetUuid(fleetId)); assertEq(storedProviderId, providerId); assertTrue(filterPointer != address(0)); - assertEq(storedFpSize, 8); + assertEq(uint8(storedFpSize), uint8(BITS_8)); assertEq(uint8(storedTagType), uint8(SwarmRegistryL1Upgradeable.TagType.VENDOR_ID)); assertEq(uint8(storedStatus), uint8(SwarmRegistryL1Upgradeable.SwarmStatus.REGISTERED)); } @@ -210,9 +216,11 @@ contract SwarmRegistryL1Test is Test { bytes memory filter = new bytes(32); - uint256 expectedId = swarmRegistry.computeSwarmId(_getFleetUuid(fleetId), filter, 8, SwarmRegistryL1Upgradeable.TagType.GENERIC); + uint256 expectedId = + swarmRegistry.computeSwarmId(_getFleetUuid(fleetId), filter, BITS_8, SwarmRegistryL1Upgradeable.TagType.GENERIC); - uint256 swarmId = _registerSwarm(fleetOwner, fleetId, providerId, filter, 8, SwarmRegistryL1Upgradeable.TagType.GENERIC); + uint256 swarmId = + _registerSwarm(fleetOwner, fleetId, providerId, filter, BITS_8, SwarmRegistryL1Upgradeable.TagType.GENERIC); assertEq(swarmId, expectedId); } @@ -220,11 +228,13 @@ contract SwarmRegistryL1Test is Test { uint256 fleetId = _registerFleet(fleetOwner, "f1"); uint256 providerId = _registerProvider(providerOwner, "url1"); - _registerSwarm(fleetOwner, fleetId, providerId, new bytes(32), 8, SwarmRegistryL1Upgradeable.TagType.GENERIC); + _registerSwarm(fleetOwner, fleetId, providerId, new bytes(32), BITS_8, SwarmRegistryL1Upgradeable.TagType.GENERIC); vm.prank(fleetOwner); vm.expectRevert(SwarmRegistryL1Upgradeable.SwarmAlreadyExists.selector); - swarmRegistry.registerSwarm(_getFleetUuid(fleetId), providerId, new bytes(32), 8, SwarmRegistryL1Upgradeable.TagType.GENERIC); + swarmRegistry.registerSwarm( + _getFleetUuid(fleetId), providerId, new bytes(32), BITS_8, SwarmRegistryL1Upgradeable.TagType.GENERIC + ); } function test_registerSwarm_emitsSwarmRegistered() public { @@ -232,12 +242,14 @@ contract SwarmRegistryL1Test is Test { uint256 providerId = _registerProvider(providerOwner, "url1"); bytes memory filter = new bytes(50); - uint256 expectedId = swarmRegistry.computeSwarmId(_getFleetUuid(fleetId), filter, 16, SwarmRegistryL1Upgradeable.TagType.GENERIC); + uint256 expectedId = swarmRegistry.computeSwarmId( + _getFleetUuid(fleetId), filter, BITS_16, SwarmRegistryL1Upgradeable.TagType.GENERIC + ); vm.expectEmit(true, true, true, true); emit SwarmRegistered(expectedId, _getFleetUuid(fleetId), providerId, fleetOwner); - _registerSwarm(fleetOwner, fleetId, providerId, filter, 16, SwarmRegistryL1Upgradeable.TagType.GENERIC); + _registerSwarm(fleetOwner, fleetId, providerId, filter, BITS_16, SwarmRegistryL1Upgradeable.TagType.GENERIC); } function test_registerSwarm_linksUuidSwarms() public { @@ -252,9 +264,9 @@ contract SwarmRegistryL1Test is Test { filter2[0] = 0x02; uint256 swarmId1 = - _registerSwarm(fleetOwner, fleetId, providerId1, filter1, 8, SwarmRegistryL1Upgradeable.TagType.GENERIC); + _registerSwarm(fleetOwner, fleetId, providerId1, filter1, BITS_8, SwarmRegistryL1Upgradeable.TagType.GENERIC); uint256 swarmId2 = - _registerSwarm(fleetOwner, fleetId, providerId2, filter2, 8, SwarmRegistryL1Upgradeable.TagType.GENERIC); + _registerSwarm(fleetOwner, fleetId, providerId2, filter2, BITS_8, SwarmRegistryL1Upgradeable.TagType.GENERIC); assertEq(swarmRegistry.uuidSwarms(_getFleetUuid(fleetId), 0), swarmId1); assertEq(swarmRegistry.uuidSwarms(_getFleetUuid(fleetId), 1), swarmId2); @@ -268,14 +280,17 @@ contract SwarmRegistryL1Test is Test { uint256 providerId = _registerProvider(providerOwner, "url"); uint256 s1 = _registerSwarm( - fleetOwner, fleetId1, providerId, new bytes(32), 8, SwarmRegistryL1Upgradeable.TagType.IBEACON_PAYLOAD_ONLY + fleetOwner, fleetId1, providerId, new bytes(32), BITS_8, SwarmRegistryL1Upgradeable.TagType.IBEACON_PAYLOAD_ONLY ); uint256 s2 = _registerSwarm( - fleetOwner, fleetId2, providerId, new bytes(32), 8, SwarmRegistryL1Upgradeable.TagType.IBEACON_INCLUDES_MAC + fleetOwner, fleetId2, providerId, new bytes(32), BITS_8, SwarmRegistryL1Upgradeable.TagType.IBEACON_INCLUDES_MAC + ); + uint256 s3 = _registerSwarm( + fleetOwner, fleetId3, providerId, new bytes(32), BITS_8, SwarmRegistryL1Upgradeable.TagType.VENDOR_ID + ); + uint256 s4 = _registerSwarm( + fleetOwner, fleetId4, providerId, new bytes(32), BITS_8, SwarmRegistryL1Upgradeable.TagType.GENERIC ); - uint256 s3 = - _registerSwarm(fleetOwner, fleetId3, providerId, new bytes(32), 8, SwarmRegistryL1Upgradeable.TagType.VENDOR_ID); - uint256 s4 = _registerSwarm(fleetOwner, fleetId4, providerId, new bytes(32), 8, SwarmRegistryL1Upgradeable.TagType.GENERIC); (,,,, SwarmRegistryL1Upgradeable.TagType t1,) = swarmRegistry.swarms(s1); (,,,, SwarmRegistryL1Upgradeable.TagType t2,) = swarmRegistry.swarms(s2); @@ -288,6 +303,25 @@ contract SwarmRegistryL1Test is Test { assertEq(uint8(t4), uint8(SwarmRegistryL1Upgradeable.TagType.GENERIC)); } + function test_registerSwarm_bothFingerprintSizes() public { + uint256 fleetId1 = _registerFleet(fleetOwner, "f1"); + uint256 fleetId2 = _registerFleet(fleetOwner, "f2"); + uint256 providerId = _registerProvider(providerOwner, "url"); + + uint256 s1 = _registerSwarm( + fleetOwner, fleetId1, providerId, new bytes(32), BITS_8, SwarmRegistryL1Upgradeable.TagType.GENERIC + ); + uint256 s2 = _registerSwarm( + fleetOwner, fleetId2, providerId, new bytes(64), BITS_16, SwarmRegistryL1Upgradeable.TagType.GENERIC + ); + + (,,, SwarmRegistryL1Upgradeable.FingerprintSize fp1,,) = swarmRegistry.swarms(s1); + (,,, SwarmRegistryL1Upgradeable.FingerprintSize fp2,,) = swarmRegistry.swarms(s2); + + assertEq(uint8(fp1), uint8(BITS_8)); + assertEq(uint8(fp2), uint8(BITS_16)); + } + // ============================== // registerSwarm — reverts // ============================== @@ -297,7 +331,9 @@ contract SwarmRegistryL1Test is Test { vm.prank(caller); vm.expectRevert(SwarmRegistryL1Upgradeable.NotUuidOwner.selector); - swarmRegistry.registerSwarm(_getFleetUuid(fleetId), 1, new bytes(10), 16, SwarmRegistryL1Upgradeable.TagType.GENERIC); + swarmRegistry.registerSwarm( + _getFleetUuid(fleetId), 1, new bytes(10), BITS_16, SwarmRegistryL1Upgradeable.TagType.GENERIC + ); } function test_RevertIf_registerSwarm_zeroUuid() public { @@ -305,7 +341,9 @@ contract SwarmRegistryL1Test is Test { vm.prank(fleetOwner); vm.expectRevert(SwarmRegistryL1Upgradeable.InvalidUuid.selector); - swarmRegistry.registerSwarm(bytes16(0), providerId, new bytes(32), 8, SwarmRegistryL1Upgradeable.TagType.GENERIC); + swarmRegistry.registerSwarm( + bytes16(0), providerId, new bytes(32), BITS_8, SwarmRegistryL1Upgradeable.TagType.GENERIC + ); } function test_RevertIf_registerSwarm_providerDoesNotExist() public { @@ -314,25 +352,9 @@ contract SwarmRegistryL1Test is Test { vm.prank(fleetOwner); vm.expectRevert(SwarmRegistryL1Upgradeable.ProviderDoesNotExist.selector); - swarmRegistry.registerSwarm(_getFleetUuid(fleetId), nonExistentProvider, new bytes(32), 8, SwarmRegistryL1Upgradeable.TagType.GENERIC); - } - - function test_RevertIf_registerSwarm_fingerprintSizeZero() public { - uint256 fleetId = _registerFleet(fleetOwner, "f1"); - uint256 providerId = _registerProvider(providerOwner, "url1"); - - vm.prank(fleetOwner); - vm.expectRevert(SwarmRegistryL1Upgradeable.InvalidFingerprintSize.selector); - swarmRegistry.registerSwarm(_getFleetUuid(fleetId), providerId, new bytes(32), 0, SwarmRegistryL1Upgradeable.TagType.GENERIC); - } - - function test_RevertIf_registerSwarm_fingerprintSizeExceedsMax() public { - uint256 fleetId = _registerFleet(fleetOwner, "f1"); - uint256 providerId = _registerProvider(providerOwner, "url1"); - - vm.prank(fleetOwner); - vm.expectRevert(SwarmRegistryL1Upgradeable.InvalidFingerprintSize.selector); - swarmRegistry.registerSwarm(_getFleetUuid(fleetId), providerId, new bytes(32), 17, SwarmRegistryL1Upgradeable.TagType.GENERIC); + swarmRegistry.registerSwarm( + _getFleetUuid(fleetId), nonExistentProvider, new bytes(32), BITS_8, SwarmRegistryL1Upgradeable.TagType.GENERIC + ); } function test_RevertIf_registerSwarm_emptyFilter() public { @@ -341,7 +363,9 @@ contract SwarmRegistryL1Test is Test { vm.prank(fleetOwner); vm.expectRevert(SwarmRegistryL1Upgradeable.InvalidFilterSize.selector); - swarmRegistry.registerSwarm(_getFleetUuid(fleetId), providerId, new bytes(0), 8, SwarmRegistryL1Upgradeable.TagType.GENERIC); + swarmRegistry.registerSwarm( + _getFleetUuid(fleetId), providerId, new bytes(0), BITS_8, SwarmRegistryL1Upgradeable.TagType.GENERIC + ); } function test_RevertIf_registerSwarm_filterTooLarge() public { @@ -350,26 +374,30 @@ contract SwarmRegistryL1Test is Test { vm.prank(fleetOwner); vm.expectRevert(SwarmRegistryL1Upgradeable.InvalidFilterSize.selector); - swarmRegistry.registerSwarm(_getFleetUuid(fleetId), providerId, new bytes(24577), 8, SwarmRegistryL1Upgradeable.TagType.GENERIC); + swarmRegistry.registerSwarm( + _getFleetUuid(fleetId), providerId, new bytes(24577), BITS_8, SwarmRegistryL1Upgradeable.TagType.GENERIC + ); } - function test_registerSwarm_maxFingerprintSize() public { + function test_registerSwarm_maxFilterSize() public { uint256 fleetId = _registerFleet(fleetOwner, "f1"); uint256 providerId = _registerProvider(providerOwner, "url1"); - // fpSize=16 is MAX_FINGERPRINT_SIZE, should succeed - uint256 swarmId = - _registerSwarm(fleetOwner, fleetId, providerId, new bytes(100), 16, SwarmRegistryL1Upgradeable.TagType.GENERIC); + // Exactly 24576 bytes should succeed + uint256 swarmId = _registerSwarm( + fleetOwner, fleetId, providerId, new bytes(24576), BITS_8, SwarmRegistryL1Upgradeable.TagType.GENERIC + ); assertTrue(swarmId != 0); } - function test_registerSwarm_maxFilterSize() public { + function test_registerSwarm_minFilterSize() public { uint256 fleetId = _registerFleet(fleetOwner, "f1"); uint256 providerId = _registerProvider(providerOwner, "url1"); - // Exactly 24576 bytes should succeed - uint256 swarmId = - _registerSwarm(fleetOwner, fleetId, providerId, new bytes(24576), 8, SwarmRegistryL1Upgradeable.TagType.GENERIC); + // 1 byte filter + uint256 swarmId = _registerSwarm( + fleetOwner, fleetId, providerId, new bytes(1), BITS_8, SwarmRegistryL1Upgradeable.TagType.GENERIC + ); assertTrue(swarmId != 0); } @@ -381,7 +409,7 @@ contract SwarmRegistryL1Test is Test { uint256 fleetId = _registerFleet(fleetOwner, "f1"); uint256 providerId = _registerProvider(providerOwner, "url1"); uint256 swarmId = - _registerSwarm(fleetOwner, fleetId, providerId, new bytes(50), 8, SwarmRegistryL1Upgradeable.TagType.GENERIC); + _registerSwarm(fleetOwner, fleetId, providerId, new bytes(50), BITS_8, SwarmRegistryL1Upgradeable.TagType.GENERIC); vm.expectEmit(true, true, true, true); emit SwarmStatusChanged(swarmId, SwarmRegistryL1Upgradeable.SwarmStatus.ACCEPTED); @@ -397,7 +425,7 @@ contract SwarmRegistryL1Test is Test { uint256 fleetId = _registerFleet(fleetOwner, "f1"); uint256 providerId = _registerProvider(providerOwner, "url1"); uint256 swarmId = - _registerSwarm(fleetOwner, fleetId, providerId, new bytes(50), 8, SwarmRegistryL1Upgradeable.TagType.GENERIC); + _registerSwarm(fleetOwner, fleetId, providerId, new bytes(50), BITS_8, SwarmRegistryL1Upgradeable.TagType.GENERIC); vm.expectEmit(true, true, true, true); emit SwarmStatusChanged(swarmId, SwarmRegistryL1Upgradeable.SwarmStatus.REJECTED); @@ -413,7 +441,7 @@ contract SwarmRegistryL1Test is Test { uint256 fleetId = _registerFleet(fleetOwner, "f1"); uint256 providerId = _registerProvider(providerOwner, "url1"); uint256 swarmId = - _registerSwarm(fleetOwner, fleetId, providerId, new bytes(50), 8, SwarmRegistryL1Upgradeable.TagType.GENERIC); + _registerSwarm(fleetOwner, fleetId, providerId, new bytes(50), BITS_8, SwarmRegistryL1Upgradeable.TagType.GENERIC); vm.prank(caller); vm.expectRevert(SwarmRegistryL1Upgradeable.NotProviderOwner.selector); @@ -424,7 +452,7 @@ contract SwarmRegistryL1Test is Test { uint256 fleetId = _registerFleet(fleetOwner, "f1"); uint256 providerId = _registerProvider(providerOwner, "url1"); uint256 swarmId = - _registerSwarm(fleetOwner, fleetId, providerId, new bytes(50), 8, SwarmRegistryL1Upgradeable.TagType.GENERIC); + _registerSwarm(fleetOwner, fleetId, providerId, new bytes(50), BITS_8, SwarmRegistryL1Upgradeable.TagType.GENERIC); vm.prank(fleetOwner); // fleet owner != provider owner vm.expectRevert(SwarmRegistryL1Upgradeable.NotProviderOwner.selector); @@ -435,7 +463,7 @@ contract SwarmRegistryL1Test is Test { uint256 fleetId = _registerFleet(fleetOwner, "f1"); uint256 providerId = _registerProvider(providerOwner, "url1"); uint256 swarmId = - _registerSwarm(fleetOwner, fleetId, providerId, new bytes(50), 8, SwarmRegistryL1Upgradeable.TagType.GENERIC); + _registerSwarm(fleetOwner, fleetId, providerId, new bytes(50), BITS_8, SwarmRegistryL1Upgradeable.TagType.GENERIC); vm.prank(providerOwner); swarmRegistry.rejectSwarm(swarmId); @@ -448,6 +476,22 @@ contract SwarmRegistryL1Test is Test { assertEq(uint8(status), uint8(SwarmRegistryL1Upgradeable.SwarmStatus.ACCEPTED)); } + function test_rejectSwarm_afterAccept() public { + uint256 fleetId = _registerFleet(fleetOwner, "f1"); + uint256 providerId = _registerProvider(providerOwner, "url1"); + uint256 swarmId = + _registerSwarm(fleetOwner, fleetId, providerId, new bytes(50), BITS_8, SwarmRegistryL1Upgradeable.TagType.GENERIC); + + vm.prank(providerOwner); + swarmRegistry.acceptSwarm(swarmId); + + vm.prank(providerOwner); + swarmRegistry.rejectSwarm(swarmId); + + (,,,,, SwarmRegistryL1Upgradeable.SwarmStatus status) = swarmRegistry.swarms(swarmId); + assertEq(uint8(status), uint8(SwarmRegistryL1Upgradeable.SwarmStatus.REJECTED)); + } + // ============================== // checkMembership — XOR logic // ============================== @@ -457,11 +501,10 @@ contract SwarmRegistryL1Test is Test { uint256 providerId = _registerProvider(providerOwner, "u1"); bytes memory tagId = hex"1122334455"; - uint8 fpSize = 16; uint256 dataLen = 100; - uint256 m = (dataLen * 8) / fpSize; // 50 slots + uint256 m = dataLen / 2; // 50 slots for 16-bit - (uint32 h1, uint32 h2, uint32 h3, uint256 expectedFp) = getExpectedValues(tagId, m, fpSize); + (uint32 h1, uint32 h2, uint32 h3, uint256 expectedFp) = getExpectedValues(tagId, m, BITS_16); // Skip if collision (extremely unlikely with 50 slots) if (h1 == h2 || h1 == h3 || h2 == h3) { @@ -472,7 +515,7 @@ contract SwarmRegistryL1Test is Test { _write16Bit(filter, h1, uint16(expectedFp)); uint256 swarmId = - _registerSwarm(fleetOwner, fleetId, providerId, filter, fpSize, SwarmRegistryL1Upgradeable.TagType.GENERIC); + _registerSwarm(fleetOwner, fleetId, providerId, filter, BITS_16, SwarmRegistryL1Upgradeable.TagType.GENERIC); // Positive check assertTrue(swarmRegistry.checkMembership(swarmId, keccak256(tagId)), "Valid tag should pass"); @@ -486,16 +529,10 @@ contract SwarmRegistryL1Test is Test { uint256 providerId = _registerProvider(providerOwner, "u1"); bytes memory tagId = hex"AABBCCDD"; - uint8 fpSize = 8; - // SSTORE2 prepends 0x00 STOP byte, so on-chain: - // extcodesize = rawLen + 1, dataLen = extcodesize - 1 = rawLen - // But SSTORE2.read offsets reads by +1 (skips STOP byte), so - // the data bytes read on-chain map 1:1 to the bytes we pass in. - // Therefore m = (rawLen * 8) / fpSize and slot indices match directly. uint256 rawLen = 80; - uint256 m = (rawLen * 8) / fpSize; // 80 + uint256 m = rawLen; // 80 slots for 8-bit - (uint32 h1, uint32 h2, uint32 h3, uint256 expectedFp) = getExpectedValues(tagId, m, fpSize); + (uint32 h1, uint32 h2, uint32 h3, uint256 expectedFp) = getExpectedValues(tagId, m, BITS_8); if (h1 == h2 || h1 == h3 || h2 == h3) { return; @@ -505,7 +542,7 @@ contract SwarmRegistryL1Test is Test { _write8Bit(filter, h1, uint8(expectedFp)); uint256 swarmId = - _registerSwarm(fleetOwner, fleetId, providerId, filter, fpSize, SwarmRegistryL1Upgradeable.TagType.GENERIC); + _registerSwarm(fleetOwner, fleetId, providerId, filter, BITS_8, SwarmRegistryL1Upgradeable.TagType.GENERIC); assertTrue(swarmRegistry.checkMembership(swarmId, keccak256(tagId)), "8-bit valid tag should pass"); assertFalse(swarmRegistry.checkMembership(swarmId, keccak256(hex"FFFFFF")), "8-bit invalid tag should fail"); @@ -521,9 +558,8 @@ contract SwarmRegistryL1Test is Test { uint256 providerId = _registerProvider(providerOwner, "u1"); // All-zero filter: f1^f2^f3 = 0^0^0 = 0 - // Only matches if expectedFp is also 0 bytes memory filter = new bytes(64); - uint256 swarmId = _registerSwarm(fleetOwner, fleetId, providerId, filter, 16, SwarmRegistryL1Upgradeable.TagType.GENERIC); + uint256 swarmId = _registerSwarm(fleetOwner, fleetId, providerId, filter, BITS_16, SwarmRegistryL1Upgradeable.TagType.GENERIC); // Some tags will match (those with expectedFp=0), most won't // The point is it doesn't revert @@ -535,9 +571,9 @@ contract SwarmRegistryL1Test is Test { uint256 fleetId = _registerFleet(fleetOwner, "f1"); uint256 providerId = _registerProvider(providerOwner, "u1"); - // 1-byte filter with 16-bit fingerprint: m = (1*8)/16 = 0, returns false immediately + // 1-byte filter with 16-bit fingerprint: m = 1/2 = 0, returns false immediately bytes memory filter = new bytes(1); - uint256 swarmId = _registerSwarm(fleetOwner, fleetId, providerId, filter, 16, SwarmRegistryL1Upgradeable.TagType.GENERIC); + uint256 swarmId = _registerSwarm(fleetOwner, fleetId, providerId, filter, BITS_16, SwarmRegistryL1Upgradeable.TagType.GENERIC); // Should return false (not revert) because m == 0 assertFalse(swarmRegistry.checkMembership(swarmId, keccak256("test")), "m=0 should return false"); @@ -553,11 +589,11 @@ contract SwarmRegistryL1Test is Test { uint256 providerId2 = _registerProvider(providerOwner, "url2"); uint256 providerId3 = _registerProvider(providerOwner, "url3"); - uint256 s1 = _registerSwarm(fleetOwner, fleetId, providerId1, new bytes(32), 8, SwarmRegistryL1Upgradeable.TagType.GENERIC); + uint256 s1 = _registerSwarm(fleetOwner, fleetId, providerId1, new bytes(32), BITS_8, SwarmRegistryL1Upgradeable.TagType.GENERIC); uint256 s2 = - _registerSwarm(fleetOwner, fleetId, providerId2, new bytes(64), 16, SwarmRegistryL1Upgradeable.TagType.VENDOR_ID); + _registerSwarm(fleetOwner, fleetId, providerId2, new bytes(64), BITS_16, SwarmRegistryL1Upgradeable.TagType.VENDOR_ID); uint256 s3 = _registerSwarm( - fleetOwner, fleetId, providerId3, new bytes(50), 12, SwarmRegistryL1Upgradeable.TagType.IBEACON_PAYLOAD_ONLY + fleetOwner, fleetId, providerId3, new bytes(50), BITS_8, SwarmRegistryL1Upgradeable.TagType.IBEACON_PAYLOAD_ONLY ); // IDs are distinct hashes @@ -568,40 +604,22 @@ contract SwarmRegistryL1Test is Test { assertEq(swarmRegistry.uuidSwarms(_getFleetUuid(fleetId), 2), s3); } - // ============================== - // Constants - // ============================== - - function test_constants() public view { - assertEq(swarmRegistry.MAX_FINGERPRINT_SIZE(), 16); - } - // ============================== // Fuzz // ============================== - function testFuzz_registerSwarm_validFingerprintSizes(uint8 fpSize) public { - fpSize = uint8(bound(fpSize, 1, 16)); + function testFuzz_registerSwarm_filterSizeRange(uint256 size) public { + size = bound(size, 1, 24576); - uint256 fleetId = _registerFleet(fleetOwner, abi.encodePacked("fleet-", fpSize)); - uint256 providerId = _registerProvider(providerOwner, string(abi.encodePacked("url-", fpSize))); - - uint256 swarmId = - _registerSwarm(fleetOwner, fleetId, providerId, new bytes(64), fpSize, SwarmRegistryL1Upgradeable.TagType.GENERIC); + uint256 fleetId = _registerFleet(fleetOwner, abi.encodePacked("f-", size)); + uint256 providerId = _registerProvider(providerOwner, string(abi.encodePacked("url-", size))); - (,,, uint8 storedFp,,) = swarmRegistry.swarms(swarmId); - assertEq(storedFp, fpSize); - } - - function testFuzz_registerSwarm_invalidFingerprintSizes(uint8 fpSize) public { - vm.assume(fpSize == 0 || fpSize > 16); - - uint256 fleetId = _registerFleet(fleetOwner, "f1"); - uint256 providerId = _registerProvider(providerOwner, "url1"); + uint256 swarmId = _registerSwarm( + fleetOwner, fleetId, providerId, new bytes(size), BITS_8, SwarmRegistryL1Upgradeable.TagType.GENERIC + ); - vm.prank(fleetOwner); - vm.expectRevert(SwarmRegistryL1Upgradeable.InvalidFingerprintSize.selector); - swarmRegistry.registerSwarm(_getFleetUuid(fleetId), providerId, new bytes(32), fpSize, SwarmRegistryL1Upgradeable.TagType.GENERIC); + (,, address pointer,,,) = swarmRegistry.swarms(swarmId); + assertTrue(pointer != address(0)); } // ============================== @@ -614,7 +632,7 @@ contract SwarmRegistryL1Test is Test { uint256 providerId2 = _registerProvider(providerOwner, "url2"); uint256 swarmId = - _registerSwarm(fleetOwner, fleetId, providerId1, new bytes(50), 8, SwarmRegistryL1Upgradeable.TagType.GENERIC); + _registerSwarm(fleetOwner, fleetId, providerId1, new bytes(50), BITS_8, SwarmRegistryL1Upgradeable.TagType.GENERIC); // Provider accepts vm.prank(providerOwner); @@ -647,7 +665,7 @@ contract SwarmRegistryL1Test is Test { uint256 providerId2 = _registerProvider(providerOwner, "url2"); uint256 swarmId = - _registerSwarm(fleetOwner, fleetId, providerId1, new bytes(50), 8, SwarmRegistryL1Upgradeable.TagType.GENERIC); + _registerSwarm(fleetOwner, fleetId, providerId1, new bytes(50), BITS_8, SwarmRegistryL1Upgradeable.TagType.GENERIC); vm.prank(caller); vm.expectRevert(SwarmRegistryL1Upgradeable.NotUuidOwner.selector); @@ -659,7 +677,7 @@ contract SwarmRegistryL1Test is Test { uint256 providerId = _registerProvider(providerOwner, "url1"); uint256 swarmId = - _registerSwarm(fleetOwner, fleetId, providerId, new bytes(50), 8, SwarmRegistryL1Upgradeable.TagType.GENERIC); + _registerSwarm(fleetOwner, fleetId, providerId, new bytes(50), BITS_8, SwarmRegistryL1Upgradeable.TagType.GENERIC); vm.prank(fleetOwner); vm.expectRevert(SwarmRegistryL1Upgradeable.ProviderDoesNotExist.selector); @@ -674,7 +692,7 @@ contract SwarmRegistryL1Test is Test { uint256 fleetId = _registerFleet(fleetOwner, "f1"); uint256 providerId = _registerProvider(providerOwner, "url1"); uint256 swarmId = - _registerSwarm(fleetOwner, fleetId, providerId, new bytes(50), 8, SwarmRegistryL1Upgradeable.TagType.GENERIC); + _registerSwarm(fleetOwner, fleetId, providerId, new bytes(50), BITS_8, SwarmRegistryL1Upgradeable.TagType.GENERIC); vm.expectEmit(true, true, true, true); emit SwarmDeleted(swarmId, _getFleetUuid(fleetId), fleetOwner); @@ -699,9 +717,9 @@ contract SwarmRegistryL1Test is Test { filter2[0] = 0x02; uint256 swarm1 = - _registerSwarm(fleetOwner, fleetId, providerId1, filter1, 8, SwarmRegistryL1Upgradeable.TagType.GENERIC); + _registerSwarm(fleetOwner, fleetId, providerId1, filter1, BITS_8, SwarmRegistryL1Upgradeable.TagType.GENERIC); uint256 swarm2 = - _registerSwarm(fleetOwner, fleetId, providerId2, filter2, 8, SwarmRegistryL1Upgradeable.TagType.GENERIC); + _registerSwarm(fleetOwner, fleetId, providerId2, filter2, BITS_8, SwarmRegistryL1Upgradeable.TagType.GENERIC); // Delete first swarm vm.prank(fleetOwner); @@ -728,11 +746,11 @@ contract SwarmRegistryL1Test is Test { filter3[0] = 0x03; uint256 swarm1 = - _registerSwarm(fleetOwner, fleetId, providerId1, filter1, 8, SwarmRegistryL1Upgradeable.TagType.GENERIC); + _registerSwarm(fleetOwner, fleetId, providerId1, filter1, BITS_8, SwarmRegistryL1Upgradeable.TagType.GENERIC); uint256 swarm2 = - _registerSwarm(fleetOwner, fleetId, providerId2, filter2, 8, SwarmRegistryL1Upgradeable.TagType.GENERIC); + _registerSwarm(fleetOwner, fleetId, providerId2, filter2, BITS_8, SwarmRegistryL1Upgradeable.TagType.GENERIC); uint256 swarm3 = - _registerSwarm(fleetOwner, fleetId, providerId3, filter3, 8, SwarmRegistryL1Upgradeable.TagType.GENERIC); + _registerSwarm(fleetOwner, fleetId, providerId3, filter3, BITS_8, SwarmRegistryL1Upgradeable.TagType.GENERIC); // Delete middle swarm vm.prank(fleetOwner); @@ -755,7 +773,7 @@ contract SwarmRegistryL1Test is Test { uint256 fleetId = _registerFleet(fleetOwner, "f1"); uint256 providerId = _registerProvider(providerOwner, "url1"); uint256 swarmId = - _registerSwarm(fleetOwner, fleetId, providerId, new bytes(50), 8, SwarmRegistryL1Upgradeable.TagType.GENERIC); + _registerSwarm(fleetOwner, fleetId, providerId, new bytes(50), BITS_8, SwarmRegistryL1Upgradeable.TagType.GENERIC); vm.prank(caller); vm.expectRevert(SwarmRegistryL1Upgradeable.NotUuidOwner.selector); @@ -767,7 +785,7 @@ contract SwarmRegistryL1Test is Test { uint256 providerId1 = _registerProvider(providerOwner, "url1"); uint256 providerId2 = _registerProvider(providerOwner, "url2"); uint256 swarmId = - _registerSwarm(fleetOwner, fleetId, providerId1, new bytes(50), 8, SwarmRegistryL1Upgradeable.TagType.GENERIC); + _registerSwarm(fleetOwner, fleetId, providerId1, new bytes(50), BITS_8, SwarmRegistryL1Upgradeable.TagType.GENERIC); // Update provider then delete vm.prank(fleetOwner); @@ -794,9 +812,9 @@ contract SwarmRegistryL1Test is Test { bytes memory filter3 = new bytes(50); filter3[0] = 0x03; - uint256 s1 = _registerSwarm(fleetOwner, fleetId, p1, filter1, 8, SwarmRegistryL1Upgradeable.TagType.GENERIC); - uint256 s2 = _registerSwarm(fleetOwner, fleetId, p2, filter2, 8, SwarmRegistryL1Upgradeable.TagType.GENERIC); - uint256 s3 = _registerSwarm(fleetOwner, fleetId, p3, filter3, 8, SwarmRegistryL1Upgradeable.TagType.GENERIC); + uint256 s1 = _registerSwarm(fleetOwner, fleetId, p1, filter1, BITS_8, SwarmRegistryL1Upgradeable.TagType.GENERIC); + uint256 s2 = _registerSwarm(fleetOwner, fleetId, p2, filter2, BITS_8, SwarmRegistryL1Upgradeable.TagType.GENERIC); + uint256 s3 = _registerSwarm(fleetOwner, fleetId, p3, filter3, BITS_8, SwarmRegistryL1Upgradeable.TagType.GENERIC); // Verify initial indices assertEq(swarmRegistry.swarmIndexInUuid(s1), 0); @@ -820,7 +838,7 @@ contract SwarmRegistryL1Test is Test { uint256 fleetId = _registerFleet(fleetOwner, "f1"); uint256 providerId = _registerProvider(providerOwner, "url1"); uint256 swarmId = - _registerSwarm(fleetOwner, fleetId, providerId, new bytes(50), 8, SwarmRegistryL1Upgradeable.TagType.GENERIC); + _registerSwarm(fleetOwner, fleetId, providerId, new bytes(50), BITS_8, SwarmRegistryL1Upgradeable.TagType.GENERIC); (bool fleetValid, bool providerValid) = swarmRegistry.isSwarmValid(swarmId); assertTrue(fleetValid); @@ -831,7 +849,7 @@ contract SwarmRegistryL1Test is Test { uint256 fleetId = _registerFleet(fleetOwner, "f1"); uint256 providerId = _registerProvider(providerOwner, "url1"); uint256 swarmId = - _registerSwarm(fleetOwner, fleetId, providerId, new bytes(50), 8, SwarmRegistryL1Upgradeable.TagType.GENERIC); + _registerSwarm(fleetOwner, fleetId, providerId, new bytes(50), BITS_8, SwarmRegistryL1Upgradeable.TagType.GENERIC); // Burn provider vm.prank(providerOwner); @@ -846,7 +864,7 @@ contract SwarmRegistryL1Test is Test { uint256 fleetId = _registerFleet(fleetOwner, "f1"); uint256 providerId = _registerProvider(providerOwner, "url1"); uint256 swarmId = - _registerSwarm(fleetOwner, fleetId, providerId, new bytes(50), 8, SwarmRegistryL1Upgradeable.TagType.GENERIC); + _registerSwarm(fleetOwner, fleetId, providerId, new bytes(50), BITS_8, SwarmRegistryL1Upgradeable.TagType.GENERIC); // Burn registered fleet token (operator = owner for fresh registration) // This mints an owned-only token back to the owner @@ -869,7 +887,7 @@ contract SwarmRegistryL1Test is Test { uint256 fleetId = _registerFleet(fleetOwner, "f1"); uint256 providerId = _registerProvider(providerOwner, "url1"); uint256 swarmId = - _registerSwarm(fleetOwner, fleetId, providerId, new bytes(50), 8, SwarmRegistryL1Upgradeable.TagType.GENERIC); + _registerSwarm(fleetOwner, fleetId, providerId, new bytes(50), BITS_8, SwarmRegistryL1Upgradeable.TagType.GENERIC); // Burn registered fleet token → mints owned-only token vm.prank(fleetOwner); @@ -902,7 +920,7 @@ contract SwarmRegistryL1Test is Test { uint256 fleetId = _registerFleet(fleetOwner, "f1"); uint256 providerId = _registerProvider(providerOwner, "url1"); uint256 swarmId = - _registerSwarm(fleetOwner, fleetId, providerId, new bytes(50), 8, SwarmRegistryL1Upgradeable.TagType.GENERIC); + _registerSwarm(fleetOwner, fleetId, providerId, new bytes(50), BITS_8, SwarmRegistryL1Upgradeable.TagType.GENERIC); // Burn provider vm.prank(providerOwner); @@ -924,7 +942,7 @@ contract SwarmRegistryL1Test is Test { uint256 fleetId = _registerFleet(fleetOwner, "f1"); uint256 providerId = _registerProvider(providerOwner, "url1"); uint256 swarmId = - _registerSwarm(fleetOwner, fleetId, providerId, new bytes(50), 8, SwarmRegistryL1Upgradeable.TagType.GENERIC); + _registerSwarm(fleetOwner, fleetId, providerId, new bytes(50), BITS_8, SwarmRegistryL1Upgradeable.TagType.GENERIC); // Burn registered fleet token → mints owned-only token vm.prank(fleetOwner); @@ -954,8 +972,8 @@ contract SwarmRegistryL1Test is Test { bytes memory filter2 = new bytes(50); filter2[0] = 0x02; - uint256 s1 = _registerSwarm(fleetOwner, fleetId, p1, filter1, 8, SwarmRegistryL1Upgradeable.TagType.GENERIC); - uint256 s2 = _registerSwarm(fleetOwner, fleetId, p2, filter2, 8, SwarmRegistryL1Upgradeable.TagType.GENERIC); + uint256 s1 = _registerSwarm(fleetOwner, fleetId, p1, filter1, BITS_8, SwarmRegistryL1Upgradeable.TagType.GENERIC); + uint256 s2 = _registerSwarm(fleetOwner, fleetId, p2, filter2, BITS_8, SwarmRegistryL1Upgradeable.TagType.GENERIC); // Burn provider of s1 vm.prank(providerOwner); @@ -979,7 +997,7 @@ contract SwarmRegistryL1Test is Test { uint256 fleetId = _registerFleet(fleetOwner, "f1"); uint256 providerId = _registerProvider(providerOwner, "url1"); uint256 swarmId = - _registerSwarm(fleetOwner, fleetId, providerId, new bytes(50), 8, SwarmRegistryL1Upgradeable.TagType.GENERIC); + _registerSwarm(fleetOwner, fleetId, providerId, new bytes(50), BITS_8, SwarmRegistryL1Upgradeable.TagType.GENERIC); vm.expectRevert(SwarmRegistryL1Upgradeable.SwarmNotOrphaned.selector); swarmRegistry.purgeOrphanedSwarm(swarmId); @@ -993,7 +1011,7 @@ contract SwarmRegistryL1Test is Test { uint256 fleetId = _registerFleet(fleetOwner, "f1"); uint256 providerId = _registerProvider(providerOwner, "url1"); uint256 swarmId = - _registerSwarm(fleetOwner, fleetId, providerId, new bytes(50), 8, SwarmRegistryL1Upgradeable.TagType.GENERIC); + _registerSwarm(fleetOwner, fleetId, providerId, new bytes(50), BITS_8, SwarmRegistryL1Upgradeable.TagType.GENERIC); // Burn provider vm.prank(providerOwner); @@ -1008,7 +1026,7 @@ contract SwarmRegistryL1Test is Test { uint256 fleetId = _registerFleet(fleetOwner, "f1"); uint256 providerId = _registerProvider(providerOwner, "url1"); uint256 swarmId = - _registerSwarm(fleetOwner, fleetId, providerId, new bytes(50), 8, SwarmRegistryL1Upgradeable.TagType.GENERIC); + _registerSwarm(fleetOwner, fleetId, providerId, new bytes(50), BITS_8, SwarmRegistryL1Upgradeable.TagType.GENERIC); // Burn registered fleet token → mints owned-only token vm.prank(fleetOwner); @@ -1029,7 +1047,7 @@ contract SwarmRegistryL1Test is Test { uint256 fleetId = _registerFleet(fleetOwner, "f1"); uint256 providerId = _registerProvider(providerOwner, "url1"); uint256 swarmId = - _registerSwarm(fleetOwner, fleetId, providerId, new bytes(50), 8, SwarmRegistryL1Upgradeable.TagType.GENERIC); + _registerSwarm(fleetOwner, fleetId, providerId, new bytes(50), BITS_8, SwarmRegistryL1Upgradeable.TagType.GENERIC); // Burn provider vm.prank(providerOwner); @@ -1043,7 +1061,7 @@ contract SwarmRegistryL1Test is Test { uint256 fleetId = _registerFleet(fleetOwner, "f1"); uint256 providerId = _registerProvider(providerOwner, "url1"); uint256 swarmId = - _registerSwarm(fleetOwner, fleetId, providerId, new bytes(50), 8, SwarmRegistryL1Upgradeable.TagType.GENERIC); + _registerSwarm(fleetOwner, fleetId, providerId, new bytes(50), BITS_8, SwarmRegistryL1Upgradeable.TagType.GENERIC); // Burn registered fleet token → mints owned-only token vm.prank(fleetOwner); @@ -1064,7 +1082,7 @@ contract SwarmRegistryL1Test is Test { uint256 fleetId = _registerFleet(fleetOwner, "f1"); uint256 providerId = _registerProvider(providerOwner, "url1"); uint256 swarmId = - _registerSwarm(fleetOwner, fleetId, providerId, new bytes(50), 8, SwarmRegistryL1Upgradeable.TagType.GENERIC); + _registerSwarm(fleetOwner, fleetId, providerId, new bytes(50), BITS_8, SwarmRegistryL1Upgradeable.TagType.GENERIC); vm.prank(providerOwner); providerContract.burn(providerId); diff --git a/test/SwarmRegistryUniversal.t.sol b/test/SwarmRegistryUniversal.t.sol index 99f27b24..f96bd1b9 100644 --- a/test/SwarmRegistryUniversal.t.sol +++ b/test/SwarmRegistryUniversal.t.sol @@ -33,6 +33,12 @@ contract SwarmRegistryUniversalTest is Test { uint16 constant US = 840; uint16 constant ADMIN_CA = 6; // California + // Alias for FingerprintSize enum + SwarmRegistryUniversalUpgradeable.FingerprintSize constant BITS_8 = + SwarmRegistryUniversalUpgradeable.FingerprintSize.BITS_8; + SwarmRegistryUniversalUpgradeable.FingerprintSize constant BITS_16 = + SwarmRegistryUniversalUpgradeable.FingerprintSize.BITS_16; + event SwarmRegistered( uint256 indexed swarmId, bytes16 indexed fleetUuid, uint256 indexed providerId, address owner, uint32 filterSize ); @@ -97,7 +103,7 @@ contract SwarmRegistryUniversalTest is Test { uint256 fleetId, uint256 providerId, bytes memory filter, - uint8 fpSize, + SwarmRegistryUniversalUpgradeable.FingerprintSize fpSize, SwarmRegistryUniversalUpgradeable.TagType tagType ) internal returns (uint256) { bytes16 fleetUuid = _getFleetUuid(fleetId); @@ -105,21 +111,22 @@ contract SwarmRegistryUniversalTest is Test { return swarmRegistry.registerSwarm(fleetUuid, providerId, filter, fpSize, tagType); } - function getExpectedValues(bytes memory tagId, uint256 m, uint8 fpSize) - public - pure - returns (uint32 h1, uint32 h2, uint32 h3, uint256 fp) - { + /// @dev Get expected hash indices and fingerprint for XOR filter verification + function getExpectedValues( + bytes memory tagId, + uint256 m, + SwarmRegistryUniversalUpgradeable.FingerprintSize fpSize + ) public pure returns (uint32 h1, uint32 h2, uint32 h3, uint256 fp) { bytes32 h = keccak256(tagId); h1 = uint32(uint256(h)) % uint32(m); h2 = uint32(uint256(h) >> 32) % uint32(m); h3 = uint32(uint256(h) >> 64) % uint32(m); - uint256 fpMask = (uint256(1) << fpSize) - 1; + uint256 fpMask = fpSize == SwarmRegistryUniversalUpgradeable.FingerprintSize.BITS_8 ? 0xFF : 0xFFFF; fp = (uint256(h) >> 96) & fpMask; } function _write16Bit(bytes memory data, uint256 slotIndex, uint16 value) internal pure { - uint256 byteOffset = (slotIndex * 16) / 8; + uint256 byteOffset = slotIndex * 2; data[byteOffset] = bytes1(uint8(value >> 8)); data[byteOffset + 1] = bytes1(uint8(value)); } @@ -173,11 +180,13 @@ contract SwarmRegistryUniversalTest is Test { uint256 providerId = _registerProvider(providerOwner, "https://api.example.com"); uint256 swarmId = _registerSwarm( - fleetOwner, fleetId, providerId, new bytes(100), 16, SwarmRegistryUniversalUpgradeable.TagType.IBEACON_INCLUDES_MAC + fleetOwner, fleetId, providerId, new bytes(100), BITS_16, SwarmRegistryUniversalUpgradeable.TagType.IBEACON_INCLUDES_MAC ); - // Swarm ID is deterministic hash of (fleetUuid, filter, fingerprintSize, tagType) - uint256 expectedId = swarmRegistry.computeSwarmId(_getFleetUuid(fleetId), new bytes(100), 16, SwarmRegistryUniversalUpgradeable.TagType.IBEACON_INCLUDES_MAC); + // Swarm ID is deterministic hash of (fleetUuid, filter, fpSize, tagType) + uint256 expectedId = swarmRegistry.computeSwarmId( + _getFleetUuid(fleetId), new bytes(100), BITS_16, SwarmRegistryUniversalUpgradeable.TagType.IBEACON_INCLUDES_MAC + ); assertEq(swarmId, expectedId); } @@ -186,13 +195,13 @@ contract SwarmRegistryUniversalTest is Test { uint256 providerId = _registerProvider(providerOwner, "url1"); uint256 swarmId = - _registerSwarm(fleetOwner, fleetId, providerId, new bytes(50), 12, SwarmRegistryUniversalUpgradeable.TagType.VENDOR_ID); + _registerSwarm(fleetOwner, fleetId, providerId, new bytes(50), BITS_16, SwarmRegistryUniversalUpgradeable.TagType.VENDOR_ID); ( bytes16 storedFleetUuid, uint256 storedProviderId, uint32 storedFilterLen, - uint8 storedFpSize, + SwarmRegistryUniversalUpgradeable.FingerprintSize storedFpSize, SwarmRegistryUniversalUpgradeable.TagType storedTagType, SwarmRegistryUniversalUpgradeable.SwarmStatus storedStatus ) = swarmRegistry.swarms(swarmId); @@ -200,7 +209,7 @@ contract SwarmRegistryUniversalTest is Test { assertEq(storedFleetUuid, _getFleetUuid(fleetId)); assertEq(storedProviderId, providerId); assertEq(storedFilterLen, 50); - assertEq(storedFpSize, 12); + assertEq(uint8(storedFpSize), uint8(BITS_16)); assertEq(uint8(storedTagType), uint8(SwarmRegistryUniversalUpgradeable.TagType.VENDOR_ID)); assertEq(uint8(storedStatus), uint8(SwarmRegistryUniversalUpgradeable.SwarmStatus.REGISTERED)); } @@ -216,7 +225,7 @@ contract SwarmRegistryUniversalTest is Test { filter[99] = 0xEF; uint256 swarmId = - _registerSwarm(fleetOwner, fleetId, providerId, filter, 16, SwarmRegistryUniversalUpgradeable.TagType.GENERIC); + _registerSwarm(fleetOwner, fleetId, providerId, filter, BITS_16, SwarmRegistryUniversalUpgradeable.TagType.GENERIC); bytes memory storedFilter = swarmRegistry.getFilterData(swarmId); assertEq(storedFilter.length, 100); @@ -231,10 +240,11 @@ contract SwarmRegistryUniversalTest is Test { bytes memory filter = new bytes(32); - uint256 expectedId = swarmRegistry.computeSwarmId(_getFleetUuid(fleetId), filter, 8, SwarmRegistryUniversalUpgradeable.TagType.GENERIC); + uint256 expectedId = + swarmRegistry.computeSwarmId(_getFleetUuid(fleetId), filter, BITS_8, SwarmRegistryUniversalUpgradeable.TagType.GENERIC); uint256 swarmId = - _registerSwarm(fleetOwner, fleetId, providerId, filter, 8, SwarmRegistryUniversalUpgradeable.TagType.GENERIC); + _registerSwarm(fleetOwner, fleetId, providerId, filter, BITS_8, SwarmRegistryUniversalUpgradeable.TagType.GENERIC); assertEq(swarmId, expectedId); } @@ -242,11 +252,13 @@ contract SwarmRegistryUniversalTest is Test { uint256 fleetId = _registerFleet(fleetOwner, "f1"); uint256 providerId = _registerProvider(providerOwner, "url1"); - _registerSwarm(fleetOwner, fleetId, providerId, new bytes(32), 8, SwarmRegistryUniversalUpgradeable.TagType.GENERIC); + _registerSwarm(fleetOwner, fleetId, providerId, new bytes(32), BITS_8, SwarmRegistryUniversalUpgradeable.TagType.GENERIC); vm.prank(fleetOwner); vm.expectRevert(SwarmRegistryUniversalUpgradeable.SwarmAlreadyExists.selector); - swarmRegistry.registerSwarm(_getFleetUuid(fleetId), providerId, new bytes(32), 8, SwarmRegistryUniversalUpgradeable.TagType.GENERIC); + swarmRegistry.registerSwarm( + _getFleetUuid(fleetId), providerId, new bytes(32), BITS_8, SwarmRegistryUniversalUpgradeable.TagType.GENERIC + ); } function test_registerSwarm_emitsSwarmRegistered() public { @@ -254,12 +266,14 @@ contract SwarmRegistryUniversalTest is Test { uint256 providerId = _registerProvider(providerOwner, "url1"); bytes memory filter = new bytes(50); - uint256 expectedId = swarmRegistry.computeSwarmId(_getFleetUuid(fleetId), filter, 16, SwarmRegistryUniversalUpgradeable.TagType.GENERIC); + uint256 expectedId = swarmRegistry.computeSwarmId( + _getFleetUuid(fleetId), filter, BITS_16, SwarmRegistryUniversalUpgradeable.TagType.GENERIC + ); vm.expectEmit(true, true, true, true); emit SwarmRegistered(expectedId, _getFleetUuid(fleetId), providerId, fleetOwner, 50); - _registerSwarm(fleetOwner, fleetId, providerId, filter, 16, SwarmRegistryUniversalUpgradeable.TagType.GENERIC); + _registerSwarm(fleetOwner, fleetId, providerId, filter, BITS_16, SwarmRegistryUniversalUpgradeable.TagType.GENERIC); } function test_registerSwarm_linksUuidSwarms() public { @@ -274,9 +288,9 @@ contract SwarmRegistryUniversalTest is Test { filter2[0] = 0x02; uint256 s1 = - _registerSwarm(fleetOwner, fleetId, providerId1, filter1, 8, SwarmRegistryUniversalUpgradeable.TagType.GENERIC); + _registerSwarm(fleetOwner, fleetId, providerId1, filter1, BITS_8, SwarmRegistryUniversalUpgradeable.TagType.GENERIC); uint256 s2 = - _registerSwarm(fleetOwner, fleetId, providerId2, filter2, 8, SwarmRegistryUniversalUpgradeable.TagType.GENERIC); + _registerSwarm(fleetOwner, fleetId, providerId2, filter2, BITS_8, SwarmRegistryUniversalUpgradeable.TagType.GENERIC); assertEq(swarmRegistry.uuidSwarms(_getFleetUuid(fleetId), 0), s1); assertEq(swarmRegistry.uuidSwarms(_getFleetUuid(fleetId), 1), s2); @@ -290,15 +304,17 @@ contract SwarmRegistryUniversalTest is Test { uint256 providerId = _registerProvider(providerOwner, "url"); uint256 s1 = _registerSwarm( - fleetOwner, fleetId1, providerId, new bytes(32), 8, SwarmRegistryUniversalUpgradeable.TagType.IBEACON_PAYLOAD_ONLY + fleetOwner, fleetId1, providerId, new bytes(32), BITS_8, SwarmRegistryUniversalUpgradeable.TagType.IBEACON_PAYLOAD_ONLY ); uint256 s2 = _registerSwarm( - fleetOwner, fleetId2, providerId, new bytes(32), 8, SwarmRegistryUniversalUpgradeable.TagType.IBEACON_INCLUDES_MAC + fleetOwner, fleetId2, providerId, new bytes(32), BITS_8, SwarmRegistryUniversalUpgradeable.TagType.IBEACON_INCLUDES_MAC + ); + uint256 s3 = _registerSwarm( + fleetOwner, fleetId3, providerId, new bytes(32), BITS_8, SwarmRegistryUniversalUpgradeable.TagType.VENDOR_ID + ); + uint256 s4 = _registerSwarm( + fleetOwner, fleetId4, providerId, new bytes(32), BITS_8, SwarmRegistryUniversalUpgradeable.TagType.GENERIC ); - uint256 s3 = - _registerSwarm(fleetOwner, fleetId3, providerId, new bytes(32), 8, SwarmRegistryUniversalUpgradeable.TagType.VENDOR_ID); - uint256 s4 = - _registerSwarm(fleetOwner, fleetId4, providerId, new bytes(32), 8, SwarmRegistryUniversalUpgradeable.TagType.GENERIC); (,,,, SwarmRegistryUniversalUpgradeable.TagType t1,) = swarmRegistry.swarms(s1); (,,,, SwarmRegistryUniversalUpgradeable.TagType t2,) = swarmRegistry.swarms(s2); @@ -311,6 +327,25 @@ contract SwarmRegistryUniversalTest is Test { assertEq(uint8(t4), uint8(SwarmRegistryUniversalUpgradeable.TagType.GENERIC)); } + function test_registerSwarm_bothFingerprintSizes() public { + uint256 fleetId1 = _registerFleet(fleetOwner, "f1"); + uint256 fleetId2 = _registerFleet(fleetOwner, "f2"); + uint256 providerId = _registerProvider(providerOwner, "url"); + + uint256 s1 = _registerSwarm( + fleetOwner, fleetId1, providerId, new bytes(32), BITS_8, SwarmRegistryUniversalUpgradeable.TagType.GENERIC + ); + uint256 s2 = _registerSwarm( + fleetOwner, fleetId2, providerId, new bytes(64), BITS_16, SwarmRegistryUniversalUpgradeable.TagType.GENERIC + ); + + (,,, SwarmRegistryUniversalUpgradeable.FingerprintSize fp1,,) = swarmRegistry.swarms(s1); + (,,, SwarmRegistryUniversalUpgradeable.FingerprintSize fp2,,) = swarmRegistry.swarms(s2); + + assertEq(uint8(fp1), uint8(BITS_8)); + assertEq(uint8(fp2), uint8(BITS_16)); + } + // ============================== // registerSwarm — reverts // ============================== @@ -320,7 +355,9 @@ contract SwarmRegistryUniversalTest is Test { vm.prank(fleetOwner); vm.expectRevert(SwarmRegistryUniversalUpgradeable.InvalidUuid.selector); - swarmRegistry.registerSwarm(bytes16(0), providerId, new bytes(32), 8, SwarmRegistryUniversalUpgradeable.TagType.GENERIC); + swarmRegistry.registerSwarm( + bytes16(0), providerId, new bytes(32), BITS_8, SwarmRegistryUniversalUpgradeable.TagType.GENERIC + ); } function test_RevertIf_registerSwarm_providerDoesNotExist() public { @@ -329,7 +366,9 @@ contract SwarmRegistryUniversalTest is Test { vm.prank(fleetOwner); vm.expectRevert(SwarmRegistryUniversalUpgradeable.ProviderDoesNotExist.selector); - swarmRegistry.registerSwarm(_getFleetUuid(fleetId), nonExistentProvider, new bytes(32), 8, SwarmRegistryUniversalUpgradeable.TagType.GENERIC); + swarmRegistry.registerSwarm( + _getFleetUuid(fleetId), nonExistentProvider, new bytes(32), BITS_8, SwarmRegistryUniversalUpgradeable.TagType.GENERIC + ); } function test_RevertIf_registerSwarm_notFleetOwner() public { @@ -337,25 +376,9 @@ contract SwarmRegistryUniversalTest is Test { vm.prank(caller); vm.expectRevert(SwarmRegistryUniversalUpgradeable.NotUuidOwner.selector); - swarmRegistry.registerSwarm(_getFleetUuid(fleetId), 1, new bytes(10), 16, SwarmRegistryUniversalUpgradeable.TagType.GENERIC); - } - - function test_RevertIf_registerSwarm_fingerprintSizeZero() public { - uint256 fleetId = _registerFleet(fleetOwner, "f1"); - uint256 providerId = _registerProvider(providerOwner, "url1"); - - vm.prank(fleetOwner); - vm.expectRevert(SwarmRegistryUniversalUpgradeable.InvalidFingerprintSize.selector); - swarmRegistry.registerSwarm(_getFleetUuid(fleetId), providerId, new bytes(32), 0, SwarmRegistryUniversalUpgradeable.TagType.GENERIC); - } - - function test_RevertIf_registerSwarm_fingerprintSizeExceedsMax() public { - uint256 fleetId = _registerFleet(fleetOwner, "f1"); - uint256 providerId = _registerProvider(providerOwner, "url1"); - - vm.prank(fleetOwner); - vm.expectRevert(SwarmRegistryUniversalUpgradeable.InvalidFingerprintSize.selector); - swarmRegistry.registerSwarm(_getFleetUuid(fleetId), providerId, new bytes(32), 17, SwarmRegistryUniversalUpgradeable.TagType.GENERIC); + swarmRegistry.registerSwarm( + _getFleetUuid(fleetId), 1, new bytes(10), BITS_16, SwarmRegistryUniversalUpgradeable.TagType.GENERIC + ); } function test_RevertIf_registerSwarm_emptyFilter() public { @@ -364,7 +387,9 @@ contract SwarmRegistryUniversalTest is Test { vm.prank(fleetOwner); vm.expectRevert(SwarmRegistryUniversalUpgradeable.InvalidFilterSize.selector); - swarmRegistry.registerSwarm(_getFleetUuid(fleetId), providerId, new bytes(0), 8, SwarmRegistryUniversalUpgradeable.TagType.GENERIC); + swarmRegistry.registerSwarm( + _getFleetUuid(fleetId), providerId, new bytes(0), BITS_8, SwarmRegistryUniversalUpgradeable.TagType.GENERIC + ); } function test_RevertIf_registerSwarm_filterTooLarge() public { @@ -373,16 +398,9 @@ contract SwarmRegistryUniversalTest is Test { vm.prank(fleetOwner); vm.expectRevert(SwarmRegistryUniversalUpgradeable.FilterTooLarge.selector); - swarmRegistry.registerSwarm(_getFleetUuid(fleetId), providerId, new bytes(24577), 8, SwarmRegistryUniversalUpgradeable.TagType.GENERIC); - } - - function test_registerSwarm_maxFingerprintSize() public { - uint256 fleetId = _registerFleet(fleetOwner, "f1"); - uint256 providerId = _registerProvider(providerOwner, "url1"); - - uint256 swarmId = - _registerSwarm(fleetOwner, fleetId, providerId, new bytes(100), 16, SwarmRegistryUniversalUpgradeable.TagType.GENERIC); - assertTrue(swarmId != 0); + swarmRegistry.registerSwarm( + _getFleetUuid(fleetId), providerId, new bytes(24577), BITS_8, SwarmRegistryUniversalUpgradeable.TagType.GENERIC + ); } function test_registerSwarm_maxFilterSize() public { @@ -390,8 +408,9 @@ contract SwarmRegistryUniversalTest is Test { uint256 providerId = _registerProvider(providerOwner, "url1"); // Exactly MAX_FILTER_SIZE (24576) should succeed - uint256 swarmId = - _registerSwarm(fleetOwner, fleetId, providerId, new bytes(24576), 8, SwarmRegistryUniversalUpgradeable.TagType.GENERIC); + uint256 swarmId = _registerSwarm( + fleetOwner, fleetId, providerId, new bytes(24576), BITS_8, SwarmRegistryUniversalUpgradeable.TagType.GENERIC + ); assertTrue(swarmId != 0); } @@ -400,8 +419,9 @@ contract SwarmRegistryUniversalTest is Test { uint256 providerId = _registerProvider(providerOwner, "url1"); // 1 byte filter - uint256 swarmId = - _registerSwarm(fleetOwner, fleetId, providerId, new bytes(1), 8, SwarmRegistryUniversalUpgradeable.TagType.GENERIC); + uint256 swarmId = _registerSwarm( + fleetOwner, fleetId, providerId, new bytes(1), BITS_8, SwarmRegistryUniversalUpgradeable.TagType.GENERIC + ); assertTrue(swarmId != 0); } @@ -412,8 +432,9 @@ contract SwarmRegistryUniversalTest is Test { function test_acceptSwarm_setsStatusAndEmits() public { uint256 fleetId = _registerFleet(fleetOwner, "f1"); uint256 providerId = _registerProvider(providerOwner, "url1"); - uint256 swarmId = - _registerSwarm(fleetOwner, fleetId, providerId, new bytes(50), 8, SwarmRegistryUniversalUpgradeable.TagType.GENERIC); + uint256 swarmId = _registerSwarm( + fleetOwner, fleetId, providerId, new bytes(50), BITS_8, SwarmRegistryUniversalUpgradeable.TagType.GENERIC + ); vm.expectEmit(true, true, true, true); emit SwarmStatusChanged(swarmId, SwarmRegistryUniversalUpgradeable.SwarmStatus.ACCEPTED); @@ -428,8 +449,9 @@ contract SwarmRegistryUniversalTest is Test { function test_rejectSwarm_setsStatusAndEmits() public { uint256 fleetId = _registerFleet(fleetOwner, "f1"); uint256 providerId = _registerProvider(providerOwner, "url1"); - uint256 swarmId = - _registerSwarm(fleetOwner, fleetId, providerId, new bytes(50), 8, SwarmRegistryUniversalUpgradeable.TagType.GENERIC); + uint256 swarmId = _registerSwarm( + fleetOwner, fleetId, providerId, new bytes(50), BITS_8, SwarmRegistryUniversalUpgradeable.TagType.GENERIC + ); vm.expectEmit(true, true, true, true); emit SwarmStatusChanged(swarmId, SwarmRegistryUniversalUpgradeable.SwarmStatus.REJECTED); @@ -444,8 +466,9 @@ contract SwarmRegistryUniversalTest is Test { function test_RevertIf_acceptSwarm_notProviderOwner() public { uint256 fleetId = _registerFleet(fleetOwner, "f1"); uint256 providerId = _registerProvider(providerOwner, "url1"); - uint256 swarmId = - _registerSwarm(fleetOwner, fleetId, providerId, new bytes(50), 8, SwarmRegistryUniversalUpgradeable.TagType.GENERIC); + uint256 swarmId = _registerSwarm( + fleetOwner, fleetId, providerId, new bytes(50), BITS_8, SwarmRegistryUniversalUpgradeable.TagType.GENERIC + ); vm.prank(caller); vm.expectRevert(SwarmRegistryUniversalUpgradeable.NotProviderOwner.selector); @@ -455,8 +478,9 @@ contract SwarmRegistryUniversalTest is Test { function test_RevertIf_rejectSwarm_notProviderOwner() public { uint256 fleetId = _registerFleet(fleetOwner, "f1"); uint256 providerId = _registerProvider(providerOwner, "url1"); - uint256 swarmId = - _registerSwarm(fleetOwner, fleetId, providerId, new bytes(50), 8, SwarmRegistryUniversalUpgradeable.TagType.GENERIC); + uint256 swarmId = _registerSwarm( + fleetOwner, fleetId, providerId, new bytes(50), BITS_8, SwarmRegistryUniversalUpgradeable.TagType.GENERIC + ); vm.prank(fleetOwner); // fleet owner != provider owner vm.expectRevert(SwarmRegistryUniversalUpgradeable.NotProviderOwner.selector); @@ -466,8 +490,9 @@ contract SwarmRegistryUniversalTest is Test { function test_RevertIf_acceptSwarm_fleetOwnerNotProvider() public { uint256 fleetId = _registerFleet(fleetOwner, "f1"); uint256 providerId = _registerProvider(providerOwner, "url1"); - uint256 swarmId = - _registerSwarm(fleetOwner, fleetId, providerId, new bytes(50), 8, SwarmRegistryUniversalUpgradeable.TagType.GENERIC); + uint256 swarmId = _registerSwarm( + fleetOwner, fleetId, providerId, new bytes(50), BITS_8, SwarmRegistryUniversalUpgradeable.TagType.GENERIC + ); vm.prank(fleetOwner); vm.expectRevert(SwarmRegistryUniversalUpgradeable.NotProviderOwner.selector); @@ -477,8 +502,9 @@ contract SwarmRegistryUniversalTest is Test { function test_acceptSwarm_afterReject() public { uint256 fleetId = _registerFleet(fleetOwner, "f1"); uint256 providerId = _registerProvider(providerOwner, "url1"); - uint256 swarmId = - _registerSwarm(fleetOwner, fleetId, providerId, new bytes(50), 8, SwarmRegistryUniversalUpgradeable.TagType.GENERIC); + uint256 swarmId = _registerSwarm( + fleetOwner, fleetId, providerId, new bytes(50), BITS_8, SwarmRegistryUniversalUpgradeable.TagType.GENERIC + ); vm.prank(providerOwner); swarmRegistry.rejectSwarm(swarmId); @@ -493,8 +519,9 @@ contract SwarmRegistryUniversalTest is Test { function test_rejectSwarm_afterAccept() public { uint256 fleetId = _registerFleet(fleetOwner, "f1"); uint256 providerId = _registerProvider(providerOwner, "url1"); - uint256 swarmId = - _registerSwarm(fleetOwner, fleetId, providerId, new bytes(50), 8, SwarmRegistryUniversalUpgradeable.TagType.GENERIC); + uint256 swarmId = _registerSwarm( + fleetOwner, fleetId, providerId, new bytes(50), BITS_8, SwarmRegistryUniversalUpgradeable.TagType.GENERIC + ); vm.prank(providerOwner); swarmRegistry.acceptSwarm(swarmId); @@ -515,11 +542,10 @@ contract SwarmRegistryUniversalTest is Test { uint256 providerId = _registerProvider(providerOwner, "u1"); bytes memory tagId = hex"1122334455"; - uint8 fpSize = 16; uint256 dataLen = 100; - uint256 m = (dataLen * 8) / fpSize; // 50 slots + uint256 m = dataLen / 2; // 50 slots for 16-bit - (uint32 h1, uint32 h2, uint32 h3, uint256 expectedFp) = getExpectedValues(tagId, m, fpSize); + (uint32 h1, uint32 h2, uint32 h3, uint256 expectedFp) = getExpectedValues(tagId, m, BITS_16); if (h1 == h2 || h1 == h3 || h2 == h3) { return; @@ -529,7 +555,7 @@ contract SwarmRegistryUniversalTest is Test { _write16Bit(filter, h1, uint16(expectedFp)); uint256 swarmId = - _registerSwarm(fleetOwner, fleetId, providerId, filter, fpSize, SwarmRegistryUniversalUpgradeable.TagType.GENERIC); + _registerSwarm(fleetOwner, fleetId, providerId, filter, BITS_16, SwarmRegistryUniversalUpgradeable.TagType.GENERIC); bytes32 tagHash = keccak256(tagId); assertTrue(swarmRegistry.checkMembership(swarmId, tagHash), "Tag should be member"); @@ -543,11 +569,10 @@ contract SwarmRegistryUniversalTest is Test { uint256 providerId = _registerProvider(providerOwner, "u1"); bytes memory tagId = hex"AABBCCDD"; - uint8 fpSize = 8; uint256 dataLen = 80; - uint256 m = (dataLen * 8) / fpSize; // 80 slots + uint256 m = dataLen; // 80 slots for 8-bit - (uint32 h1, uint32 h2, uint32 h3, uint256 expectedFp) = getExpectedValues(tagId, m, fpSize); + (uint32 h1, uint32 h2, uint32 h3, uint256 expectedFp) = getExpectedValues(tagId, m, BITS_8); if (h1 == h2 || h1 == h3 || h2 == h3) { return; @@ -557,7 +582,7 @@ contract SwarmRegistryUniversalTest is Test { _write8Bit(filter, h1, uint8(expectedFp)); uint256 swarmId = - _registerSwarm(fleetOwner, fleetId, providerId, filter, fpSize, SwarmRegistryUniversalUpgradeable.TagType.GENERIC); + _registerSwarm(fleetOwner, fleetId, providerId, filter, BITS_8, SwarmRegistryUniversalUpgradeable.TagType.GENERIC); assertTrue(swarmRegistry.checkMembership(swarmId, keccak256(tagId)), "8-bit valid tag should pass"); assertFalse(swarmRegistry.checkMembership(swarmId, keccak256(hex"FFFFFF")), "8-bit invalid tag should fail"); @@ -575,7 +600,7 @@ contract SwarmRegistryUniversalTest is Test { // All-zero filter: f1^f2^f3 = 0^0^0 = 0 bytes memory filter = new bytes(64); uint256 swarmId = - _registerSwarm(fleetOwner, fleetId, providerId, filter, 16, SwarmRegistryUniversalUpgradeable.TagType.GENERIC); + _registerSwarm(fleetOwner, fleetId, providerId, filter, BITS_16, SwarmRegistryUniversalUpgradeable.TagType.GENERIC); // Should not revert regardless of result swarmRegistry.checkMembership(swarmId, keccak256("test1")); @@ -586,9 +611,10 @@ contract SwarmRegistryUniversalTest is Test { uint256 fleetId = _registerFleet(fleetOwner, "f1"); uint256 providerId = _registerProvider(providerOwner, "u1"); - // 1-byte filter with 16-bit fingerprint: m = (1*8)/16 = 0, returns false immediately + // 1-byte filter with 16-bit fingerprint: m = 1/2 = 0, returns false immediately bytes memory filter = new bytes(1); - uint256 swarmId = _registerSwarm(fleetOwner, fleetId, providerId, filter, 16, SwarmRegistryUniversalUpgradeable.TagType.GENERIC); + uint256 swarmId = + _registerSwarm(fleetOwner, fleetId, providerId, filter, BITS_16, SwarmRegistryUniversalUpgradeable.TagType.GENERIC); // Should return false (not revert) because m == 0 assertFalse(swarmRegistry.checkMembership(swarmId, keccak256("test")), "m=0 should return false"); @@ -607,7 +633,7 @@ contract SwarmRegistryUniversalTest is Test { filter[99] = 0x01; uint256 swarmId = - _registerSwarm(fleetOwner, fleetId, providerId, filter, 16, SwarmRegistryUniversalUpgradeable.TagType.GENERIC); + _registerSwarm(fleetOwner, fleetId, providerId, filter, BITS_16, SwarmRegistryUniversalUpgradeable.TagType.GENERIC); bytes memory stored = swarmRegistry.getFilterData(swarmId); assertEq(stored.length, 100); @@ -630,13 +656,14 @@ contract SwarmRegistryUniversalTest is Test { uint256 providerId2 = _registerProvider(providerOwner, "url2"); uint256 providerId3 = _registerProvider(providerOwner, "url3"); - uint256 s1 = - _registerSwarm(fleetOwner, fleetId, providerId1, new bytes(32), 8, SwarmRegistryUniversalUpgradeable.TagType.GENERIC); + uint256 s1 = _registerSwarm( + fleetOwner, fleetId, providerId1, new bytes(32), BITS_8, SwarmRegistryUniversalUpgradeable.TagType.GENERIC + ); uint256 s2 = _registerSwarm( - fleetOwner, fleetId, providerId2, new bytes(64), 16, SwarmRegistryUniversalUpgradeable.TagType.VENDOR_ID + fleetOwner, fleetId, providerId2, new bytes(64), BITS_16, SwarmRegistryUniversalUpgradeable.TagType.VENDOR_ID ); uint256 s3 = _registerSwarm( - fleetOwner, fleetId, providerId3, new bytes(50), 12, SwarmRegistryUniversalUpgradeable.TagType.IBEACON_PAYLOAD_ONLY + fleetOwner, fleetId, providerId3, new bytes(50), BITS_8, SwarmRegistryUniversalUpgradeable.TagType.IBEACON_PAYLOAD_ONLY ); // IDs are distinct hashes @@ -652,7 +679,6 @@ contract SwarmRegistryUniversalTest is Test { // ============================== function test_constants() public view { - assertEq(swarmRegistry.MAX_FINGERPRINT_SIZE(), 16); assertEq(swarmRegistry.MAX_FILTER_SIZE(), 24576); } @@ -660,39 +686,15 @@ contract SwarmRegistryUniversalTest is Test { // Fuzz // ============================== - function testFuzz_registerSwarm_validFingerprintSizes(uint8 fpSize) public { - fpSize = uint8(bound(fpSize, 1, 16)); - - uint256 fleetId = _registerFleet(fleetOwner, abi.encodePacked("fleet-", fpSize)); - uint256 providerId = _registerProvider(providerOwner, string(abi.encodePacked("url-", fpSize))); - - uint256 swarmId = _registerSwarm( - fleetOwner, fleetId, providerId, new bytes(64), fpSize, SwarmRegistryUniversalUpgradeable.TagType.GENERIC - ); - - (,,, uint8 storedFp,,) = swarmRegistry.swarms(swarmId); - assertEq(storedFp, fpSize); - } - - function testFuzz_registerSwarm_invalidFingerprintSizes(uint8 fpSize) public { - vm.assume(fpSize == 0 || fpSize > 16); - - uint256 fleetId = _registerFleet(fleetOwner, "f1"); - uint256 providerId = _registerProvider(providerOwner, "url1"); - - vm.prank(fleetOwner); - vm.expectRevert(SwarmRegistryUniversalUpgradeable.InvalidFingerprintSize.selector); - swarmRegistry.registerSwarm(_getFleetUuid(fleetId), providerId, new bytes(32), fpSize, SwarmRegistryUniversalUpgradeable.TagType.GENERIC); - } - function testFuzz_registerSwarm_filterSizeRange(uint256 size) public { size = bound(size, 1, 24576); uint256 fleetId = _registerFleet(fleetOwner, abi.encodePacked("f-", size)); uint256 providerId = _registerProvider(providerOwner, string(abi.encodePacked("url-", size))); - uint256 swarmId = - _registerSwarm(fleetOwner, fleetId, providerId, new bytes(size), 8, SwarmRegistryUniversalUpgradeable.TagType.GENERIC); + uint256 swarmId = _registerSwarm( + fleetOwner, fleetId, providerId, new bytes(size), BITS_8, SwarmRegistryUniversalUpgradeable.TagType.GENERIC + ); (,, uint32 storedLen,,,) = swarmRegistry.swarms(swarmId); assertEq(storedLen, uint32(size)); @@ -707,8 +709,9 @@ contract SwarmRegistryUniversalTest is Test { uint256 providerId1 = _registerProvider(providerOwner, "url1"); uint256 providerId2 = _registerProvider(providerOwner, "url2"); - uint256 swarmId = - _registerSwarm(fleetOwner, fleetId, providerId1, new bytes(50), 8, SwarmRegistryUniversalUpgradeable.TagType.GENERIC); + uint256 swarmId = _registerSwarm( + fleetOwner, fleetId, providerId1, new bytes(50), BITS_8, SwarmRegistryUniversalUpgradeable.TagType.GENERIC + ); // Provider accepts vm.prank(providerOwner); @@ -740,8 +743,9 @@ contract SwarmRegistryUniversalTest is Test { uint256 providerId1 = _registerProvider(providerOwner, "url1"); uint256 providerId2 = _registerProvider(providerOwner, "url2"); - uint256 swarmId = - _registerSwarm(fleetOwner, fleetId, providerId1, new bytes(50), 8, SwarmRegistryUniversalUpgradeable.TagType.GENERIC); + uint256 swarmId = _registerSwarm( + fleetOwner, fleetId, providerId1, new bytes(50), BITS_8, SwarmRegistryUniversalUpgradeable.TagType.GENERIC + ); vm.prank(caller); vm.expectRevert(SwarmRegistryUniversalUpgradeable.NotUuidOwner.selector); @@ -752,8 +756,9 @@ contract SwarmRegistryUniversalTest is Test { uint256 fleetId = _registerFleet(fleetOwner, "f1"); uint256 providerId = _registerProvider(providerOwner, "url1"); - uint256 swarmId = - _registerSwarm(fleetOwner, fleetId, providerId, new bytes(50), 8, SwarmRegistryUniversalUpgradeable.TagType.GENERIC); + uint256 swarmId = _registerSwarm( + fleetOwner, fleetId, providerId, new bytes(50), BITS_8, SwarmRegistryUniversalUpgradeable.TagType.GENERIC + ); vm.prank(fleetOwner); vm.expectRevert(SwarmRegistryUniversalUpgradeable.ProviderDoesNotExist.selector); @@ -767,8 +772,9 @@ contract SwarmRegistryUniversalTest is Test { function test_deleteSwarm_removesSwarmAndEmits() public { uint256 fleetId = _registerFleet(fleetOwner, "f1"); uint256 providerId = _registerProvider(providerOwner, "url1"); - uint256 swarmId = - _registerSwarm(fleetOwner, fleetId, providerId, new bytes(50), 8, SwarmRegistryUniversalUpgradeable.TagType.GENERIC); + uint256 swarmId = _registerSwarm( + fleetOwner, fleetId, providerId, new bytes(50), BITS_8, SwarmRegistryUniversalUpgradeable.TagType.GENERIC + ); vm.expectEmit(true, true, true, true); emit SwarmDeleted(swarmId, _getFleetUuid(fleetId), fleetOwner); @@ -793,10 +799,12 @@ contract SwarmRegistryUniversalTest is Test { bytes memory filter2 = new bytes(50); filter2[0] = 0x02; - uint256 swarm1 = - _registerSwarm(fleetOwner, fleetId, providerId1, filter1, 8, SwarmRegistryUniversalUpgradeable.TagType.GENERIC); - uint256 swarm2 = - _registerSwarm(fleetOwner, fleetId, providerId2, filter2, 8, SwarmRegistryUniversalUpgradeable.TagType.GENERIC); + uint256 swarm1 = _registerSwarm( + fleetOwner, fleetId, providerId1, filter1, BITS_8, SwarmRegistryUniversalUpgradeable.TagType.GENERIC + ); + uint256 swarm2 = _registerSwarm( + fleetOwner, fleetId, providerId2, filter2, BITS_8, SwarmRegistryUniversalUpgradeable.TagType.GENERIC + ); // Delete first swarm vm.prank(fleetOwner); @@ -822,12 +830,15 @@ contract SwarmRegistryUniversalTest is Test { bytes memory filter3 = new bytes(50); filter3[0] = 0x03; - uint256 swarm1 = - _registerSwarm(fleetOwner, fleetId, providerId1, filter1, 8, SwarmRegistryUniversalUpgradeable.TagType.GENERIC); - uint256 swarm2 = - _registerSwarm(fleetOwner, fleetId, providerId2, filter2, 8, SwarmRegistryUniversalUpgradeable.TagType.GENERIC); - uint256 swarm3 = - _registerSwarm(fleetOwner, fleetId, providerId3, filter3, 8, SwarmRegistryUniversalUpgradeable.TagType.GENERIC); + uint256 swarm1 = _registerSwarm( + fleetOwner, fleetId, providerId1, filter1, BITS_8, SwarmRegistryUniversalUpgradeable.TagType.GENERIC + ); + uint256 swarm2 = _registerSwarm( + fleetOwner, fleetId, providerId2, filter2, BITS_8, SwarmRegistryUniversalUpgradeable.TagType.GENERIC + ); + uint256 swarm3 = _registerSwarm( + fleetOwner, fleetId, providerId3, filter3, BITS_8, SwarmRegistryUniversalUpgradeable.TagType.GENERIC + ); // Delete middle swarm vm.prank(fleetOwner); @@ -849,8 +860,9 @@ contract SwarmRegistryUniversalTest is Test { filterData[i] = bytes1(uint8(i)); } - uint256 swarmId = - _registerSwarm(fleetOwner, fleetId, providerId, filterData, 8, SwarmRegistryUniversalUpgradeable.TagType.GENERIC); + uint256 swarmId = _registerSwarm( + fleetOwner, fleetId, providerId, filterData, BITS_8, SwarmRegistryUniversalUpgradeable.TagType.GENERIC + ); // Delete swarm vm.prank(fleetOwner); @@ -870,8 +882,9 @@ contract SwarmRegistryUniversalTest is Test { function test_RevertIf_deleteSwarm_notFleetOwner() public { uint256 fleetId = _registerFleet(fleetOwner, "f1"); uint256 providerId = _registerProvider(providerOwner, "url1"); - uint256 swarmId = - _registerSwarm(fleetOwner, fleetId, providerId, new bytes(50), 8, SwarmRegistryUniversalUpgradeable.TagType.GENERIC); + uint256 swarmId = _registerSwarm( + fleetOwner, fleetId, providerId, new bytes(50), BITS_8, SwarmRegistryUniversalUpgradeable.TagType.GENERIC + ); vm.prank(caller); vm.expectRevert(SwarmRegistryUniversalUpgradeable.NotUuidOwner.selector); @@ -882,8 +895,9 @@ contract SwarmRegistryUniversalTest is Test { uint256 fleetId = _registerFleet(fleetOwner, "f1"); uint256 providerId1 = _registerProvider(providerOwner, "url1"); uint256 providerId2 = _registerProvider(providerOwner, "url2"); - uint256 swarmId = - _registerSwarm(fleetOwner, fleetId, providerId1, new bytes(50), 8, SwarmRegistryUniversalUpgradeable.TagType.GENERIC); + uint256 swarmId = _registerSwarm( + fleetOwner, fleetId, providerId1, new bytes(50), BITS_8, SwarmRegistryUniversalUpgradeable.TagType.GENERIC + ); // Update provider then delete vm.prank(fleetOwner); @@ -910,9 +924,12 @@ contract SwarmRegistryUniversalTest is Test { bytes memory filter3 = new bytes(50); filter3[0] = 0x03; - uint256 s1 = _registerSwarm(fleetOwner, fleetId, p1, filter1, 8, SwarmRegistryUniversalUpgradeable.TagType.GENERIC); - uint256 s2 = _registerSwarm(fleetOwner, fleetId, p2, filter2, 8, SwarmRegistryUniversalUpgradeable.TagType.GENERIC); - uint256 s3 = _registerSwarm(fleetOwner, fleetId, p3, filter3, 8, SwarmRegistryUniversalUpgradeable.TagType.GENERIC); + uint256 s1 = + _registerSwarm(fleetOwner, fleetId, p1, filter1, BITS_8, SwarmRegistryUniversalUpgradeable.TagType.GENERIC); + uint256 s2 = + _registerSwarm(fleetOwner, fleetId, p2, filter2, BITS_8, SwarmRegistryUniversalUpgradeable.TagType.GENERIC); + uint256 s3 = + _registerSwarm(fleetOwner, fleetId, p3, filter3, BITS_8, SwarmRegistryUniversalUpgradeable.TagType.GENERIC); // Verify initial indices assertEq(swarmRegistry.swarmIndexInUuid(s1), 0); @@ -935,8 +952,9 @@ contract SwarmRegistryUniversalTest is Test { function test_isSwarmValid_bothValid() public { uint256 fleetId = _registerFleet(fleetOwner, "f1"); uint256 providerId = _registerProvider(providerOwner, "url1"); - uint256 swarmId = - _registerSwarm(fleetOwner, fleetId, providerId, new bytes(50), 8, SwarmRegistryUniversalUpgradeable.TagType.GENERIC); + uint256 swarmId = _registerSwarm( + fleetOwner, fleetId, providerId, new bytes(50), BITS_8, SwarmRegistryUniversalUpgradeable.TagType.GENERIC + ); (bool fleetValid, bool providerValid) = swarmRegistry.isSwarmValid(swarmId); assertTrue(fleetValid); @@ -946,8 +964,9 @@ contract SwarmRegistryUniversalTest is Test { function test_isSwarmValid_providerBurned() public { uint256 fleetId = _registerFleet(fleetOwner, "f1"); uint256 providerId = _registerProvider(providerOwner, "url1"); - uint256 swarmId = - _registerSwarm(fleetOwner, fleetId, providerId, new bytes(50), 8, SwarmRegistryUniversalUpgradeable.TagType.GENERIC); + uint256 swarmId = _registerSwarm( + fleetOwner, fleetId, providerId, new bytes(50), BITS_8, SwarmRegistryUniversalUpgradeable.TagType.GENERIC + ); vm.prank(providerOwner); providerContract.burn(providerId); @@ -960,8 +979,9 @@ contract SwarmRegistryUniversalTest is Test { function test_isSwarmValid_fleetBurned() public { uint256 fleetId = _registerFleet(fleetOwner, "f1"); uint256 providerId = _registerProvider(providerOwner, "url1"); - uint256 swarmId = - _registerSwarm(fleetOwner, fleetId, providerId, new bytes(50), 8, SwarmRegistryUniversalUpgradeable.TagType.GENERIC); + uint256 swarmId = _registerSwarm( + fleetOwner, fleetId, providerId, new bytes(50), BITS_8, SwarmRegistryUniversalUpgradeable.TagType.GENERIC + ); // Burn registered fleet token (operator = owner for fresh registration) // This mints an owned-only token back to the owner @@ -983,8 +1003,9 @@ contract SwarmRegistryUniversalTest is Test { function test_isSwarmValid_bothBurned() public { uint256 fleetId = _registerFleet(fleetOwner, "f1"); uint256 providerId = _registerProvider(providerOwner, "url1"); - uint256 swarmId = - _registerSwarm(fleetOwner, fleetId, providerId, new bytes(50), 8, SwarmRegistryUniversalUpgradeable.TagType.GENERIC); + uint256 swarmId = _registerSwarm( + fleetOwner, fleetId, providerId, new bytes(50), BITS_8, SwarmRegistryUniversalUpgradeable.TagType.GENERIC + ); // Burn registered fleet token → mints owned-only token vm.prank(fleetOwner); @@ -1016,8 +1037,9 @@ contract SwarmRegistryUniversalTest is Test { function test_purgeOrphanedSwarm_providerBurned() public { uint256 fleetId = _registerFleet(fleetOwner, "f1"); uint256 providerId = _registerProvider(providerOwner, "url1"); - uint256 swarmId = - _registerSwarm(fleetOwner, fleetId, providerId, new bytes(50), 8, SwarmRegistryUniversalUpgradeable.TagType.GENERIC); + uint256 swarmId = _registerSwarm( + fleetOwner, fleetId, providerId, new bytes(50), BITS_8, SwarmRegistryUniversalUpgradeable.TagType.GENERIC + ); vm.prank(providerOwner); providerContract.burn(providerId); @@ -1035,8 +1057,9 @@ contract SwarmRegistryUniversalTest is Test { function test_purgeOrphanedSwarm_fleetBurned() public { uint256 fleetId = _registerFleet(fleetOwner, "f1"); uint256 providerId = _registerProvider(providerOwner, "url1"); - uint256 swarmId = - _registerSwarm(fleetOwner, fleetId, providerId, new bytes(50), 8, SwarmRegistryUniversalUpgradeable.TagType.GENERIC); + uint256 swarmId = _registerSwarm( + fleetOwner, fleetId, providerId, new bytes(50), BITS_8, SwarmRegistryUniversalUpgradeable.TagType.GENERIC + ); // Burn registered fleet token → mints owned-only token vm.prank(fleetOwner); @@ -1067,8 +1090,10 @@ contract SwarmRegistryUniversalTest is Test { bytes memory filter2 = new bytes(50); filter2[0] = 0x02; - uint256 s1 = _registerSwarm(fleetOwner, fleetId, p1, filter1, 8, SwarmRegistryUniversalUpgradeable.TagType.GENERIC); - uint256 s2 = _registerSwarm(fleetOwner, fleetId, p2, filter2, 8, SwarmRegistryUniversalUpgradeable.TagType.GENERIC); + uint256 s1 = + _registerSwarm(fleetOwner, fleetId, p1, filter1, BITS_8, SwarmRegistryUniversalUpgradeable.TagType.GENERIC); + uint256 s2 = + _registerSwarm(fleetOwner, fleetId, p2, filter2, BITS_8, SwarmRegistryUniversalUpgradeable.TagType.GENERIC); // Burn provider of s1 vm.prank(providerOwner); @@ -1092,8 +1117,9 @@ contract SwarmRegistryUniversalTest is Test { filter[i] = bytes1(uint8(i)); } - uint256 swarmId = - _registerSwarm(fleetOwner, fleetId, providerId, filter, 8, SwarmRegistryUniversalUpgradeable.TagType.GENERIC); + uint256 swarmId = _registerSwarm( + fleetOwner, fleetId, providerId, filter, BITS_8, SwarmRegistryUniversalUpgradeable.TagType.GENERIC + ); vm.prank(providerOwner); providerContract.burn(providerId); @@ -1114,8 +1140,9 @@ contract SwarmRegistryUniversalTest is Test { function test_RevertIf_purgeOrphanedSwarm_swarmNotOrphaned() public { uint256 fleetId = _registerFleet(fleetOwner, "f1"); uint256 providerId = _registerProvider(providerOwner, "url1"); - uint256 swarmId = - _registerSwarm(fleetOwner, fleetId, providerId, new bytes(50), 8, SwarmRegistryUniversalUpgradeable.TagType.GENERIC); + uint256 swarmId = _registerSwarm( + fleetOwner, fleetId, providerId, new bytes(50), BITS_8, SwarmRegistryUniversalUpgradeable.TagType.GENERIC + ); vm.expectRevert(SwarmRegistryUniversalUpgradeable.SwarmNotOrphaned.selector); swarmRegistry.purgeOrphanedSwarm(swarmId); @@ -1128,8 +1155,9 @@ contract SwarmRegistryUniversalTest is Test { function test_RevertIf_acceptSwarm_orphaned() public { uint256 fleetId = _registerFleet(fleetOwner, "f1"); uint256 providerId = _registerProvider(providerOwner, "url1"); - uint256 swarmId = - _registerSwarm(fleetOwner, fleetId, providerId, new bytes(50), 8, SwarmRegistryUniversalUpgradeable.TagType.GENERIC); + uint256 swarmId = _registerSwarm( + fleetOwner, fleetId, providerId, new bytes(50), BITS_8, SwarmRegistryUniversalUpgradeable.TagType.GENERIC + ); vm.prank(providerOwner); providerContract.burn(providerId); @@ -1142,8 +1170,9 @@ contract SwarmRegistryUniversalTest is Test { function test_RevertIf_rejectSwarm_orphaned() public { uint256 fleetId = _registerFleet(fleetOwner, "f1"); uint256 providerId = _registerProvider(providerOwner, "url1"); - uint256 swarmId = - _registerSwarm(fleetOwner, fleetId, providerId, new bytes(50), 8, SwarmRegistryUniversalUpgradeable.TagType.GENERIC); + uint256 swarmId = _registerSwarm( + fleetOwner, fleetId, providerId, new bytes(50), BITS_8, SwarmRegistryUniversalUpgradeable.TagType.GENERIC + ); // Burn registered fleet token → mints owned-only token vm.prank(fleetOwner); @@ -1163,8 +1192,9 @@ contract SwarmRegistryUniversalTest is Test { function test_RevertIf_checkMembership_orphaned() public { uint256 fleetId = _registerFleet(fleetOwner, "f1"); uint256 providerId = _registerProvider(providerOwner, "url1"); - uint256 swarmId = - _registerSwarm(fleetOwner, fleetId, providerId, new bytes(50), 8, SwarmRegistryUniversalUpgradeable.TagType.GENERIC); + uint256 swarmId = _registerSwarm( + fleetOwner, fleetId, providerId, new bytes(50), BITS_8, SwarmRegistryUniversalUpgradeable.TagType.GENERIC + ); vm.prank(providerOwner); providerContract.burn(providerId); @@ -1176,8 +1206,9 @@ contract SwarmRegistryUniversalTest is Test { function test_RevertIf_acceptSwarm_fleetBurned() public { uint256 fleetId = _registerFleet(fleetOwner, "f1"); uint256 providerId = _registerProvider(providerOwner, "url1"); - uint256 swarmId = - _registerSwarm(fleetOwner, fleetId, providerId, new bytes(50), 8, SwarmRegistryUniversalUpgradeable.TagType.GENERIC); + uint256 swarmId = _registerSwarm( + fleetOwner, fleetId, providerId, new bytes(50), BITS_8, SwarmRegistryUniversalUpgradeable.TagType.GENERIC + ); // Burn registered fleet token → mints owned-only token vm.prank(fleetOwner); @@ -1197,8 +1228,9 @@ contract SwarmRegistryUniversalTest is Test { function test_purge_thenAcceptReverts() public { uint256 fleetId = _registerFleet(fleetOwner, "f1"); uint256 providerId = _registerProvider(providerOwner, "url1"); - uint256 swarmId = - _registerSwarm(fleetOwner, fleetId, providerId, new bytes(50), 8, SwarmRegistryUniversalUpgradeable.TagType.GENERIC); + uint256 swarmId = _registerSwarm( + fleetOwner, fleetId, providerId, new bytes(50), BITS_8, SwarmRegistryUniversalUpgradeable.TagType.GENERIC + ); vm.prank(providerOwner); providerContract.burn(providerId); diff --git a/test/upgradeable/UpgradeableContracts.t.sol b/test/upgradeable/UpgradeableContracts.t.sol index c8a2f9dd..664e44c4 100644 --- a/test/upgradeable/UpgradeableContracts.t.sol +++ b/test/upgradeable/UpgradeableContracts.t.sol @@ -373,11 +373,11 @@ contract UpgradeableContractsTest is Test { // Register swarm with correct parameters bytes memory filterData = hex"0102030405"; - uint8 fingerprintSize = 16; + SwarmRegistryUniversalUpgradeable.FingerprintSize fpSize = SwarmRegistryUniversalUpgradeable.FingerprintSize.BITS_16; SwarmRegistryUniversalUpgradeable.TagType tagType = SwarmRegistryUniversalUpgradeable.TagType.IBEACON_PAYLOAD_ONLY; vm.prank(alice); - uint256 swarmId = swarmRegistry.registerSwarm(uuid, providerId, filterData, fingerprintSize, tagType); + uint256 swarmId = swarmRegistry.registerSwarm(uuid, providerId, filterData, fpSize, tagType); // Deploy V2 and upgrade SwarmRegistryUniversalUpgradeableV2Mock v2Impl = new SwarmRegistryUniversalUpgradeableV2Mock(); From 4227ba70087a068898a0d45c878c83cd6178207a Mon Sep 17 00:00:00 2001 From: Alex Sedighi Date: Fri, 6 Mar 2026 17:41:24 +1300 Subject: [PATCH 11/15] feat(swarms): add getFilterData to L1, fix CI lcov install, improve tests - Add getFilterData() function to SwarmRegistryL1Upgradeable for parity with Universal - Install lcov in CI workflow before running coverage report action (fixes genhtml missing error) - Add coverage tests for 8-bit fingerprint path and upgrade authorization - Fix test string identifiers for spellcheck compliance --- .github/workflows/checks.yml | 3 + src/swarms/SwarmRegistryL1Upgradeable.sol | 9 ++ test/SwarmRegistryL1.t.sol | 131 ++++++++++++++++++++++ test/SwarmRegistryUniversal.t.sol | 92 +++++++++++++++ 4 files changed, 235 insertions(+) diff --git a/.github/workflows/checks.yml b/.github/workflows/checks.yml index bfebae9d..baa8bfbf 100644 --- a/.github/workflows/checks.yml +++ b/.github/workflows/checks.yml @@ -62,6 +62,9 @@ jobs: path: coverage.lcov retention-days: 30 + - name: Install lcov + run: apt-get update && apt-get install -y lcov + - name: Report coverage to PR uses: zgosalvez/github-actions-report-lcov@v4 with: diff --git a/src/swarms/SwarmRegistryL1Upgradeable.sol b/src/swarms/SwarmRegistryL1Upgradeable.sol index 182474c5..e2b4219f 100644 --- a/src/swarms/SwarmRegistryL1Upgradeable.sol +++ b/src/swarms/SwarmRegistryL1Upgradeable.sol @@ -338,6 +338,15 @@ contract SwarmRegistryL1Upgradeable is Initializable, Ownable2StepUpgradeable, U emit SwarmPurged(swarmId, fleetUuid, msg.sender); } + /// @notice Returns the raw XOR filter bytes for a swarm. + function getFilterData(uint256 swarmId) external view returns (bytes memory) { + Swarm storage s = swarms[swarmId]; + if (s.filterPointer == address(0)) { + revert SwarmNotFound(); + } + return SSTORE2.read(s.filterPointer); + } + /// @notice Tests tag membership against the swarm's XOR filter. function checkMembership(uint256 swarmId, bytes32 tagHash) external view returns (bool isValid) { Swarm storage s = swarms[swarmId]; diff --git a/test/SwarmRegistryL1.t.sol b/test/SwarmRegistryL1.t.sol index d8ae263e..a68edd4e 100644 --- a/test/SwarmRegistryL1.t.sol +++ b/test/SwarmRegistryL1.t.sol @@ -1095,4 +1095,135 @@ contract SwarmRegistryL1Test is Test { vm.expectRevert(SwarmRegistryL1Upgradeable.SwarmNotFound.selector); swarmRegistry.acceptSwarm(swarmId); } + + // ============================== + // Additional Coverage Tests + // ============================== + + function test_checkMembership_8bit_forcedPath() public { + // This test ensures the 8-bit _readFingerprint path is exercised + uint256 fleetId = _registerFleet(fleetOwner, "f8bit"); + uint256 providerId = _registerProvider(providerOwner, "url8bit"); + + // Use 100 bytes for filter + uint256 filterLen = 100; + bytes memory filter = new bytes(filterLen); + + // For 8-bit, m = filterLen = 100 slots + bytes memory tagId = abi.encodePacked(uint256(0x12345678)); + bytes32 tagHash = keccak256(tagId); + uint32 m32 = uint32(filterLen); + + uint32 h1 = uint32(uint256(tagHash)) % m32; + uint32 h2 = uint32(uint256(tagHash) >> 32) % m32; + uint32 h3 = uint32(uint256(tagHash) >> 64) % m32; + + // If there are collisions, try different tag + if (h1 == h2 || h1 == h3 || h2 == h3) { + tagId = abi.encodePacked(uint256(0xABCDEF01)); + tagHash = keccak256(tagId); + h1 = uint32(uint256(tagHash)) % m32; + h2 = uint32(uint256(tagHash) >> 32) % m32; + h3 = uint32(uint256(tagHash) >> 64) % m32; + } + + // Calculate expected fingerprint (8-bit) + uint256 expectedFp = (uint256(tagHash) >> 96) & 0xFF; + + // Write fingerprint to h1 slot (f1 ^ 0 ^ 0 = expectedFp) + filter[h1] = bytes1(uint8(expectedFp)); + + uint256 swarmId = _registerSwarm( + fleetOwner, fleetId, providerId, filter, BITS_8, SwarmRegistryL1Upgradeable.TagType.GENERIC + ); + + // This should exercise the 8-bit path in _readFingerprint + bool result = swarmRegistry.checkMembership(swarmId, tagHash); + assertTrue(result, "8-bit membership check should pass"); + } + + function test_upgrade_ownerCanUpgrade() public { + // Deploy a new implementation + SwarmRegistryL1Upgradeable newImpl = new SwarmRegistryL1Upgradeable(); + + // Owner should be able to upgrade (tests _authorizeUpgrade) + vm.prank(contractOwner); + swarmRegistry.upgradeToAndCall(address(newImpl), ""); + + // Verify upgrade succeeded - contract still works + assertEq(address(swarmRegistry.FLEET_CONTRACT()), address(fleetContract)); + } + + function test_RevertIf_upgrade_notOwner() public { + SwarmRegistryL1Upgradeable newImpl = new SwarmRegistryL1Upgradeable(); + + vm.prank(caller); + vm.expectRevert(); + swarmRegistry.upgradeToAndCall(address(newImpl), ""); + } + + function test_checkMembership_mZero_16bit_returnsFalse() public { + // Edge case: filter too short for 16-bit -> m = 0 -> return false + uint256 fleetId = _registerFleet(fleetOwner, "f0"); + uint256 providerId = _registerProvider(providerOwner, "url0"); + + // 1 byte filter with 16-bit: m = 1/2 = 0 + bytes memory filter = new bytes(1); + uint256 swarmId = + _registerSwarm(fleetOwner, fleetId, providerId, filter, BITS_16, SwarmRegistryL1Upgradeable.TagType.GENERIC); + + // Should return false without reverting + assertFalse(swarmRegistry.checkMembership(swarmId, keccak256("anyTag"))); + } + + function test_RevertIf_acceptSwarm_fleetOwnerNotProvider() public { + uint256 fleetId = _registerFleet(fleetOwner, "f1"); + uint256 providerId = _registerProvider(providerOwner, "url1"); + uint256 swarmId = _registerSwarm( + fleetOwner, fleetId, providerId, new bytes(50), BITS_8, SwarmRegistryL1Upgradeable.TagType.GENERIC + ); + + // Fleet owner is not provider owner + vm.prank(fleetOwner); + vm.expectRevert(SwarmRegistryL1Upgradeable.NotProviderOwner.selector); + swarmRegistry.acceptSwarm(swarmId); + } + + function test_registerSwarm_zeroProviderId() public { + uint256 fleetId = _registerFleet(fleetOwner, "f1"); + + vm.prank(fleetOwner); + vm.expectRevert(SwarmRegistryL1Upgradeable.ProviderDoesNotExist.selector); + swarmRegistry.registerSwarm( + _getFleetUuid(fleetId), 0, new bytes(32), BITS_8, SwarmRegistryL1Upgradeable.TagType.GENERIC + ); + } + + // ============================== + // getFilterData + // ============================== + + function test_getFilterData() public { + uint256 fleetId = _registerFleet(fleetOwner, "f1"); + uint256 providerId = _registerProvider(providerOwner, "url1"); + + bytes memory filter = new bytes(100); + filter[0] = 0xFF; + filter[99] = 0x01; + + uint256 swarmId = _registerSwarm( + fleetOwner, fleetId, providerId, filter, BITS_16, SwarmRegistryL1Upgradeable.TagType.GENERIC + ); + + bytes memory stored = swarmRegistry.getFilterData(swarmId); + assertEq(stored.length, 100); + assertEq(uint8(stored[0]), 0xFF); + assertEq(uint8(stored[99]), 0x01); + } + + function test_RevertIf_getFilterData_swarmNotFound() public { + vm.expectRevert(SwarmRegistryL1Upgradeable.SwarmNotFound.selector); + swarmRegistry.getFilterData(999); + } + } diff --git a/test/SwarmRegistryUniversal.t.sol b/test/SwarmRegistryUniversal.t.sol index f96bd1b9..4ea27115 100644 --- a/test/SwarmRegistryUniversal.t.sol +++ b/test/SwarmRegistryUniversal.t.sol @@ -1243,4 +1243,96 @@ contract SwarmRegistryUniversalTest is Test { vm.expectRevert(SwarmRegistryUniversalUpgradeable.SwarmNotFound.selector); swarmRegistry.acceptSwarm(swarmId); } + + // ============================== + // Additional Coverage Tests + // ============================== + + function test_checkMembership_8bit_forcedPath() public { + // This test ensures the 8-bit _readFingerprint path is exercised + // Uses a tagId known to have non-colliding h1, h2, h3 for m=100 + uint256 fleetId = _registerFleet(fleetOwner, "f8bit"); + uint256 providerId = _registerProvider(providerOwner, "url8bit"); + + // Use 100 bytes for filter + uint256 filterLen = 100; + bytes memory filter = new bytes(filterLen); + + // For 8-bit, m = filterLen = 100 slots + // Pick a tag that's known to have distinct h1, h2, h3 + bytes memory tagId = abi.encodePacked(uint256(0x12345678)); + bytes32 tagHash = keccak256(tagId); + uint32 m32 = uint32(filterLen); + + uint32 h1 = uint32(uint256(tagHash)) % m32; + uint32 h2 = uint32(uint256(tagHash) >> 32) % m32; + uint32 h3 = uint32(uint256(tagHash) >> 64) % m32; + + // If there are collisions, try different tag + if (h1 == h2 || h1 == h3 || h2 == h3) { + tagId = abi.encodePacked(uint256(0xABCDEF01)); + tagHash = keccak256(tagId); + h1 = uint32(uint256(tagHash)) % m32; + h2 = uint32(uint256(tagHash) >> 32) % m32; + h3 = uint32(uint256(tagHash) >> 64) % m32; + } + + // Calculate expected fingerprint (8-bit) + uint256 expectedFp = (uint256(tagHash) >> 96) & 0xFF; + + // Write fingerprint to h1 slot (f1 ^ 0 ^ 0 = expectedFp) + filter[h1] = bytes1(uint8(expectedFp)); + + uint256 swarmId = _registerSwarm( + fleetOwner, fleetId, providerId, filter, BITS_8, SwarmRegistryUniversalUpgradeable.TagType.GENERIC + ); + + // This should exercise the 8-bit path in _readFingerprint + bool result = swarmRegistry.checkMembership(swarmId, tagHash); + assertTrue(result, "8-bit membership check should pass"); + } + + function test_upgrade_ownerCanUpgrade() public { + // Deploy a new implementation + SwarmRegistryUniversalUpgradeable newImpl = new SwarmRegistryUniversalUpgradeable(); + + // Owner should be able to upgrade (tests _authorizeUpgrade) + vm.prank(contractOwner); + swarmRegistry.upgradeToAndCall(address(newImpl), ""); + + // Verify upgrade succeeded - contract still works + assertEq(address(swarmRegistry.FLEET_CONTRACT()), address(fleetContract)); + } + + function test_RevertIf_upgrade_notOwner() public { + SwarmRegistryUniversalUpgradeable newImpl = new SwarmRegistryUniversalUpgradeable(); + + vm.prank(caller); + vm.expectRevert(); + swarmRegistry.upgradeToAndCall(address(newImpl), ""); + } + + function test_checkMembership_mZero_16bit_returnsFalse() public { + // Edge case: filter too short for 16-bit -> m = 0 -> return false + uint256 fleetId = _registerFleet(fleetOwner, "f0"); + uint256 providerId = _registerProvider(providerOwner, "url0"); + + // 1 byte filter with 16-bit: m = 1/2 = 0 + bytes memory filter = new bytes(1); + uint256 swarmId = + _registerSwarm(fleetOwner, fleetId, providerId, filter, BITS_16, SwarmRegistryUniversalUpgradeable.TagType.GENERIC); + + // Should return false without reverting + assertFalse(swarmRegistry.checkMembership(swarmId, keccak256("anyTag"))); + } + + function test_registerSwarm_zeroProviderId() public { + uint256 fleetId = _registerFleet(fleetOwner, "f1"); + + vm.prank(fleetOwner); + vm.expectRevert(SwarmRegistryUniversalUpgradeable.ProviderDoesNotExist.selector); + swarmRegistry.registerSwarm( + _getFleetUuid(fleetId), 0, new bytes(32), BITS_8, SwarmRegistryUniversalUpgradeable.TagType.GENERIC + ); + } } From 0fca64c87e22ea54ef8f995585126b47750836b4 Mon Sep 17 00:00:00 2001 From: Alex Sedighi Date: Fri, 6 Mar 2026 18:04:20 +1300 Subject: [PATCH 12/15] fix: use regular anvil for L1 upgrade test, add V1 initializer checks - SwarmRegistryL1Upgradeable uses SSTORE2 which relies on EXTCODECOPY - EXTCODECOPY is not supported on ZkSync Era, causing tx drops - Updated test to use regular anvil instead of anvil-zksync - Added Phase 1B to verify V1 initializers run correctly - Updated README with correct instructions and warnings --- test/upgrade-demo/README.md | 62 +++++++++++++++------- test/upgrade-demo/TestUpgradeOnAnvil.s.sol | 53 ++++++++++++++++-- 2 files changed, 92 insertions(+), 23 deletions(-) diff --git a/test/upgrade-demo/README.md b/test/upgrade-demo/README.md index 1f68f053..128fbea0 100644 --- a/test/upgrade-demo/README.md +++ b/test/upgrade-demo/README.md @@ -12,8 +12,9 @@ The V2 contracts (`FleetIdentityUpgradeableV2`, `ServiceProviderUpgradeableV2`, ## Prerequisites -1. **anvil-zksync** installed (comes with foundry-zksync) -2. Contracts compiled with optimizer enabled (see `foundry.toml`) +1. **anvil** installed (comes with foundry) for L1 mode +2. **anvil-zksync** installed (comes with foundry-zksync) for ZkSync mode +3. Contracts compiled with optimizer enabled (see `foundry.toml`) --- @@ -57,16 +58,19 @@ cast balance 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266 --rpc-url http://127.0.0 ## Running the Upgrade Test -### Option A: L1 Mode (Ethereum Mainnet Simulation) +### Option A: L1 Mode (Ethereum Mainnet Simulation) — Recommended Use this for testing `SwarmRegistryL1Upgradeable` which uses SSTORE2. +> **Important:** SSTORE2 relies on `EXTCODECOPY` which is not supported on ZkSync Era. +> You must use regular `anvil`, not `anvil-zksync --zksync-os`. + ```bash # 1. Stop any existing anvil lsof -ti:8545 | xargs kill -9 2>/dev/null || true -# 2. Start anvil-zksync in L1 mode -~/.foundry/bin/anvil-zksync --host 127.0.0.1 --port 8545 +# 2. Start regular anvil (NOT anvil-zksync with --zksync-os) +anvil --host 127.0.0.1 --port 8545 # 3. Verify it's running cast chain-id --rpc-url http://127.0.0.1:8545 @@ -84,17 +88,21 @@ lsof -ti:8545 | xargs kill -9 Use this for testing with full ZkSync system contracts. +> **Note:** This test script uses `SwarmRegistryL1Upgradeable` which is incompatible with ZkSync +> due to SSTORE2's reliance on `EXTCODECOPY`. For ZkSync, use `SwarmRegistryUniversalUpgradeable` instead. + ```bash # 1. Stop any existing anvil lsof -ti:8545 | xargs kill -9 2>/dev/null || true # 2. Start anvil-zksync with ZkSync OS enabled -~/.foundry/bin/anvil-zksync --host 127.0.0.1 --port 8545 --zksync +~/.foundry/bin/anvil-zksync --host 127.0.0.1 --port 8545 --zksync-os # 3. Verify it's running (chain ID will be 260 for ZkSync) cast chain-id --rpc-url http://127.0.0.1:8545 # 4. Run the test with --zksync flag (in another terminal) +# NOTE: This will FAIL with SwarmRegistryL1 - use SwarmRegistryUniversal for ZkSync forge script test/upgrade-demo/TestUpgradeOnAnvil.s.sol:TestUpgradeOnAnvil \ --rpc-url http://127.0.0.1:8545 \ --broadcast \ @@ -110,22 +118,22 @@ Use this for testing L1↔L2 interactions (not needed for basic upgrade demo). ```bash # Start with both L1 and ZkSync -~/.foundry/bin/anvil-zksync --host 127.0.0.1 --port 8545 --l1 --zksync +~/.foundry/bin/anvil-zksync --host 127.0.0.1 --port 8545 --l1 --zksync-os ``` --- ## Quick Reference -| Task | Command | -| ----------------- | ------------------------------------------------------------------- | -| Check if running | `cast chain-id --rpc-url http://127.0.0.1:8545` | -| Check port usage | `lsof -i :8545` | -| Kill on port 8545 | `lsof -ti:8545 \| xargs kill -9` | -| Kill all anvil | `pkill -f anvil` | -| Start L1 mode | `~/.foundry/bin/anvil-zksync --host 127.0.0.1 --port 8545` | -| Start ZkSync mode | `~/.foundry/bin/anvil-zksync --host 127.0.0.1 --port 8545 --zksync` | -| Health check | `cast block-number --rpc-url http://127.0.0.1:8545` | +| Task | Command | +| ----------------- | ---------------------------------------------------------------------- | +| Check if running | `cast chain-id --rpc-url http://127.0.0.1:8545` | +| Check port usage | `lsof -i :8545` | +| Kill on port 8545 | `lsof -ti:8545 \| xargs kill -9` | +| Kill all anvil | `pkill -f anvil` | +| Start L1 mode | `anvil --host 127.0.0.1 --port 8545` | +| Start ZkSync mode | `~/.foundry/bin/anvil-zksync --host 127.0.0.1 --port 8545 --zksync-os` | +| Health check | `cast block-number --rpc-url http://127.0.0.1:8545` | --- @@ -134,9 +142,10 @@ Use this for testing L1↔L2 interactions (not needed for basic upgrade demo). The script will: 1. **Deploy V1 contracts** - ServiceProvider, FleetIdentity, SwarmRegistryL1 (all via ERC1967 proxies) -2. **Create state** - Register a provider URL and a fleet with bond -3. **Upgrade to V2** - Deploy V2 implementations and call `upgradeToAndCall()` -4. **Verify success** - Check `version()` returns "2.0.0" and all state is preserved +2. **Verify V1 initializers** - Check owner, ERC721 metadata, bond parameters, and contract references +3. **Create state** - Register a provider URL and a fleet with bond +4. **Upgrade to V2** - Deploy V2 implementations and call `upgradeToAndCall()` +5. **Verify success** - Check `version()` returns "2.0.0" and all state is preserved ``` === PHASE 1: Deploy V1 Contracts === @@ -145,6 +154,21 @@ The script will: FleetIdentity Proxy: 0x... SwarmRegistry Proxy: 0x... +=== PHASE 1B: Verify V1 Initializers === + ServiceProvider V1 Initialization: + owner: 0x... [OK] + name: Swarm Service Provider [OK] + symbol: SSV [OK] + FleetIdentity V1 Initialization: + owner: 0x... [OK] + BOND_TOKEN: 0x... [OK] + BASE_BOND: 100000000000000000000 [OK] + countryBondMultiplier: 16 [OK] + SwarmRegistry V1 Initialization: + owner: 0x... [OK] + FLEET_CONTRACT: 0x... [OK] + PROVIDER_CONTRACT: 0x... [OK] + === PHASE 2: Create State === Registered Provider: Token ID: ... Registered Fleet: Token ID: ... diff --git a/test/upgrade-demo/TestUpgradeOnAnvil.s.sol b/test/upgrade-demo/TestUpgradeOnAnvil.s.sol index 77001a3b..51212a01 100644 --- a/test/upgrade-demo/TestUpgradeOnAnvil.s.sol +++ b/test/upgrade-demo/TestUpgradeOnAnvil.s.sol @@ -55,9 +55,12 @@ contract MockBondToken is ERC20 { * @title TestUpgradeOnAnvil * @notice End-to-end script to deploy, use, upgrade, and verify swarm contracts on anvil. * + * @dev NOTE: This script uses SwarmRegistryL1Upgradeable which relies on SSTORE2 (EXTCODECOPY). + * SSTORE2 is NOT compatible with ZkSync Era. Use regular anvil, not anvil-zksync. + * * Usage: - * 1. Start anvil-zksync in a separate terminal: - * ~/.foundry/bin/anvil-zksync --host 127.0.0.1 --port 8545 + * 1. Start regular anvil in a separate terminal: + * anvil --host 127.0.0.1 --port 8545 * * 2. Run this script: * forge script test/upgrade-demo/TestUpgradeOnAnvil.s.sol:TestUpgradeOnAnvil \ @@ -128,13 +131,55 @@ contract TestUpgradeOnAnvil is Script { console.log(" SwarmRegistry Proxy:", swarmRegistryProxy); console.log(" SwarmRegistry Impl V1:", address(srImpl)); + // ═══════════════════════════════════════════ + // PHASE 1B: Verify V1 Initializers + // ═══════════════════════════════════════════ + console.log("\n=== PHASE 1B: Verify V1 Initializers ===\n"); + + // Verify ServiceProvider initialization + ServiceProviderUpgradeable sp = ServiceProviderUpgradeable(serviceProviderProxy); + console.log("ServiceProvider V1 Initialization:"); + require(sp.owner() == deployer, "SP: owner not initialized correctly"); + console.log(" owner:", sp.owner(), "[OK]"); + require(keccak256(bytes(sp.name())) == keccak256(bytes("Swarm Service Provider")), "SP: name mismatch"); + console.log(" name:", sp.name(), "[OK]"); + require(keccak256(bytes(sp.symbol())) == keccak256(bytes("SSV")), "SP: symbol mismatch"); + console.log(" symbol:", sp.symbol(), "[OK]"); + + // Verify FleetIdentity initialization + FleetIdentityUpgradeable fi = FleetIdentityUpgradeable(fleetIdentityProxy); + console.log("\nFleetIdentity V1 Initialization:"); + require(fi.owner() == deployer, "FI: owner not initialized correctly"); + console.log(" owner:", fi.owner(), "[OK]"); + require(address(fi.BOND_TOKEN()) == address(bondToken), "FI: BOND_TOKEN mismatch"); + console.log(" BOND_TOKEN:", address(fi.BOND_TOKEN()), "[OK]"); + require(fi.BASE_BOND() == baseBond, "FI: BASE_BOND mismatch"); + console.log(" BASE_BOND:", fi.BASE_BOND(), "[OK]"); + require(fi.countryBondMultiplier() == 16, "FI: countryBondMultiplier mismatch"); + console.log(" countryBondMultiplier:", fi.countryBondMultiplier(), "[OK]"); + require(keccak256(bytes(fi.name())) == keccak256(bytes("Swarm Fleet Identity")), "FI: name mismatch"); + console.log(" name:", fi.name(), "[OK]"); + require(keccak256(bytes(fi.symbol())) == keccak256(bytes("SFID")), "FI: symbol mismatch"); + console.log(" symbol:", fi.symbol(), "[OK]"); + + // Verify SwarmRegistry initialization + SwarmRegistryL1Upgradeable sr = SwarmRegistryL1Upgradeable(swarmRegistryProxy); + console.log("\nSwarmRegistry V1 Initialization:"); + require(sr.owner() == deployer, "SR: owner not initialized correctly"); + console.log(" owner:", sr.owner(), "[OK]"); + require(address(sr.FLEET_CONTRACT()) == fleetIdentityProxy, "SR: FLEET_CONTRACT mismatch"); + console.log(" FLEET_CONTRACT:", address(sr.FLEET_CONTRACT()), "[OK]"); + require(address(sr.PROVIDER_CONTRACT()) == serviceProviderProxy, "SR: PROVIDER_CONTRACT mismatch"); + console.log(" PROVIDER_CONTRACT:", address(sr.PROVIDER_CONTRACT()), "[OK]"); + + console.log("\nAll V1 initializers verified successfully!"); + // ═══════════════════════════════════════════ // PHASE 2: Create State (register provider & fleet) // ═══════════════════════════════════════════ console.log("\n=== PHASE 2: Create State ===\n"); // Register a provider - ServiceProviderUpgradeable sp = ServiceProviderUpgradeable(serviceProviderProxy); string memory providerUrl = "https://api.example.com"; providerTokenId = sp.registerProvider(providerUrl); console.log("Registered Provider:"); @@ -143,7 +188,6 @@ contract TestUpgradeOnAnvil is Script { console.log(" Owner:", sp.ownerOf(providerTokenId)); // Approve bond token for fleet - FleetIdentityUpgradeable fi = FleetIdentityUpgradeable(fleetIdentityProxy); bondToken.approve(fleetIdentityProxy, type(uint256).max); console.log("\nBond token approved for FleetIdentity"); @@ -238,6 +282,7 @@ contract TestUpgradeOnAnvil is Script { console.log(" UPGRADE TEST COMPLETED SUCCESSFULLY"); console.log("========================================"); console.log("- All V1 contracts deployed"); + console.log("- V1 initializers verified (owner, params, ERC721)"); console.log("- State created (provider + fleet)"); console.log("- Upgraded to V2 implementations"); console.log("- Version functions return '2.0.0'"); From a2d3950fc5c20f53052eebb6f2fb7c2bae35f4fc Mon Sep 17 00:00:00 2001 From: Alex Sedighi Date: Fri, 6 Mar 2026 18:48:43 +1300 Subject: [PATCH 13/15] feat(swarms): add interfaces for UUPS upgradeable contracts - Create src/swarms/interfaces/ directory with: - SwarmTypes.sol: shared enums (RegistrationLevel, SwarmStatus, TagType, FingerprintSize) for use by contracts and external integrators - IFleetIdentity.sol: public interface for FleetIdentityUpgradeable - IServiceProvider.sol: public interface for ServiceProviderUpgradeable - ISwarmRegistry.sol: common interface for SwarmRegistry variants - Update contracts to import shared enums from SwarmTypes.sol instead of defining them inline, reducing duplication - Update tests to use imported enums from SwarmTypes.sol This follows the recommended UUPS pattern of defining public interfaces separately to make the expected API surface explicit across upgrades. --- src/swarms/FleetIdentityUpgradeable.sol | 14 +- src/swarms/SwarmRegistryL1Upgradeable.sol | 32 +- .../SwarmRegistryUniversalUpgradeable.sol | 33 +- src/swarms/interfaces/IFleetIdentity.sol | 345 ++++++++++++++++++ src/swarms/interfaces/IServiceProvider.sol | 55 +++ src/swarms/interfaces/ISwarmRegistry.sol | 152 ++++++++ src/swarms/interfaces/SwarmTypes.sol | 47 +++ test/SwarmRegistryL1.t.sol | 211 +++++------ test/SwarmRegistryUniversal.t.sol | 219 ++++++----- test/upgradeable/UpgradeableContracts.t.sol | 5 +- 10 files changed, 830 insertions(+), 283 deletions(-) create mode 100644 src/swarms/interfaces/IFleetIdentity.sol create mode 100644 src/swarms/interfaces/IServiceProvider.sol create mode 100644 src/swarms/interfaces/ISwarmRegistry.sol create mode 100644 src/swarms/interfaces/SwarmTypes.sol diff --git a/src/swarms/FleetIdentityUpgradeable.sol b/src/swarms/FleetIdentityUpgradeable.sol index 4c18089b..daa40891 100644 --- a/src/swarms/FleetIdentityUpgradeable.sol +++ b/src/swarms/FleetIdentityUpgradeable.sol @@ -11,6 +11,8 @@ import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; import {ReentrancyGuard} from "solady/utils/ReentrancyGuard.sol"; +import {RegistrationLevel} from "./interfaces/SwarmTypes.sol"; + /** * @title FleetIdentityUpgradeable * @notice UUPS-upgradeable ERC-721 with ERC721Enumerable representing ownership of a BLE fleet, @@ -82,18 +84,6 @@ contract FleetIdentityUpgradeable is error InvalidMultiplier(); error InvalidBondToken(); - // ────────────────────────────────────────────── - // Enums - // ────────────────────────────────────────────── - - /// @notice Registration level for a UUID. - enum RegistrationLevel { - None, // 0 - not registered (default) - Owned, // 1 - owned but not registered in any region - Local, // 2 - admin area (local) level - Country // 3 - country level - } - // ────────────────────────────────────────────── // Constants // ────────────────────────────────────────────── diff --git a/src/swarms/SwarmRegistryL1Upgradeable.sol b/src/swarms/SwarmRegistryL1Upgradeable.sol index e2b4219f..eb5c92b3 100644 --- a/src/swarms/SwarmRegistryL1Upgradeable.sol +++ b/src/swarms/SwarmRegistryL1Upgradeable.sol @@ -10,14 +10,9 @@ import {UUPSUpgradeable} from "@openzeppelin/contracts-upgradeable/proxy/utils/U import {Initializable} from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol"; import {ReentrancyGuard} from "solady/utils/ReentrancyGuard.sol"; -// Import interfaces only - the registry stores proxy addresses -interface IFleetIdentity { - function uuidOwner(bytes16 uuid) external view returns (address); -} - -interface IServiceProvider { - function ownerOf(uint256 tokenId) external view returns (address); -} +import {IFleetIdentity} from "./interfaces/IFleetIdentity.sol"; +import {IServiceProvider} from "./interfaces/IServiceProvider.sol"; +import {SwarmStatus, TagType, FingerprintSize} from "./interfaces/SwarmTypes.sol"; /** * @title SwarmRegistryL1Upgradeable @@ -55,26 +50,8 @@ contract SwarmRegistryL1Upgradeable is Initializable, Ownable2StepUpgradeable, U error SwarmOrphaned(); // ────────────────────────────────────────────── - // Enums & Structs + // Structs (L1-specific: uses SSTORE2 pointer) // ────────────────────────────────────────────── - enum SwarmStatus { - REGISTERED, - ACCEPTED, - REJECTED - } - - enum TagType { - IBEACON_PAYLOAD_ONLY, // 0x00: proxUUID || major || minor - IBEACON_INCLUDES_MAC, // 0x01: proxUUID || major || minor || MAC (Normalized) - VENDOR_ID, // 0x02: companyID || hash(vendorBytes) - GENERIC // 0x03 - } - - /// @notice Fingerprint size for XOR filter (8-bit or 16-bit only for gas efficiency) - enum FingerprintSize { - BITS_8, // 8-bit fingerprints (1 byte each) - BITS_16 // 16-bit fingerprints (2 bytes each) - } struct Swarm { bytes16 fleetUuid; @@ -121,6 +98,7 @@ contract SwarmRegistryL1Upgradeable is Initializable, Ownable2StepUpgradeable, U // ────────────────────────────────────────────── // Events // ────────────────────────────────────────────── + event SwarmRegistered(uint256 indexed swarmId, bytes16 indexed fleetUuid, uint256 indexed providerId, address owner); event SwarmStatusChanged(uint256 indexed swarmId, SwarmStatus status); event SwarmProviderUpdated(uint256 indexed swarmId, uint256 indexed oldProvider, uint256 indexed newProvider); diff --git a/src/swarms/SwarmRegistryUniversalUpgradeable.sol b/src/swarms/SwarmRegistryUniversalUpgradeable.sol index ff0baa8e..381a9330 100644 --- a/src/swarms/SwarmRegistryUniversalUpgradeable.sol +++ b/src/swarms/SwarmRegistryUniversalUpgradeable.sol @@ -7,14 +7,9 @@ import {UUPSUpgradeable} from "@openzeppelin/contracts-upgradeable/proxy/utils/U import {Initializable} from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol"; import {ReentrancyGuard} from "solady/utils/ReentrancyGuard.sol"; -// Import interfaces only - the registry stores proxy addresses -interface IFleetIdentity { - function uuidOwner(bytes16 uuid) external view returns (address); -} - -interface IServiceProvider { - function ownerOf(uint256 tokenId) external view returns (address); -} +import {IFleetIdentity} from "./interfaces/IFleetIdentity.sol"; +import {IServiceProvider} from "./interfaces/IServiceProvider.sol"; +import {SwarmStatus, TagType, FingerprintSize} from "./interfaces/SwarmTypes.sol"; /** * @title SwarmRegistryUniversalUpgradeable @@ -58,26 +53,8 @@ contract SwarmRegistryUniversalUpgradeable is error SwarmOrphaned(); // ────────────────────────────────────────────── - // Enums & Structs + // Structs (Universal-specific: uses native bytes storage) // ────────────────────────────────────────────── - enum SwarmStatus { - REGISTERED, - ACCEPTED, - REJECTED - } - - enum TagType { - IBEACON_PAYLOAD_ONLY, // 0x00: proxUUID || major || minor - IBEACON_INCLUDES_MAC, // 0x01: proxUUID || major || minor || MAC (Normalized) - VENDOR_ID, // 0x02: companyID || hash(vendorBytes) - GENERIC // 0x03 - } - - /// @notice Fingerprint size for XOR filter (8-bit or 16-bit only for gas efficiency) - enum FingerprintSize { - BITS_8, // 8-bit fingerprints (1 byte each) - BITS_16 // 16-bit fingerprints (2 bytes each) - } struct Swarm { bytes16 fleetUuid; @@ -130,6 +107,8 @@ contract SwarmRegistryUniversalUpgradeable is // ────────────────────────────────────────────── // Events // ────────────────────────────────────────────── + + /// @dev SwarmRegistered with filterSize param (Universal-specific). event SwarmRegistered( uint256 indexed swarmId, bytes16 indexed fleetUuid, uint256 indexed providerId, address owner, uint32 filterSize ); diff --git a/src/swarms/interfaces/IFleetIdentity.sol b/src/swarms/interfaces/IFleetIdentity.sol new file mode 100644 index 00000000..c0d6f007 --- /dev/null +++ b/src/swarms/interfaces/IFleetIdentity.sol @@ -0,0 +1,345 @@ +// SPDX-License-Identifier: BSD-3-Clause-Clear + +pragma solidity ^0.8.24; + +import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import {RegistrationLevel} from "./SwarmTypes.sol"; + +/** + * @title IFleetIdentity + * @notice Interface for FleetIdentity — an ERC-721 with ERC721Enumerable representing + * ownership of a BLE fleet, secured by an ERC-20 bond organized into geometric tiers. + * + * @dev This interface defines the public contract surface that all FleetIdentity + * implementations must uphold across upgrades (UUPS pattern). + * + * **Two-level geographic registration** + * + * Fleets register at exactly one level: + * - Country — regionKey = countryCode (ISO 3166-1 numeric, 1-999) + * - Admin Area — regionKey = (countryCode << 10) | adminCode (>= 1024) + * + * Each regionKey has its **own independent tier namespace** — tier indices + * start at 0 for every region. + * + * **Economic Model** + * + * - Tier capacity: 10 members per tier (unified across levels) + * - Local bond: BASE_BOND * 2^tier + * - Country bond: BASE_BOND * COUNTRY_BOND_MULTIPLIER * 2^tier (16× local) + * + * **TokenID Encoding** + * + * TokenID = (regionKey << 128) | uuid + * - Bits 0-127: UUID (bytes16 Proximity UUID) + * - Bits 128-159: Region key (32-bit country or admin-area code) + */ +interface IFleetIdentity { + // ══════════════════════════════════════════════ + // Events + // ══════════════════════════════════════════════ + + /// @notice Emitted when a fleet is registered in a region. + /// @param owner The address that owns the UUID. + /// @param uuid The fleet's proximity UUID. + /// @param tokenId The minted NFT token ID. + /// @param regionKey The region key (country or admin-area). + /// @param tierIndex The tier the fleet was registered at. + /// @param bondAmount The total bond amount paid. + /// @param operator The operator address for tier management. + event FleetRegistered( + address indexed owner, + bytes16 indexed uuid, + uint256 indexed tokenId, + uint32 regionKey, + uint256 tierIndex, + uint256 bondAmount, + address operator + ); + + /// @notice Emitted when an operator is changed for a UUID. + /// @param uuid The UUID whose operator changed. + /// @param oldOperator The previous operator address. + /// @param newOperator The new operator address. + /// @param tierExcessTransferred The tier bonds transferred between operators. + event OperatorSet( + bytes16 indexed uuid, address indexed oldOperator, address indexed newOperator, uint256 tierExcessTransferred + ); + + /// @notice Emitted when a fleet is promoted to a higher tier. + /// @param tokenId The token ID of the promoted fleet. + /// @param fromTier The original tier index. + /// @param toTier The new (higher) tier index. + /// @param additionalBond The additional bond paid for promotion. + event FleetPromoted( + uint256 indexed tokenId, uint256 indexed fromTier, uint256 indexed toTier, uint256 additionalBond + ); + + /// @notice Emitted when a fleet is demoted to a lower tier. + /// @param tokenId The token ID of the demoted fleet. + /// @param fromTier The original tier index. + /// @param toTier The new (lower) tier index. + /// @param bondRefund The bond amount refunded. + event FleetDemoted(uint256 indexed tokenId, uint256 indexed fromTier, uint256 indexed toTier, uint256 bondRefund); + + /// @notice Emitted when a fleet NFT is burned. + /// @param owner The former owner of the token. + /// @param tokenId The burned token ID. + /// @param regionKey The region the fleet was registered in. + /// @param tierIndex The tier the fleet was in. + /// @param bondRefund The bond amount refunded. + event FleetBurned( + address indexed owner, uint256 indexed tokenId, uint32 indexed regionKey, uint256 tierIndex, uint256 bondRefund + ); + + /// @notice Emitted when a UUID is claimed in owned-only mode. + /// @param owner The address that claimed the UUID. + /// @param uuid The claimed UUID. + /// @param operator The operator address assigned. + event UuidClaimed(address indexed owner, bytes16 indexed uuid, address indexed operator); + + // ══════════════════════════════════════════════ + // Registration Functions + // ══════════════════════════════════════════════ + + /// @notice Register a fleet under a country at a specific tier. + /// @param uuid The proximity UUID (must be non-zero). + /// @param countryCode ISO 3166-1 numeric country code (1-999). + /// @param targetTier The tier to register at. + /// @return tokenId The minted NFT token ID. + function registerFleetCountry(bytes16 uuid, uint16 countryCode, uint256 targetTier) + external + returns (uint256 tokenId); + + /// @notice Register a fleet under a country + admin area at a specific tier. + /// @param uuid The proximity UUID (must be non-zero). + /// @param countryCode ISO 3166-1 numeric country code (1-999). + /// @param adminCode Admin-area code within the country (1-255). + /// @param targetTier The tier to register at. + /// @return tokenId The minted NFT token ID. + function registerFleetLocal(bytes16 uuid, uint16 countryCode, uint16 adminCode, uint256 targetTier) + external + returns (uint256 tokenId); + + /// @notice Claim ownership of a UUID without registering in any region. + /// @param uuid The proximity UUID (must be non-zero, unclaimed). + /// @param operator The operator address for future tier management. + /// @return tokenId The minted NFT token ID (uses UUID as low 128 bits). + function claimUuid(bytes16 uuid, address operator) external returns (uint256 tokenId); + + // ══════════════════════════════════════════════ + // Tier Management + // ══════════════════════════════════════════════ + + /// @notice Promotes a fleet to the next tier within its region. + /// @param tokenId The token ID to promote. + function promote(uint256 tokenId) external; + + /// @notice Moves a fleet to a different tier within its region. + /// @param tokenId The token ID to reassign. + /// @param targetTier The target tier (higher = promote, lower = demote). + function reassignTier(uint256 tokenId, uint256 targetTier) external; + + /// @notice Burns the fleet NFT and refunds the bond. + /// @param tokenId The token ID to burn. + function burn(uint256 tokenId) external; + + // ══════════════════════════════════════════════ + // Operator Management + // ══════════════════════════════════════════════ + + /// @notice Sets or changes the operator for a UUID. + /// @param uuid The UUID to update. + /// @param newOperator The new operator address. + function setOperator(bytes16 uuid, address newOperator) external; + + // ══════════════════════════════════════════════ + // View Functions: Bond & Tier Helpers + // ══════════════════════════════════════════════ + + /// @notice Bond required for tier K at current parameters. + /// @param tier The tier index. + /// @param isCountry True for country-level, false for local. + /// @return The bond amount required. + function tierBond(uint256 tier, bool isCountry) external view returns (uint256); + + /// @notice Returns the cheapest tier for local inclusion. + /// @param countryCode ISO 3166-1 numeric country code. + /// @param adminCode Admin-area code within the country. + /// @return inclusionTier The tier that would be included in bundles. + /// @return bond The bond required for that tier. + function localInclusionHint(uint16 countryCode, uint16 adminCode) + external + view + returns (uint256 inclusionTier, uint256 bond); + + /// @notice Returns the cheapest tier for country inclusion. + /// @param countryCode ISO 3166-1 numeric country code. + /// @return inclusionTier The tier that would be included in bundles. + /// @return bond The bond required for that tier. + function countryInclusionHint(uint16 countryCode) external view returns (uint256 inclusionTier, uint256 bond); + + /// @notice Highest non-empty tier in a region, or 0 if none. + /// @param regionKey The region to query. + /// @return The highest active tier index. + function highestActiveTier(uint32 regionKey) external view returns (uint256); + + /// @notice Number of members in a specific tier of a region. + /// @param regionKey The region to query. + /// @param tier The tier index. + /// @return The member count. + function tierMemberCount(uint32 regionKey, uint256 tier) external view returns (uint256); + + /// @notice All token IDs in a specific tier of a region. + /// @param regionKey The region to query. + /// @param tier The tier index. + /// @return Array of token IDs. + function getTierMembers(uint32 regionKey, uint256 tier) external view returns (uint256[] memory); + + /// @notice All UUIDs in a specific tier of a region. + /// @param regionKey The region to query. + /// @param tier The tier index. + /// @return uuids Array of UUIDs. + function getTierUuids(uint32 regionKey, uint256 tier) external view returns (bytes16[] memory uuids); + + /// @notice Bond amount for a token. + /// @param tokenId The token to query. + /// @return The current bond amount. + function bonds(uint256 tokenId) external view returns (uint256); + + /// @notice Returns true if the UUID is in owned-only state. + /// @param uuid The UUID to check. + /// @return True if owned but not registered in any region. + function isOwnedOnly(bytes16 uuid) external view returns (bool); + + /// @notice Returns the effective operator for a UUID. + /// @param uuid The UUID to query. + /// @return operator The operator address (defaults to owner if not set). + function operatorOf(bytes16 uuid) external view returns (address operator); + + /// @notice Returns the country bond multiplier. + /// @return The multiplier (e.g., 16 means country bonds are 16× local). + function countryBondMultiplier() external view returns (uint256); + + // ══════════════════════════════════════════════ + // View Functions: EdgeBeaconScanner Discovery + // ══════════════════════════════════════════════ + + /// @notice Builds a priority-ordered bundle of up to 20 UUIDs. + /// @param countryCode ISO 3166-1 numeric country code. + /// @param adminCode Admin-area code within the country. + /// @return uuids Array of UUIDs ordered by tier (highest first). + /// @return count Number of UUIDs in the bundle. + function buildHighestBondedUuidBundle(uint16 countryCode, uint16 adminCode) + external + view + returns (bytes16[] memory uuids, uint256 count); + + /// @notice Builds a bundle containing ONLY country-level fleets. + /// @param countryCode ISO 3166-1 numeric country code. + /// @return uuids Array of country-level UUIDs. + /// @return count Number of UUIDs in the bundle. + function buildCountryOnlyBundle(uint16 countryCode) external view returns (bytes16[] memory uuids, uint256 count); + + // ══════════════════════════════════════════════ + // View Functions: Region Indexes + // ══════════════════════════════════════════════ + + /// @notice Returns all country codes that have at least one active fleet. + /// @return Array of ISO 3166-1 numeric country codes. + function getActiveCountries() external view returns (uint16[] memory); + + /// @notice Returns all admin-area region keys across all countries. + /// @return Array of encoded region keys (countryCode << 10 | adminCode). + function getActiveAdminAreas() external view returns (uint32[] memory); + + /// @notice Returns all active admin-area region keys for a specific country. + /// @param countryCode ISO 3166-1 numeric country code. + /// @return Array of encoded region keys for that country. + function getCountryAdminAreas(uint16 countryCode) external view returns (uint32[] memory); + + // ══════════════════════════════════════════════ + // Pure Functions: Token & Region Helpers + // ══════════════════════════════════════════════ + + /// @notice UUID for a token ID. + /// @param tokenId The token ID to decode. + /// @return The UUID (low 128 bits). + function tokenUuid(uint256 tokenId) external pure returns (bytes16); + + /// @notice Region key encoded in a token ID. + /// @param tokenId The token ID to decode. + /// @return The region key (bits 128-159). + function tokenRegion(uint256 tokenId) external pure returns (uint32); + + /// @notice Computes the deterministic token ID for a uuid+region pair. + /// @param uuid The proximity UUID. + /// @param regionKey The region key. + /// @return The computed token ID. + function computeTokenId(bytes16 uuid, uint32 regionKey) external pure returns (uint256); + + /// @notice Encodes a country code and admin code into a region key. + /// @param countryCode ISO 3166-1 numeric country code (1-999). + /// @param adminCode Admin-area code within the country (1-255). + /// @return Encoded region key: (countryCode << 10) | adminCode. + function makeAdminRegion(uint16 countryCode, uint16 adminCode) external pure returns (uint32); + + // ══════════════════════════════════════════════ + // Public Getters + // ══════════════════════════════════════════════ + + /// @notice Returns the bond token address. + function BOND_TOKEN() external view returns (IERC20); + + /// @notice Returns the base bond amount. + function BASE_BOND() external view returns (uint256); + + /// @notice UUID -> address that first registered a token for this UUID. + function uuidOwner(bytes16 uuid) external view returns (address); + + /// @notice UUID -> count of active tokens for this UUID (across all regions). + function uuidTokenCount(bytes16 uuid) external view returns (uint256); + + /// @notice UUID -> registration level. + function uuidLevel(bytes16 uuid) external view returns (RegistrationLevel); + + /// @notice UUID -> operator address for tier maintenance. + function uuidOperator(bytes16 uuid) external view returns (address); + + /// @notice UUID -> total tier bonds across all registered regions. + function uuidTotalTierBonds(bytes16 uuid) external view returns (uint256); + + /// @notice regionKey -> number of tiers opened in that region. + function regionTierCount(uint32 regionKey) external view returns (uint256); + + /// @notice Token ID -> tier index (within its region) the fleet belongs to. + function fleetTier(uint256 tokenId) external view returns (uint256); + + /// @notice tokenId -> tier-0 equivalent bond paid at registration. + function tokenTier0Bond(uint256 tokenId) external view returns (uint256); + + /// @notice UUID -> ownership bond paid at claim/first-registration. + function uuidOwnershipBondPaid(bytes16 uuid) external view returns (uint256); + + // ══════════════════════════════════════════════ + // Constants + // ══════════════════════════════════════════════ + + /// @notice Unified tier capacity for all levels. + function TIER_CAPACITY() external view returns (uint256); + + /// @notice Default country bond multiplier when not explicitly set (16× local). + function DEFAULT_COUNTRY_BOND_MULTIPLIER() external view returns (uint256); + + /// @notice Default base bond for tier 0. + function DEFAULT_BASE_BOND() external view returns (uint256); + + /// @notice Hard cap on tier count per region. + function MAX_TIERS() external view returns (uint256); + + /// @notice Maximum UUIDs returned by buildHighestBondedUuidBundle. + function MAX_BONDED_UUID_BUNDLE_SIZE() external view returns (uint256); + + /// @notice Region key for owned-only UUIDs (not registered in any region). + function OWNED_REGION_KEY() external view returns (uint32); +} diff --git a/src/swarms/interfaces/IServiceProvider.sol b/src/swarms/interfaces/IServiceProvider.sol new file mode 100644 index 00000000..3e89f883 --- /dev/null +++ b/src/swarms/interfaces/IServiceProvider.sol @@ -0,0 +1,55 @@ +// SPDX-License-Identifier: BSD-3-Clause-Clear + +pragma solidity ^0.8.24; + +/** + * @title IServiceProvider + * @notice Interface for ServiceProvider — an ERC-721 representing ownership of a service endpoint URL. + * @dev This interface defines the public contract surface that all ServiceProvider + * implementations must uphold across upgrades (UUPS pattern). + * + * TokenID = keccak256(url), guaranteeing one owner per URL. + */ +interface IServiceProvider { + // ══════════════════════════════════════════════ + // Events + // ══════════════════════════════════════════════ + + /// @notice Emitted when a new service provider URL is registered. + /// @param owner The address that registered the provider. + /// @param url The service endpoint URL. + /// @param tokenId The minted NFT token ID (derived from URL hash). + event ProviderRegistered(address indexed owner, string url, uint256 indexed tokenId); + + /// @notice Emitted when a provider NFT is burned. + /// @param owner The former owner of the token. + /// @param tokenId The burned token ID. + event ProviderBurned(address indexed owner, uint256 indexed tokenId); + + // ══════════════════════════════════════════════ + // Core Functions + // ══════════════════════════════════════════════ + + /// @notice Mints a new provider NFT for the given URL. + /// @param url The backend service URL (must be unique, non-empty). + /// @return tokenId The deterministic token ID derived from `url`. + function registerProvider(string calldata url) external returns (uint256 tokenId); + + /// @notice Burns the provider NFT. Caller must be the token owner. + /// @param tokenId The provider token ID to burn. + function burn(uint256 tokenId) external; + + // ══════════════════════════════════════════════ + // View Functions + // ══════════════════════════════════════════════ + + /// @notice Maps TokenID -> Provider URL. + /// @param tokenId The token to query. + /// @return The provider URL string. + function providerUrls(uint256 tokenId) external view returns (string memory); + + /// @notice Returns the owner of the specified token ID (ERC-721). + /// @param tokenId The token ID to query. + /// @return The address of the token owner. + function ownerOf(uint256 tokenId) external view returns (address); +} diff --git a/src/swarms/interfaces/ISwarmRegistry.sol b/src/swarms/interfaces/ISwarmRegistry.sol new file mode 100644 index 00000000..ba1b39cc --- /dev/null +++ b/src/swarms/interfaces/ISwarmRegistry.sol @@ -0,0 +1,152 @@ +// SPDX-License-Identifier: BSD-3-Clause-Clear + +pragma solidity ^0.8.24; + +import {IFleetIdentity} from "./IFleetIdentity.sol"; +import {IServiceProvider} from "./IServiceProvider.sol"; +import {SwarmStatus, TagType, FingerprintSize} from "./SwarmTypes.sol"; + +/** + * @title ISwarmRegistry + * @notice Interface for SwarmRegistry — a permissionless BLE swarm registry. + * @dev This interface defines the public contract surface that all SwarmRegistry + * implementations must uphold across upgrades (UUPS pattern). + * + * There are two implementations: + * - SwarmRegistryL1Upgradeable: Uses SSTORE2 for filter storage (L1 only, not ZkSync compatible) + * - SwarmRegistryUniversalUpgradeable: Uses native bytes storage (cross-chain compatible) + * + * Both implementations share the same public interface defined here. + * The `swarms()` mapping getter returns implementation-specific struct layouts + * and is NOT included in this interface. + */ +interface ISwarmRegistry { + // ══════════════════════════════════════════════ + // Events + // ══════════════════════════════════════════════ + + /// @notice Emitted when a new swarm is registered. + /// @param swarmId The unique swarm identifier. + /// @param fleetUuid The fleet UUID this swarm belongs to. + /// @param providerId The service provider NFT ID. + /// @param owner The address that registered the swarm. + /// @dev Note: SwarmRegistryUniversal also emits filterSize as a 4th non-indexed param. + event SwarmRegistered(uint256 indexed swarmId, bytes16 indexed fleetUuid, uint256 indexed providerId, address owner); + + /// @notice Emitted when a swarm's status changes. + /// @param swarmId The swarm identifier. + /// @param status The new status (REGISTERED, ACCEPTED, REJECTED). + event SwarmStatusChanged(uint256 indexed swarmId, SwarmStatus status); + + /// @notice Emitted when a swarm's provider is updated. + /// @param swarmId The swarm identifier. + /// @param oldProvider The previous provider ID. + /// @param newProvider The new provider ID. + event SwarmProviderUpdated(uint256 indexed swarmId, uint256 indexed oldProvider, uint256 indexed newProvider); + + /// @notice Emitted when a swarm is deleted by its owner. + /// @param swarmId The deleted swarm identifier. + /// @param fleetUuid The fleet UUID the swarm belonged to. + /// @param owner The address that deleted the swarm. + event SwarmDeleted(uint256 indexed swarmId, bytes16 indexed fleetUuid, address indexed owner); + + /// @notice Emitted when an orphaned swarm is purged. + /// @param swarmId The purged swarm identifier. + /// @param fleetUuid The fleet UUID the swarm belonged to. + /// @param purgedBy The address that called purge. + event SwarmPurged(uint256 indexed swarmId, bytes16 indexed fleetUuid, address indexed purgedBy); + + // ══════════════════════════════════════════════ + // Pure Functions + // ══════════════════════════════════════════════ + + /// @notice Derives a deterministic swarm ID. + /// @param fleetUuid The fleet UUID. + /// @param filter The XOR filter data. + /// @param fpSize Fingerprint size (BITS_8 or BITS_16). + /// @param tagType The tag type classification. + /// @return The computed swarm ID. + function computeSwarmId(bytes16 fleetUuid, bytes calldata filter, FingerprintSize fpSize, TagType tagType) + external + pure + returns (uint256); + + // ══════════════════════════════════════════════ + // Core Functions + // ══════════════════════════════════════════════ + + /// @notice Registers a new swarm. Caller must own the fleet UUID. + /// @param fleetUuid The fleet UUID (must be owned by caller). + /// @param providerId The service provider NFT ID. + /// @param filter The XOR filter data. + /// @param fpSize Fingerprint size (BITS_8 or BITS_16). + /// @param tagType The tag type for this swarm. + /// @return swarmId The registered swarm's unique identifier. + function registerSwarm( + bytes16 fleetUuid, + uint256 providerId, + bytes calldata filter, + FingerprintSize fpSize, + TagType tagType + ) external returns (uint256 swarmId); + + /// @notice Approves a swarm. Caller must own the provider NFT. + /// @param swarmId The swarm to accept. + function acceptSwarm(uint256 swarmId) external; + + /// @notice Rejects a swarm. Caller must own the provider NFT. + /// @param swarmId The swarm to reject. + function rejectSwarm(uint256 swarmId) external; + + /// @notice Reassigns the service provider. Resets status to REGISTERED. + /// @param swarmId The swarm to update. + /// @param newProviderId The new service provider NFT ID. + function updateSwarmProvider(uint256 swarmId, uint256 newProviderId) external; + + /// @notice Permanently deletes a swarm. Caller must own the fleet UUID. + /// @param swarmId The swarm to delete. + function deleteSwarm(uint256 swarmId) external; + + /// @notice Permissionless-ly removes an orphaned swarm. + /// @dev A swarm is orphaned if its fleet UUID or provider NFT no longer exists. + /// @param swarmId The orphaned swarm to purge. + function purgeOrphanedSwarm(uint256 swarmId) external; + + // ══════════════════════════════════════════════ + // View Functions + // ══════════════════════════════════════════════ + + /// @notice Returns the FleetIdentity contract address. + function FLEET_CONTRACT() external view returns (IFleetIdentity); + + /// @notice Returns the ServiceProvider contract address. + function PROVIDER_CONTRACT() external view returns (IServiceProvider); + + /// @notice Returns whether the swarm's fleet UUID and provider NFT are still valid. + /// @param swarmId The swarm to check. + /// @return fleetValid True if the fleet UUID owner exists. + /// @return providerValid True if the provider NFT exists. + function isSwarmValid(uint256 swarmId) external view returns (bool fleetValid, bool providerValid); + + /// @notice Returns the raw XOR filter bytes for a swarm. + /// @param swarmId The swarm to query. + /// @return The filter data bytes. + function getFilterData(uint256 swarmId) external view returns (bytes memory); + + /// @notice Tests tag membership against the swarm's XOR filter. + /// @param swarmId The swarm to query. + /// @param tagHash The keccak256 hash of the tag to test. + /// @return isValid True if the tag is probably a member of the filter. + function checkMembership(uint256 swarmId, bytes32 tagHash) external view returns (bool isValid); + + /// @notice UUID -> List of SwarmIDs at a given index. + /// @param fleetUuid The fleet UUID. + /// @param index The array index. + /// @return The swarm ID at that index. + function uuidSwarms(bytes16 fleetUuid, uint256 index) external view returns (uint256); + + /// @notice SwarmID -> index in uuidSwarms[fleetUuid] (for O(1) removal). + /// @param swarmId The swarm ID. + /// @return The index in the UUID's swarm array. + function swarmIndexInUuid(uint256 swarmId) external view returns (uint256); +} diff --git a/src/swarms/interfaces/SwarmTypes.sol b/src/swarms/interfaces/SwarmTypes.sol new file mode 100644 index 00000000..a0ea30e7 --- /dev/null +++ b/src/swarms/interfaces/SwarmTypes.sol @@ -0,0 +1,47 @@ +// SPDX-License-Identifier: BSD-3-Clause-Clear + +pragma solidity ^0.8.24; + +/** + * @title SwarmTypes + * @notice Shared type definitions for the Swarm system contracts. + * @dev Solidity interfaces cannot define enums, so shared enums live here. + * Import this file alongside interfaces when type definitions are needed. + */ + +// ══════════════════════════════════════════════ +// FleetIdentity Types +// ══════════════════════════════════════════════ + +/// @notice Registration level for a UUID in FleetIdentity. +enum RegistrationLevel { + None, // 0 - not registered (default) + Owned, // 1 - owned but not registered in any region + Local, // 2 - admin area (local) level + Country // 3 - country level +} + +// ══════════════════════════════════════════════ +// SwarmRegistry Types +// ══════════════════════════════════════════════ + +/// @notice Status of a swarm registration. +enum SwarmStatus { + REGISTERED, // 0 - registered but awaiting provider approval + ACCEPTED, // 1 - approved by the service provider + REJECTED // 2 - rejected by the service provider +} + +/// @notice Tag type classification for BLE beacons. +enum TagType { + IBEACON_PAYLOAD_ONLY, // 0x00: proxUUID || major || minor + IBEACON_INCLUDES_MAC, // 0x01: proxUUID || major || minor || MAC (Normalized) + VENDOR_ID, // 0x02: companyID || hash(vendorBytes) + GENERIC // 0x03: generic tag type +} + +/// @notice Fingerprint size for XOR filter (8-bit or 16-bit only for gas efficiency). +enum FingerprintSize { + BITS_8, // 8-bit fingerprints (1 byte each) + BITS_16 // 16-bit fingerprints (2 bytes each) +} diff --git a/test/SwarmRegistryL1.t.sol b/test/SwarmRegistryL1.t.sol index a68edd4e..c51d5291 100644 --- a/test/SwarmRegistryL1.t.sol +++ b/test/SwarmRegistryL1.t.sol @@ -6,6 +6,7 @@ import {ERC1967Proxy} from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.s import "../src/swarms/SwarmRegistryL1Upgradeable.sol"; import {FleetIdentityUpgradeable} from "../src/swarms/FleetIdentityUpgradeable.sol"; import {ServiceProviderUpgradeable} from "../src/swarms/ServiceProviderUpgradeable.sol"; +import {SwarmStatus, TagType, FingerprintSize} from "../src/swarms/interfaces/SwarmTypes.sol"; import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; contract MockBondTokenL1 is ERC20 { @@ -34,11 +35,11 @@ contract SwarmRegistryL1Test is Test { uint16 constant ADMIN_CA = 6; // California // Alias for FingerprintSize enum - SwarmRegistryL1Upgradeable.FingerprintSize constant BITS_8 = SwarmRegistryL1Upgradeable.FingerprintSize.BITS_8; - SwarmRegistryL1Upgradeable.FingerprintSize constant BITS_16 = SwarmRegistryL1Upgradeable.FingerprintSize.BITS_16; + FingerprintSize constant BITS_8 = FingerprintSize.BITS_8; + FingerprintSize constant BITS_16 = FingerprintSize.BITS_16; event SwarmRegistered(uint256 indexed swarmId, bytes16 indexed fleetUuid, uint256 indexed providerId, address owner); - event SwarmStatusChanged(uint256 indexed swarmId, SwarmRegistryL1Upgradeable.SwarmStatus status); + event SwarmStatusChanged(uint256 indexed swarmId, SwarmStatus status); event SwarmProviderUpdated(uint256 indexed swarmId, uint256 indexed oldProvider, uint256 indexed newProvider); event SwarmDeleted(uint256 indexed swarmId, bytes16 indexed fleetUuid, address indexed owner); event SwarmPurged(uint256 indexed swarmId, bytes16 indexed fleetUuid, address indexed purgedBy); @@ -99,8 +100,8 @@ contract SwarmRegistryL1Test is Test { uint256 fleetId, uint256 providerId, bytes memory filter, - SwarmRegistryL1Upgradeable.FingerprintSize fpSize, - SwarmRegistryL1Upgradeable.TagType tagType + FingerprintSize fpSize, + TagType tagType ) internal returns (uint256) { bytes16 fleetUuid = _getFleetUuid(fleetId); vm.prank(owner); @@ -111,13 +112,13 @@ contract SwarmRegistryL1Test is Test { function getExpectedValues( bytes memory tagId, uint256 m, - SwarmRegistryL1Upgradeable.FingerprintSize fpSize + FingerprintSize fpSize ) public pure returns (uint32 h1, uint32 h2, uint32 h3, uint256 fp) { bytes32 h = keccak256(tagId); h1 = uint32(uint256(h)) % uint32(m); h2 = uint32(uint256(h) >> 32) % uint32(m); h3 = uint32(uint256(h) >> 64) % uint32(m); - uint256 fpMask = fpSize == SwarmRegistryL1Upgradeable.FingerprintSize.BITS_8 ? 0xFF : 0xFFFF; + uint256 fpMask = fpSize == FingerprintSize.BITS_8 ? 0xFF : 0xFFFF; fp = (uint256(h) >> 96) & fpMask; } @@ -176,12 +177,12 @@ contract SwarmRegistryL1Test is Test { uint256 providerId = _registerProvider(providerOwner, "https://api.example.com"); uint256 swarmId = _registerSwarm( - fleetOwner, fleetId, providerId, new bytes(100), BITS_16, SwarmRegistryL1Upgradeable.TagType.IBEACON_INCLUDES_MAC + fleetOwner, fleetId, providerId, new bytes(100), BITS_16, TagType.IBEACON_INCLUDES_MAC ); // Swarm ID is deterministic hash of (fleetUuid, filter, fpSize, tagType) uint256 expectedId = swarmRegistry.computeSwarmId( - _getFleetUuid(fleetId), new bytes(100), BITS_16, SwarmRegistryL1Upgradeable.TagType.IBEACON_INCLUDES_MAC + _getFleetUuid(fleetId), new bytes(100), BITS_16, TagType.IBEACON_INCLUDES_MAC ); assertEq(swarmId, expectedId); } @@ -191,23 +192,23 @@ contract SwarmRegistryL1Test is Test { uint256 providerId = _registerProvider(providerOwner, "url1"); uint256 swarmId = - _registerSwarm(fleetOwner, fleetId, providerId, new bytes(50), BITS_8, SwarmRegistryL1Upgradeable.TagType.VENDOR_ID); + _registerSwarm(fleetOwner, fleetId, providerId, new bytes(50), BITS_8, TagType.VENDOR_ID); ( bytes16 storedFleetUuid, uint256 storedProviderId, address filterPointer, - SwarmRegistryL1Upgradeable.FingerprintSize storedFpSize, - SwarmRegistryL1Upgradeable.TagType storedTagType, - SwarmRegistryL1Upgradeable.SwarmStatus storedStatus + FingerprintSize storedFpSize, + TagType storedTagType, + SwarmStatus storedStatus ) = swarmRegistry.swarms(swarmId); assertEq(storedFleetUuid, _getFleetUuid(fleetId)); assertEq(storedProviderId, providerId); assertTrue(filterPointer != address(0)); assertEq(uint8(storedFpSize), uint8(BITS_8)); - assertEq(uint8(storedTagType), uint8(SwarmRegistryL1Upgradeable.TagType.VENDOR_ID)); - assertEq(uint8(storedStatus), uint8(SwarmRegistryL1Upgradeable.SwarmStatus.REGISTERED)); + assertEq(uint8(storedTagType), uint8(TagType.VENDOR_ID)); + assertEq(uint8(storedStatus), uint8(SwarmStatus.REGISTERED)); } function test_registerSwarm_deterministicId() public { @@ -217,10 +218,10 @@ contract SwarmRegistryL1Test is Test { bytes memory filter = new bytes(32); uint256 expectedId = - swarmRegistry.computeSwarmId(_getFleetUuid(fleetId), filter, BITS_8, SwarmRegistryL1Upgradeable.TagType.GENERIC); + swarmRegistry.computeSwarmId(_getFleetUuid(fleetId), filter, BITS_8, TagType.GENERIC); uint256 swarmId = - _registerSwarm(fleetOwner, fleetId, providerId, filter, BITS_8, SwarmRegistryL1Upgradeable.TagType.GENERIC); + _registerSwarm(fleetOwner, fleetId, providerId, filter, BITS_8, TagType.GENERIC); assertEq(swarmId, expectedId); } @@ -228,12 +229,12 @@ contract SwarmRegistryL1Test is Test { uint256 fleetId = _registerFleet(fleetOwner, "f1"); uint256 providerId = _registerProvider(providerOwner, "url1"); - _registerSwarm(fleetOwner, fleetId, providerId, new bytes(32), BITS_8, SwarmRegistryL1Upgradeable.TagType.GENERIC); + _registerSwarm(fleetOwner, fleetId, providerId, new bytes(32), BITS_8, TagType.GENERIC); vm.prank(fleetOwner); vm.expectRevert(SwarmRegistryL1Upgradeable.SwarmAlreadyExists.selector); swarmRegistry.registerSwarm( - _getFleetUuid(fleetId), providerId, new bytes(32), BITS_8, SwarmRegistryL1Upgradeable.TagType.GENERIC + _getFleetUuid(fleetId), providerId, new bytes(32), BITS_8, TagType.GENERIC ); } @@ -243,13 +244,13 @@ contract SwarmRegistryL1Test is Test { bytes memory filter = new bytes(50); uint256 expectedId = swarmRegistry.computeSwarmId( - _getFleetUuid(fleetId), filter, BITS_16, SwarmRegistryL1Upgradeable.TagType.GENERIC + _getFleetUuid(fleetId), filter, BITS_16, TagType.GENERIC ); vm.expectEmit(true, true, true, true); emit SwarmRegistered(expectedId, _getFleetUuid(fleetId), providerId, fleetOwner); - _registerSwarm(fleetOwner, fleetId, providerId, filter, BITS_16, SwarmRegistryL1Upgradeable.TagType.GENERIC); + _registerSwarm(fleetOwner, fleetId, providerId, filter, BITS_16, TagType.GENERIC); } function test_registerSwarm_linksUuidSwarms() public { @@ -264,9 +265,9 @@ contract SwarmRegistryL1Test is Test { filter2[0] = 0x02; uint256 swarmId1 = - _registerSwarm(fleetOwner, fleetId, providerId1, filter1, BITS_8, SwarmRegistryL1Upgradeable.TagType.GENERIC); + _registerSwarm(fleetOwner, fleetId, providerId1, filter1, BITS_8, TagType.GENERIC); uint256 swarmId2 = - _registerSwarm(fleetOwner, fleetId, providerId2, filter2, BITS_8, SwarmRegistryL1Upgradeable.TagType.GENERIC); + _registerSwarm(fleetOwner, fleetId, providerId2, filter2, BITS_8, TagType.GENERIC); assertEq(swarmRegistry.uuidSwarms(_getFleetUuid(fleetId), 0), swarmId1); assertEq(swarmRegistry.uuidSwarms(_getFleetUuid(fleetId), 1), swarmId2); @@ -280,27 +281,27 @@ contract SwarmRegistryL1Test is Test { uint256 providerId = _registerProvider(providerOwner, "url"); uint256 s1 = _registerSwarm( - fleetOwner, fleetId1, providerId, new bytes(32), BITS_8, SwarmRegistryL1Upgradeable.TagType.IBEACON_PAYLOAD_ONLY + fleetOwner, fleetId1, providerId, new bytes(32), BITS_8, TagType.IBEACON_PAYLOAD_ONLY ); uint256 s2 = _registerSwarm( - fleetOwner, fleetId2, providerId, new bytes(32), BITS_8, SwarmRegistryL1Upgradeable.TagType.IBEACON_INCLUDES_MAC + fleetOwner, fleetId2, providerId, new bytes(32), BITS_8, TagType.IBEACON_INCLUDES_MAC ); uint256 s3 = _registerSwarm( - fleetOwner, fleetId3, providerId, new bytes(32), BITS_8, SwarmRegistryL1Upgradeable.TagType.VENDOR_ID + fleetOwner, fleetId3, providerId, new bytes(32), BITS_8, TagType.VENDOR_ID ); uint256 s4 = _registerSwarm( - fleetOwner, fleetId4, providerId, new bytes(32), BITS_8, SwarmRegistryL1Upgradeable.TagType.GENERIC + fleetOwner, fleetId4, providerId, new bytes(32), BITS_8, TagType.GENERIC ); - (,,,, SwarmRegistryL1Upgradeable.TagType t1,) = swarmRegistry.swarms(s1); - (,,,, SwarmRegistryL1Upgradeable.TagType t2,) = swarmRegistry.swarms(s2); - (,,,, SwarmRegistryL1Upgradeable.TagType t3,) = swarmRegistry.swarms(s3); - (,,,, SwarmRegistryL1Upgradeable.TagType t4,) = swarmRegistry.swarms(s4); + (,,,, TagType t1,) = swarmRegistry.swarms(s1); + (,,,, TagType t2,) = swarmRegistry.swarms(s2); + (,,,, TagType t3,) = swarmRegistry.swarms(s3); + (,,,, TagType t4,) = swarmRegistry.swarms(s4); - assertEq(uint8(t1), uint8(SwarmRegistryL1Upgradeable.TagType.IBEACON_PAYLOAD_ONLY)); - assertEq(uint8(t2), uint8(SwarmRegistryL1Upgradeable.TagType.IBEACON_INCLUDES_MAC)); - assertEq(uint8(t3), uint8(SwarmRegistryL1Upgradeable.TagType.VENDOR_ID)); - assertEq(uint8(t4), uint8(SwarmRegistryL1Upgradeable.TagType.GENERIC)); + assertEq(uint8(t1), uint8(TagType.IBEACON_PAYLOAD_ONLY)); + assertEq(uint8(t2), uint8(TagType.IBEACON_INCLUDES_MAC)); + assertEq(uint8(t3), uint8(TagType.VENDOR_ID)); + assertEq(uint8(t4), uint8(TagType.GENERIC)); } function test_registerSwarm_bothFingerprintSizes() public { @@ -309,14 +310,14 @@ contract SwarmRegistryL1Test is Test { uint256 providerId = _registerProvider(providerOwner, "url"); uint256 s1 = _registerSwarm( - fleetOwner, fleetId1, providerId, new bytes(32), BITS_8, SwarmRegistryL1Upgradeable.TagType.GENERIC + fleetOwner, fleetId1, providerId, new bytes(32), BITS_8, TagType.GENERIC ); uint256 s2 = _registerSwarm( - fleetOwner, fleetId2, providerId, new bytes(64), BITS_16, SwarmRegistryL1Upgradeable.TagType.GENERIC + fleetOwner, fleetId2, providerId, new bytes(64), BITS_16, TagType.GENERIC ); - (,,, SwarmRegistryL1Upgradeable.FingerprintSize fp1,,) = swarmRegistry.swarms(s1); - (,,, SwarmRegistryL1Upgradeable.FingerprintSize fp2,,) = swarmRegistry.swarms(s2); + (,,, FingerprintSize fp1,,) = swarmRegistry.swarms(s1); + (,,, FingerprintSize fp2,,) = swarmRegistry.swarms(s2); assertEq(uint8(fp1), uint8(BITS_8)); assertEq(uint8(fp2), uint8(BITS_16)); @@ -332,7 +333,7 @@ contract SwarmRegistryL1Test is Test { vm.prank(caller); vm.expectRevert(SwarmRegistryL1Upgradeable.NotUuidOwner.selector); swarmRegistry.registerSwarm( - _getFleetUuid(fleetId), 1, new bytes(10), BITS_16, SwarmRegistryL1Upgradeable.TagType.GENERIC + _getFleetUuid(fleetId), 1, new bytes(10), BITS_16, TagType.GENERIC ); } @@ -342,7 +343,7 @@ contract SwarmRegistryL1Test is Test { vm.prank(fleetOwner); vm.expectRevert(SwarmRegistryL1Upgradeable.InvalidUuid.selector); swarmRegistry.registerSwarm( - bytes16(0), providerId, new bytes(32), BITS_8, SwarmRegistryL1Upgradeable.TagType.GENERIC + bytes16(0), providerId, new bytes(32), BITS_8, TagType.GENERIC ); } @@ -353,7 +354,7 @@ contract SwarmRegistryL1Test is Test { vm.prank(fleetOwner); vm.expectRevert(SwarmRegistryL1Upgradeable.ProviderDoesNotExist.selector); swarmRegistry.registerSwarm( - _getFleetUuid(fleetId), nonExistentProvider, new bytes(32), BITS_8, SwarmRegistryL1Upgradeable.TagType.GENERIC + _getFleetUuid(fleetId), nonExistentProvider, new bytes(32), BITS_8, TagType.GENERIC ); } @@ -364,7 +365,7 @@ contract SwarmRegistryL1Test is Test { vm.prank(fleetOwner); vm.expectRevert(SwarmRegistryL1Upgradeable.InvalidFilterSize.selector); swarmRegistry.registerSwarm( - _getFleetUuid(fleetId), providerId, new bytes(0), BITS_8, SwarmRegistryL1Upgradeable.TagType.GENERIC + _getFleetUuid(fleetId), providerId, new bytes(0), BITS_8, TagType.GENERIC ); } @@ -375,7 +376,7 @@ contract SwarmRegistryL1Test is Test { vm.prank(fleetOwner); vm.expectRevert(SwarmRegistryL1Upgradeable.InvalidFilterSize.selector); swarmRegistry.registerSwarm( - _getFleetUuid(fleetId), providerId, new bytes(24577), BITS_8, SwarmRegistryL1Upgradeable.TagType.GENERIC + _getFleetUuid(fleetId), providerId, new bytes(24577), BITS_8, TagType.GENERIC ); } @@ -385,7 +386,7 @@ contract SwarmRegistryL1Test is Test { // Exactly 24576 bytes should succeed uint256 swarmId = _registerSwarm( - fleetOwner, fleetId, providerId, new bytes(24576), BITS_8, SwarmRegistryL1Upgradeable.TagType.GENERIC + fleetOwner, fleetId, providerId, new bytes(24576), BITS_8, TagType.GENERIC ); assertTrue(swarmId != 0); } @@ -396,7 +397,7 @@ contract SwarmRegistryL1Test is Test { // 1 byte filter uint256 swarmId = _registerSwarm( - fleetOwner, fleetId, providerId, new bytes(1), BITS_8, SwarmRegistryL1Upgradeable.TagType.GENERIC + fleetOwner, fleetId, providerId, new bytes(1), BITS_8, TagType.GENERIC ); assertTrue(swarmId != 0); } @@ -409,39 +410,39 @@ contract SwarmRegistryL1Test is Test { uint256 fleetId = _registerFleet(fleetOwner, "f1"); uint256 providerId = _registerProvider(providerOwner, "url1"); uint256 swarmId = - _registerSwarm(fleetOwner, fleetId, providerId, new bytes(50), BITS_8, SwarmRegistryL1Upgradeable.TagType.GENERIC); + _registerSwarm(fleetOwner, fleetId, providerId, new bytes(50), BITS_8, TagType.GENERIC); vm.expectEmit(true, true, true, true); - emit SwarmStatusChanged(swarmId, SwarmRegistryL1Upgradeable.SwarmStatus.ACCEPTED); + emit SwarmStatusChanged(swarmId, SwarmStatus.ACCEPTED); vm.prank(providerOwner); swarmRegistry.acceptSwarm(swarmId); - (,,,,, SwarmRegistryL1Upgradeable.SwarmStatus status) = swarmRegistry.swarms(swarmId); - assertEq(uint8(status), uint8(SwarmRegistryL1Upgradeable.SwarmStatus.ACCEPTED)); + (,,,,, SwarmStatus status) = swarmRegistry.swarms(swarmId); + assertEq(uint8(status), uint8(SwarmStatus.ACCEPTED)); } function test_rejectSwarm_setsStatusAndEmits() public { uint256 fleetId = _registerFleet(fleetOwner, "f1"); uint256 providerId = _registerProvider(providerOwner, "url1"); uint256 swarmId = - _registerSwarm(fleetOwner, fleetId, providerId, new bytes(50), BITS_8, SwarmRegistryL1Upgradeable.TagType.GENERIC); + _registerSwarm(fleetOwner, fleetId, providerId, new bytes(50), BITS_8, TagType.GENERIC); vm.expectEmit(true, true, true, true); - emit SwarmStatusChanged(swarmId, SwarmRegistryL1Upgradeable.SwarmStatus.REJECTED); + emit SwarmStatusChanged(swarmId, SwarmStatus.REJECTED); vm.prank(providerOwner); swarmRegistry.rejectSwarm(swarmId); - (,,,,, SwarmRegistryL1Upgradeable.SwarmStatus status) = swarmRegistry.swarms(swarmId); - assertEq(uint8(status), uint8(SwarmRegistryL1Upgradeable.SwarmStatus.REJECTED)); + (,,,,, SwarmStatus status) = swarmRegistry.swarms(swarmId); + assertEq(uint8(status), uint8(SwarmStatus.REJECTED)); } function test_RevertIf_acceptSwarm_notProviderOwner() public { uint256 fleetId = _registerFleet(fleetOwner, "f1"); uint256 providerId = _registerProvider(providerOwner, "url1"); uint256 swarmId = - _registerSwarm(fleetOwner, fleetId, providerId, new bytes(50), BITS_8, SwarmRegistryL1Upgradeable.TagType.GENERIC); + _registerSwarm(fleetOwner, fleetId, providerId, new bytes(50), BITS_8, TagType.GENERIC); vm.prank(caller); vm.expectRevert(SwarmRegistryL1Upgradeable.NotProviderOwner.selector); @@ -452,7 +453,7 @@ contract SwarmRegistryL1Test is Test { uint256 fleetId = _registerFleet(fleetOwner, "f1"); uint256 providerId = _registerProvider(providerOwner, "url1"); uint256 swarmId = - _registerSwarm(fleetOwner, fleetId, providerId, new bytes(50), BITS_8, SwarmRegistryL1Upgradeable.TagType.GENERIC); + _registerSwarm(fleetOwner, fleetId, providerId, new bytes(50), BITS_8, TagType.GENERIC); vm.prank(fleetOwner); // fleet owner != provider owner vm.expectRevert(SwarmRegistryL1Upgradeable.NotProviderOwner.selector); @@ -463,7 +464,7 @@ contract SwarmRegistryL1Test is Test { uint256 fleetId = _registerFleet(fleetOwner, "f1"); uint256 providerId = _registerProvider(providerOwner, "url1"); uint256 swarmId = - _registerSwarm(fleetOwner, fleetId, providerId, new bytes(50), BITS_8, SwarmRegistryL1Upgradeable.TagType.GENERIC); + _registerSwarm(fleetOwner, fleetId, providerId, new bytes(50), BITS_8, TagType.GENERIC); vm.prank(providerOwner); swarmRegistry.rejectSwarm(swarmId); @@ -472,15 +473,15 @@ contract SwarmRegistryL1Test is Test { vm.prank(providerOwner); swarmRegistry.acceptSwarm(swarmId); - (,,,,, SwarmRegistryL1Upgradeable.SwarmStatus status) = swarmRegistry.swarms(swarmId); - assertEq(uint8(status), uint8(SwarmRegistryL1Upgradeable.SwarmStatus.ACCEPTED)); + (,,,,, SwarmStatus status) = swarmRegistry.swarms(swarmId); + assertEq(uint8(status), uint8(SwarmStatus.ACCEPTED)); } function test_rejectSwarm_afterAccept() public { uint256 fleetId = _registerFleet(fleetOwner, "f1"); uint256 providerId = _registerProvider(providerOwner, "url1"); uint256 swarmId = - _registerSwarm(fleetOwner, fleetId, providerId, new bytes(50), BITS_8, SwarmRegistryL1Upgradeable.TagType.GENERIC); + _registerSwarm(fleetOwner, fleetId, providerId, new bytes(50), BITS_8, TagType.GENERIC); vm.prank(providerOwner); swarmRegistry.acceptSwarm(swarmId); @@ -488,8 +489,8 @@ contract SwarmRegistryL1Test is Test { vm.prank(providerOwner); swarmRegistry.rejectSwarm(swarmId); - (,,,,, SwarmRegistryL1Upgradeable.SwarmStatus status) = swarmRegistry.swarms(swarmId); - assertEq(uint8(status), uint8(SwarmRegistryL1Upgradeable.SwarmStatus.REJECTED)); + (,,,,, SwarmStatus status) = swarmRegistry.swarms(swarmId); + assertEq(uint8(status), uint8(SwarmStatus.REJECTED)); } // ============================== @@ -515,7 +516,7 @@ contract SwarmRegistryL1Test is Test { _write16Bit(filter, h1, uint16(expectedFp)); uint256 swarmId = - _registerSwarm(fleetOwner, fleetId, providerId, filter, BITS_16, SwarmRegistryL1Upgradeable.TagType.GENERIC); + _registerSwarm(fleetOwner, fleetId, providerId, filter, BITS_16, TagType.GENERIC); // Positive check assertTrue(swarmRegistry.checkMembership(swarmId, keccak256(tagId)), "Valid tag should pass"); @@ -542,7 +543,7 @@ contract SwarmRegistryL1Test is Test { _write8Bit(filter, h1, uint8(expectedFp)); uint256 swarmId = - _registerSwarm(fleetOwner, fleetId, providerId, filter, BITS_8, SwarmRegistryL1Upgradeable.TagType.GENERIC); + _registerSwarm(fleetOwner, fleetId, providerId, filter, BITS_8, TagType.GENERIC); assertTrue(swarmRegistry.checkMembership(swarmId, keccak256(tagId)), "8-bit valid tag should pass"); assertFalse(swarmRegistry.checkMembership(swarmId, keccak256(hex"FFFFFF")), "8-bit invalid tag should fail"); @@ -559,7 +560,7 @@ contract SwarmRegistryL1Test is Test { // All-zero filter: f1^f2^f3 = 0^0^0 = 0 bytes memory filter = new bytes(64); - uint256 swarmId = _registerSwarm(fleetOwner, fleetId, providerId, filter, BITS_16, SwarmRegistryL1Upgradeable.TagType.GENERIC); + uint256 swarmId = _registerSwarm(fleetOwner, fleetId, providerId, filter, BITS_16, TagType.GENERIC); // Some tags will match (those with expectedFp=0), most won't // The point is it doesn't revert @@ -573,7 +574,7 @@ contract SwarmRegistryL1Test is Test { // 1-byte filter with 16-bit fingerprint: m = 1/2 = 0, returns false immediately bytes memory filter = new bytes(1); - uint256 swarmId = _registerSwarm(fleetOwner, fleetId, providerId, filter, BITS_16, SwarmRegistryL1Upgradeable.TagType.GENERIC); + uint256 swarmId = _registerSwarm(fleetOwner, fleetId, providerId, filter, BITS_16, TagType.GENERIC); // Should return false (not revert) because m == 0 assertFalse(swarmRegistry.checkMembership(swarmId, keccak256("test")), "m=0 should return false"); @@ -589,11 +590,11 @@ contract SwarmRegistryL1Test is Test { uint256 providerId2 = _registerProvider(providerOwner, "url2"); uint256 providerId3 = _registerProvider(providerOwner, "url3"); - uint256 s1 = _registerSwarm(fleetOwner, fleetId, providerId1, new bytes(32), BITS_8, SwarmRegistryL1Upgradeable.TagType.GENERIC); + uint256 s1 = _registerSwarm(fleetOwner, fleetId, providerId1, new bytes(32), BITS_8, TagType.GENERIC); uint256 s2 = - _registerSwarm(fleetOwner, fleetId, providerId2, new bytes(64), BITS_16, SwarmRegistryL1Upgradeable.TagType.VENDOR_ID); + _registerSwarm(fleetOwner, fleetId, providerId2, new bytes(64), BITS_16, TagType.VENDOR_ID); uint256 s3 = _registerSwarm( - fleetOwner, fleetId, providerId3, new bytes(50), BITS_8, SwarmRegistryL1Upgradeable.TagType.IBEACON_PAYLOAD_ONLY + fleetOwner, fleetId, providerId3, new bytes(50), BITS_8, TagType.IBEACON_PAYLOAD_ONLY ); // IDs are distinct hashes @@ -615,7 +616,7 @@ contract SwarmRegistryL1Test is Test { uint256 providerId = _registerProvider(providerOwner, string(abi.encodePacked("url-", size))); uint256 swarmId = _registerSwarm( - fleetOwner, fleetId, providerId, new bytes(size), BITS_8, SwarmRegistryL1Upgradeable.TagType.GENERIC + fleetOwner, fleetId, providerId, new bytes(size), BITS_8, TagType.GENERIC ); (,, address pointer,,,) = swarmRegistry.swarms(swarmId); @@ -632,7 +633,7 @@ contract SwarmRegistryL1Test is Test { uint256 providerId2 = _registerProvider(providerOwner, "url2"); uint256 swarmId = - _registerSwarm(fleetOwner, fleetId, providerId1, new bytes(50), BITS_8, SwarmRegistryL1Upgradeable.TagType.GENERIC); + _registerSwarm(fleetOwner, fleetId, providerId1, new bytes(50), BITS_8, TagType.GENERIC); // Provider accepts vm.prank(providerOwner); @@ -646,9 +647,9 @@ contract SwarmRegistryL1Test is Test { swarmRegistry.updateSwarmProvider(swarmId, providerId2); // Check new provider and status reset - (, uint256 newProviderId,,,, SwarmRegistryL1Upgradeable.SwarmStatus status) = swarmRegistry.swarms(swarmId); + (, uint256 newProviderId,,,, SwarmStatus status) = swarmRegistry.swarms(swarmId); assertEq(newProviderId, providerId2); - assertEq(uint8(status), uint8(SwarmRegistryL1Upgradeable.SwarmStatus.REGISTERED)); + assertEq(uint8(status), uint8(SwarmStatus.REGISTERED)); } function test_RevertIf_updateSwarmProvider_swarmNotFound() public { @@ -665,7 +666,7 @@ contract SwarmRegistryL1Test is Test { uint256 providerId2 = _registerProvider(providerOwner, "url2"); uint256 swarmId = - _registerSwarm(fleetOwner, fleetId, providerId1, new bytes(50), BITS_8, SwarmRegistryL1Upgradeable.TagType.GENERIC); + _registerSwarm(fleetOwner, fleetId, providerId1, new bytes(50), BITS_8, TagType.GENERIC); vm.prank(caller); vm.expectRevert(SwarmRegistryL1Upgradeable.NotUuidOwner.selector); @@ -677,7 +678,7 @@ contract SwarmRegistryL1Test is Test { uint256 providerId = _registerProvider(providerOwner, "url1"); uint256 swarmId = - _registerSwarm(fleetOwner, fleetId, providerId, new bytes(50), BITS_8, SwarmRegistryL1Upgradeable.TagType.GENERIC); + _registerSwarm(fleetOwner, fleetId, providerId, new bytes(50), BITS_8, TagType.GENERIC); vm.prank(fleetOwner); vm.expectRevert(SwarmRegistryL1Upgradeable.ProviderDoesNotExist.selector); @@ -692,7 +693,7 @@ contract SwarmRegistryL1Test is Test { uint256 fleetId = _registerFleet(fleetOwner, "f1"); uint256 providerId = _registerProvider(providerOwner, "url1"); uint256 swarmId = - _registerSwarm(fleetOwner, fleetId, providerId, new bytes(50), BITS_8, SwarmRegistryL1Upgradeable.TagType.GENERIC); + _registerSwarm(fleetOwner, fleetId, providerId, new bytes(50), BITS_8, TagType.GENERIC); vm.expectEmit(true, true, true, true); emit SwarmDeleted(swarmId, _getFleetUuid(fleetId), fleetOwner); @@ -717,9 +718,9 @@ contract SwarmRegistryL1Test is Test { filter2[0] = 0x02; uint256 swarm1 = - _registerSwarm(fleetOwner, fleetId, providerId1, filter1, BITS_8, SwarmRegistryL1Upgradeable.TagType.GENERIC); + _registerSwarm(fleetOwner, fleetId, providerId1, filter1, BITS_8, TagType.GENERIC); uint256 swarm2 = - _registerSwarm(fleetOwner, fleetId, providerId2, filter2, BITS_8, SwarmRegistryL1Upgradeable.TagType.GENERIC); + _registerSwarm(fleetOwner, fleetId, providerId2, filter2, BITS_8, TagType.GENERIC); // Delete first swarm vm.prank(fleetOwner); @@ -746,11 +747,11 @@ contract SwarmRegistryL1Test is Test { filter3[0] = 0x03; uint256 swarm1 = - _registerSwarm(fleetOwner, fleetId, providerId1, filter1, BITS_8, SwarmRegistryL1Upgradeable.TagType.GENERIC); + _registerSwarm(fleetOwner, fleetId, providerId1, filter1, BITS_8, TagType.GENERIC); uint256 swarm2 = - _registerSwarm(fleetOwner, fleetId, providerId2, filter2, BITS_8, SwarmRegistryL1Upgradeable.TagType.GENERIC); + _registerSwarm(fleetOwner, fleetId, providerId2, filter2, BITS_8, TagType.GENERIC); uint256 swarm3 = - _registerSwarm(fleetOwner, fleetId, providerId3, filter3, BITS_8, SwarmRegistryL1Upgradeable.TagType.GENERIC); + _registerSwarm(fleetOwner, fleetId, providerId3, filter3, BITS_8, TagType.GENERIC); // Delete middle swarm vm.prank(fleetOwner); @@ -773,7 +774,7 @@ contract SwarmRegistryL1Test is Test { uint256 fleetId = _registerFleet(fleetOwner, "f1"); uint256 providerId = _registerProvider(providerOwner, "url1"); uint256 swarmId = - _registerSwarm(fleetOwner, fleetId, providerId, new bytes(50), BITS_8, SwarmRegistryL1Upgradeable.TagType.GENERIC); + _registerSwarm(fleetOwner, fleetId, providerId, new bytes(50), BITS_8, TagType.GENERIC); vm.prank(caller); vm.expectRevert(SwarmRegistryL1Upgradeable.NotUuidOwner.selector); @@ -785,7 +786,7 @@ contract SwarmRegistryL1Test is Test { uint256 providerId1 = _registerProvider(providerOwner, "url1"); uint256 providerId2 = _registerProvider(providerOwner, "url2"); uint256 swarmId = - _registerSwarm(fleetOwner, fleetId, providerId1, new bytes(50), BITS_8, SwarmRegistryL1Upgradeable.TagType.GENERIC); + _registerSwarm(fleetOwner, fleetId, providerId1, new bytes(50), BITS_8, TagType.GENERIC); // Update provider then delete vm.prank(fleetOwner); @@ -812,9 +813,9 @@ contract SwarmRegistryL1Test is Test { bytes memory filter3 = new bytes(50); filter3[0] = 0x03; - uint256 s1 = _registerSwarm(fleetOwner, fleetId, p1, filter1, BITS_8, SwarmRegistryL1Upgradeable.TagType.GENERIC); - uint256 s2 = _registerSwarm(fleetOwner, fleetId, p2, filter2, BITS_8, SwarmRegistryL1Upgradeable.TagType.GENERIC); - uint256 s3 = _registerSwarm(fleetOwner, fleetId, p3, filter3, BITS_8, SwarmRegistryL1Upgradeable.TagType.GENERIC); + uint256 s1 = _registerSwarm(fleetOwner, fleetId, p1, filter1, BITS_8, TagType.GENERIC); + uint256 s2 = _registerSwarm(fleetOwner, fleetId, p2, filter2, BITS_8, TagType.GENERIC); + uint256 s3 = _registerSwarm(fleetOwner, fleetId, p3, filter3, BITS_8, TagType.GENERIC); // Verify initial indices assertEq(swarmRegistry.swarmIndexInUuid(s1), 0); @@ -838,7 +839,7 @@ contract SwarmRegistryL1Test is Test { uint256 fleetId = _registerFleet(fleetOwner, "f1"); uint256 providerId = _registerProvider(providerOwner, "url1"); uint256 swarmId = - _registerSwarm(fleetOwner, fleetId, providerId, new bytes(50), BITS_8, SwarmRegistryL1Upgradeable.TagType.GENERIC); + _registerSwarm(fleetOwner, fleetId, providerId, new bytes(50), BITS_8, TagType.GENERIC); (bool fleetValid, bool providerValid) = swarmRegistry.isSwarmValid(swarmId); assertTrue(fleetValid); @@ -849,7 +850,7 @@ contract SwarmRegistryL1Test is Test { uint256 fleetId = _registerFleet(fleetOwner, "f1"); uint256 providerId = _registerProvider(providerOwner, "url1"); uint256 swarmId = - _registerSwarm(fleetOwner, fleetId, providerId, new bytes(50), BITS_8, SwarmRegistryL1Upgradeable.TagType.GENERIC); + _registerSwarm(fleetOwner, fleetId, providerId, new bytes(50), BITS_8, TagType.GENERIC); // Burn provider vm.prank(providerOwner); @@ -864,7 +865,7 @@ contract SwarmRegistryL1Test is Test { uint256 fleetId = _registerFleet(fleetOwner, "f1"); uint256 providerId = _registerProvider(providerOwner, "url1"); uint256 swarmId = - _registerSwarm(fleetOwner, fleetId, providerId, new bytes(50), BITS_8, SwarmRegistryL1Upgradeable.TagType.GENERIC); + _registerSwarm(fleetOwner, fleetId, providerId, new bytes(50), BITS_8, TagType.GENERIC); // Burn registered fleet token (operator = owner for fresh registration) // This mints an owned-only token back to the owner @@ -887,7 +888,7 @@ contract SwarmRegistryL1Test is Test { uint256 fleetId = _registerFleet(fleetOwner, "f1"); uint256 providerId = _registerProvider(providerOwner, "url1"); uint256 swarmId = - _registerSwarm(fleetOwner, fleetId, providerId, new bytes(50), BITS_8, SwarmRegistryL1Upgradeable.TagType.GENERIC); + _registerSwarm(fleetOwner, fleetId, providerId, new bytes(50), BITS_8, TagType.GENERIC); // Burn registered fleet token → mints owned-only token vm.prank(fleetOwner); @@ -920,7 +921,7 @@ contract SwarmRegistryL1Test is Test { uint256 fleetId = _registerFleet(fleetOwner, "f1"); uint256 providerId = _registerProvider(providerOwner, "url1"); uint256 swarmId = - _registerSwarm(fleetOwner, fleetId, providerId, new bytes(50), BITS_8, SwarmRegistryL1Upgradeable.TagType.GENERIC); + _registerSwarm(fleetOwner, fleetId, providerId, new bytes(50), BITS_8, TagType.GENERIC); // Burn provider vm.prank(providerOwner); @@ -942,7 +943,7 @@ contract SwarmRegistryL1Test is Test { uint256 fleetId = _registerFleet(fleetOwner, "f1"); uint256 providerId = _registerProvider(providerOwner, "url1"); uint256 swarmId = - _registerSwarm(fleetOwner, fleetId, providerId, new bytes(50), BITS_8, SwarmRegistryL1Upgradeable.TagType.GENERIC); + _registerSwarm(fleetOwner, fleetId, providerId, new bytes(50), BITS_8, TagType.GENERIC); // Burn registered fleet token → mints owned-only token vm.prank(fleetOwner); @@ -972,8 +973,8 @@ contract SwarmRegistryL1Test is Test { bytes memory filter2 = new bytes(50); filter2[0] = 0x02; - uint256 s1 = _registerSwarm(fleetOwner, fleetId, p1, filter1, BITS_8, SwarmRegistryL1Upgradeable.TagType.GENERIC); - uint256 s2 = _registerSwarm(fleetOwner, fleetId, p2, filter2, BITS_8, SwarmRegistryL1Upgradeable.TagType.GENERIC); + uint256 s1 = _registerSwarm(fleetOwner, fleetId, p1, filter1, BITS_8, TagType.GENERIC); + uint256 s2 = _registerSwarm(fleetOwner, fleetId, p2, filter2, BITS_8, TagType.GENERIC); // Burn provider of s1 vm.prank(providerOwner); @@ -997,7 +998,7 @@ contract SwarmRegistryL1Test is Test { uint256 fleetId = _registerFleet(fleetOwner, "f1"); uint256 providerId = _registerProvider(providerOwner, "url1"); uint256 swarmId = - _registerSwarm(fleetOwner, fleetId, providerId, new bytes(50), BITS_8, SwarmRegistryL1Upgradeable.TagType.GENERIC); + _registerSwarm(fleetOwner, fleetId, providerId, new bytes(50), BITS_8, TagType.GENERIC); vm.expectRevert(SwarmRegistryL1Upgradeable.SwarmNotOrphaned.selector); swarmRegistry.purgeOrphanedSwarm(swarmId); @@ -1011,7 +1012,7 @@ contract SwarmRegistryL1Test is Test { uint256 fleetId = _registerFleet(fleetOwner, "f1"); uint256 providerId = _registerProvider(providerOwner, "url1"); uint256 swarmId = - _registerSwarm(fleetOwner, fleetId, providerId, new bytes(50), BITS_8, SwarmRegistryL1Upgradeable.TagType.GENERIC); + _registerSwarm(fleetOwner, fleetId, providerId, new bytes(50), BITS_8, TagType.GENERIC); // Burn provider vm.prank(providerOwner); @@ -1026,7 +1027,7 @@ contract SwarmRegistryL1Test is Test { uint256 fleetId = _registerFleet(fleetOwner, "f1"); uint256 providerId = _registerProvider(providerOwner, "url1"); uint256 swarmId = - _registerSwarm(fleetOwner, fleetId, providerId, new bytes(50), BITS_8, SwarmRegistryL1Upgradeable.TagType.GENERIC); + _registerSwarm(fleetOwner, fleetId, providerId, new bytes(50), BITS_8, TagType.GENERIC); // Burn registered fleet token → mints owned-only token vm.prank(fleetOwner); @@ -1047,7 +1048,7 @@ contract SwarmRegistryL1Test is Test { uint256 fleetId = _registerFleet(fleetOwner, "f1"); uint256 providerId = _registerProvider(providerOwner, "url1"); uint256 swarmId = - _registerSwarm(fleetOwner, fleetId, providerId, new bytes(50), BITS_8, SwarmRegistryL1Upgradeable.TagType.GENERIC); + _registerSwarm(fleetOwner, fleetId, providerId, new bytes(50), BITS_8, TagType.GENERIC); // Burn provider vm.prank(providerOwner); @@ -1061,7 +1062,7 @@ contract SwarmRegistryL1Test is Test { uint256 fleetId = _registerFleet(fleetOwner, "f1"); uint256 providerId = _registerProvider(providerOwner, "url1"); uint256 swarmId = - _registerSwarm(fleetOwner, fleetId, providerId, new bytes(50), BITS_8, SwarmRegistryL1Upgradeable.TagType.GENERIC); + _registerSwarm(fleetOwner, fleetId, providerId, new bytes(50), BITS_8, TagType.GENERIC); // Burn registered fleet token → mints owned-only token vm.prank(fleetOwner); @@ -1082,7 +1083,7 @@ contract SwarmRegistryL1Test is Test { uint256 fleetId = _registerFleet(fleetOwner, "f1"); uint256 providerId = _registerProvider(providerOwner, "url1"); uint256 swarmId = - _registerSwarm(fleetOwner, fleetId, providerId, new bytes(50), BITS_8, SwarmRegistryL1Upgradeable.TagType.GENERIC); + _registerSwarm(fleetOwner, fleetId, providerId, new bytes(50), BITS_8, TagType.GENERIC); vm.prank(providerOwner); providerContract.burn(providerId); @@ -1134,7 +1135,7 @@ contract SwarmRegistryL1Test is Test { filter[h1] = bytes1(uint8(expectedFp)); uint256 swarmId = _registerSwarm( - fleetOwner, fleetId, providerId, filter, BITS_8, SwarmRegistryL1Upgradeable.TagType.GENERIC + fleetOwner, fleetId, providerId, filter, BITS_8, TagType.GENERIC ); // This should exercise the 8-bit path in _readFingerprint @@ -1170,7 +1171,7 @@ contract SwarmRegistryL1Test is Test { // 1 byte filter with 16-bit: m = 1/2 = 0 bytes memory filter = new bytes(1); uint256 swarmId = - _registerSwarm(fleetOwner, fleetId, providerId, filter, BITS_16, SwarmRegistryL1Upgradeable.TagType.GENERIC); + _registerSwarm(fleetOwner, fleetId, providerId, filter, BITS_16, TagType.GENERIC); // Should return false without reverting assertFalse(swarmRegistry.checkMembership(swarmId, keccak256("anyTag"))); @@ -1180,7 +1181,7 @@ contract SwarmRegistryL1Test is Test { uint256 fleetId = _registerFleet(fleetOwner, "f1"); uint256 providerId = _registerProvider(providerOwner, "url1"); uint256 swarmId = _registerSwarm( - fleetOwner, fleetId, providerId, new bytes(50), BITS_8, SwarmRegistryL1Upgradeable.TagType.GENERIC + fleetOwner, fleetId, providerId, new bytes(50), BITS_8, TagType.GENERIC ); // Fleet owner is not provider owner @@ -1195,7 +1196,7 @@ contract SwarmRegistryL1Test is Test { vm.prank(fleetOwner); vm.expectRevert(SwarmRegistryL1Upgradeable.ProviderDoesNotExist.selector); swarmRegistry.registerSwarm( - _getFleetUuid(fleetId), 0, new bytes(32), BITS_8, SwarmRegistryL1Upgradeable.TagType.GENERIC + _getFleetUuid(fleetId), 0, new bytes(32), BITS_8, TagType.GENERIC ); } @@ -1212,7 +1213,7 @@ contract SwarmRegistryL1Test is Test { filter[99] = 0x01; uint256 swarmId = _registerSwarm( - fleetOwner, fleetId, providerId, filter, BITS_16, SwarmRegistryL1Upgradeable.TagType.GENERIC + fleetOwner, fleetId, providerId, filter, BITS_16, TagType.GENERIC ); bytes memory stored = swarmRegistry.getFilterData(swarmId); diff --git a/test/SwarmRegistryUniversal.t.sol b/test/SwarmRegistryUniversal.t.sol index 4ea27115..ec200ddb 100644 --- a/test/SwarmRegistryUniversal.t.sol +++ b/test/SwarmRegistryUniversal.t.sol @@ -6,6 +6,7 @@ import {ERC1967Proxy} from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.s import "../src/swarms/SwarmRegistryUniversalUpgradeable.sol"; import {FleetIdentityUpgradeable} from "../src/swarms/FleetIdentityUpgradeable.sol"; import {ServiceProviderUpgradeable} from "../src/swarms/ServiceProviderUpgradeable.sol"; +import {SwarmStatus, TagType, FingerprintSize} from "../src/swarms/interfaces/SwarmTypes.sol"; import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; contract MockBondTokenUniv is ERC20 { @@ -34,15 +35,13 @@ contract SwarmRegistryUniversalTest is Test { uint16 constant ADMIN_CA = 6; // California // Alias for FingerprintSize enum - SwarmRegistryUniversalUpgradeable.FingerprintSize constant BITS_8 = - SwarmRegistryUniversalUpgradeable.FingerprintSize.BITS_8; - SwarmRegistryUniversalUpgradeable.FingerprintSize constant BITS_16 = - SwarmRegistryUniversalUpgradeable.FingerprintSize.BITS_16; + FingerprintSize constant BITS_8 = FingerprintSize.BITS_8; + FingerprintSize constant BITS_16 = FingerprintSize.BITS_16; event SwarmRegistered( uint256 indexed swarmId, bytes16 indexed fleetUuid, uint256 indexed providerId, address owner, uint32 filterSize ); - event SwarmStatusChanged(uint256 indexed swarmId, SwarmRegistryUniversalUpgradeable.SwarmStatus status); + event SwarmStatusChanged(uint256 indexed swarmId, SwarmStatus status); event SwarmProviderUpdated(uint256 indexed swarmId, uint256 indexed oldProviderId, uint256 indexed newProviderId); event SwarmDeleted(uint256 indexed swarmId, bytes16 indexed fleetUuid, address indexed owner); event SwarmPurged(uint256 indexed swarmId, bytes16 indexed fleetUuid, address indexed purgedBy); @@ -103,8 +102,8 @@ contract SwarmRegistryUniversalTest is Test { uint256 fleetId, uint256 providerId, bytes memory filter, - SwarmRegistryUniversalUpgradeable.FingerprintSize fpSize, - SwarmRegistryUniversalUpgradeable.TagType tagType + FingerprintSize fpSize, + TagType tagType ) internal returns (uint256) { bytes16 fleetUuid = _getFleetUuid(fleetId); vm.prank(owner); @@ -115,13 +114,13 @@ contract SwarmRegistryUniversalTest is Test { function getExpectedValues( bytes memory tagId, uint256 m, - SwarmRegistryUniversalUpgradeable.FingerprintSize fpSize + FingerprintSize fpSize ) public pure returns (uint32 h1, uint32 h2, uint32 h3, uint256 fp) { bytes32 h = keccak256(tagId); h1 = uint32(uint256(h)) % uint32(m); h2 = uint32(uint256(h) >> 32) % uint32(m); h3 = uint32(uint256(h) >> 64) % uint32(m); - uint256 fpMask = fpSize == SwarmRegistryUniversalUpgradeable.FingerprintSize.BITS_8 ? 0xFF : 0xFFFF; + uint256 fpMask = fpSize == FingerprintSize.BITS_8 ? 0xFF : 0xFFFF; fp = (uint256(h) >> 96) & fpMask; } @@ -180,12 +179,12 @@ contract SwarmRegistryUniversalTest is Test { uint256 providerId = _registerProvider(providerOwner, "https://api.example.com"); uint256 swarmId = _registerSwarm( - fleetOwner, fleetId, providerId, new bytes(100), BITS_16, SwarmRegistryUniversalUpgradeable.TagType.IBEACON_INCLUDES_MAC + fleetOwner, fleetId, providerId, new bytes(100), BITS_16, TagType.IBEACON_INCLUDES_MAC ); // Swarm ID is deterministic hash of (fleetUuid, filter, fpSize, tagType) uint256 expectedId = swarmRegistry.computeSwarmId( - _getFleetUuid(fleetId), new bytes(100), BITS_16, SwarmRegistryUniversalUpgradeable.TagType.IBEACON_INCLUDES_MAC + _getFleetUuid(fleetId), new bytes(100), BITS_16, TagType.IBEACON_INCLUDES_MAC ); assertEq(swarmId, expectedId); } @@ -195,23 +194,23 @@ contract SwarmRegistryUniversalTest is Test { uint256 providerId = _registerProvider(providerOwner, "url1"); uint256 swarmId = - _registerSwarm(fleetOwner, fleetId, providerId, new bytes(50), BITS_16, SwarmRegistryUniversalUpgradeable.TagType.VENDOR_ID); + _registerSwarm(fleetOwner, fleetId, providerId, new bytes(50), BITS_16, TagType.VENDOR_ID); ( bytes16 storedFleetUuid, uint256 storedProviderId, uint32 storedFilterLen, - SwarmRegistryUniversalUpgradeable.FingerprintSize storedFpSize, - SwarmRegistryUniversalUpgradeable.TagType storedTagType, - SwarmRegistryUniversalUpgradeable.SwarmStatus storedStatus + FingerprintSize storedFpSize, + TagType storedTagType, + SwarmStatus storedStatus ) = swarmRegistry.swarms(swarmId); assertEq(storedFleetUuid, _getFleetUuid(fleetId)); assertEq(storedProviderId, providerId); assertEq(storedFilterLen, 50); assertEq(uint8(storedFpSize), uint8(BITS_16)); - assertEq(uint8(storedTagType), uint8(SwarmRegistryUniversalUpgradeable.TagType.VENDOR_ID)); - assertEq(uint8(storedStatus), uint8(SwarmRegistryUniversalUpgradeable.SwarmStatus.REGISTERED)); + assertEq(uint8(storedTagType), uint8(TagType.VENDOR_ID)); + assertEq(uint8(storedStatus), uint8(SwarmStatus.REGISTERED)); } function test_registerSwarm_storesFilterData() public { @@ -225,7 +224,7 @@ contract SwarmRegistryUniversalTest is Test { filter[99] = 0xEF; uint256 swarmId = - _registerSwarm(fleetOwner, fleetId, providerId, filter, BITS_16, SwarmRegistryUniversalUpgradeable.TagType.GENERIC); + _registerSwarm(fleetOwner, fleetId, providerId, filter, BITS_16, TagType.GENERIC); bytes memory storedFilter = swarmRegistry.getFilterData(swarmId); assertEq(storedFilter.length, 100); @@ -241,10 +240,10 @@ contract SwarmRegistryUniversalTest is Test { bytes memory filter = new bytes(32); uint256 expectedId = - swarmRegistry.computeSwarmId(_getFleetUuid(fleetId), filter, BITS_8, SwarmRegistryUniversalUpgradeable.TagType.GENERIC); + swarmRegistry.computeSwarmId(_getFleetUuid(fleetId), filter, BITS_8, TagType.GENERIC); uint256 swarmId = - _registerSwarm(fleetOwner, fleetId, providerId, filter, BITS_8, SwarmRegistryUniversalUpgradeable.TagType.GENERIC); + _registerSwarm(fleetOwner, fleetId, providerId, filter, BITS_8, TagType.GENERIC); assertEq(swarmId, expectedId); } @@ -252,12 +251,12 @@ contract SwarmRegistryUniversalTest is Test { uint256 fleetId = _registerFleet(fleetOwner, "f1"); uint256 providerId = _registerProvider(providerOwner, "url1"); - _registerSwarm(fleetOwner, fleetId, providerId, new bytes(32), BITS_8, SwarmRegistryUniversalUpgradeable.TagType.GENERIC); + _registerSwarm(fleetOwner, fleetId, providerId, new bytes(32), BITS_8, TagType.GENERIC); vm.prank(fleetOwner); vm.expectRevert(SwarmRegistryUniversalUpgradeable.SwarmAlreadyExists.selector); swarmRegistry.registerSwarm( - _getFleetUuid(fleetId), providerId, new bytes(32), BITS_8, SwarmRegistryUniversalUpgradeable.TagType.GENERIC + _getFleetUuid(fleetId), providerId, new bytes(32), BITS_8, TagType.GENERIC ); } @@ -267,13 +266,13 @@ contract SwarmRegistryUniversalTest is Test { bytes memory filter = new bytes(50); uint256 expectedId = swarmRegistry.computeSwarmId( - _getFleetUuid(fleetId), filter, BITS_16, SwarmRegistryUniversalUpgradeable.TagType.GENERIC + _getFleetUuid(fleetId), filter, BITS_16, TagType.GENERIC ); vm.expectEmit(true, true, true, true); emit SwarmRegistered(expectedId, _getFleetUuid(fleetId), providerId, fleetOwner, 50); - _registerSwarm(fleetOwner, fleetId, providerId, filter, BITS_16, SwarmRegistryUniversalUpgradeable.TagType.GENERIC); + _registerSwarm(fleetOwner, fleetId, providerId, filter, BITS_16, TagType.GENERIC); } function test_registerSwarm_linksUuidSwarms() public { @@ -288,9 +287,9 @@ contract SwarmRegistryUniversalTest is Test { filter2[0] = 0x02; uint256 s1 = - _registerSwarm(fleetOwner, fleetId, providerId1, filter1, BITS_8, SwarmRegistryUniversalUpgradeable.TagType.GENERIC); + _registerSwarm(fleetOwner, fleetId, providerId1, filter1, BITS_8, TagType.GENERIC); uint256 s2 = - _registerSwarm(fleetOwner, fleetId, providerId2, filter2, BITS_8, SwarmRegistryUniversalUpgradeable.TagType.GENERIC); + _registerSwarm(fleetOwner, fleetId, providerId2, filter2, BITS_8, TagType.GENERIC); assertEq(swarmRegistry.uuidSwarms(_getFleetUuid(fleetId), 0), s1); assertEq(swarmRegistry.uuidSwarms(_getFleetUuid(fleetId), 1), s2); @@ -304,27 +303,27 @@ contract SwarmRegistryUniversalTest is Test { uint256 providerId = _registerProvider(providerOwner, "url"); uint256 s1 = _registerSwarm( - fleetOwner, fleetId1, providerId, new bytes(32), BITS_8, SwarmRegistryUniversalUpgradeable.TagType.IBEACON_PAYLOAD_ONLY + fleetOwner, fleetId1, providerId, new bytes(32), BITS_8, TagType.IBEACON_PAYLOAD_ONLY ); uint256 s2 = _registerSwarm( - fleetOwner, fleetId2, providerId, new bytes(32), BITS_8, SwarmRegistryUniversalUpgradeable.TagType.IBEACON_INCLUDES_MAC + fleetOwner, fleetId2, providerId, new bytes(32), BITS_8, TagType.IBEACON_INCLUDES_MAC ); uint256 s3 = _registerSwarm( - fleetOwner, fleetId3, providerId, new bytes(32), BITS_8, SwarmRegistryUniversalUpgradeable.TagType.VENDOR_ID + fleetOwner, fleetId3, providerId, new bytes(32), BITS_8, TagType.VENDOR_ID ); uint256 s4 = _registerSwarm( - fleetOwner, fleetId4, providerId, new bytes(32), BITS_8, SwarmRegistryUniversalUpgradeable.TagType.GENERIC + fleetOwner, fleetId4, providerId, new bytes(32), BITS_8, TagType.GENERIC ); - (,,,, SwarmRegistryUniversalUpgradeable.TagType t1,) = swarmRegistry.swarms(s1); - (,,,, SwarmRegistryUniversalUpgradeable.TagType t2,) = swarmRegistry.swarms(s2); - (,,,, SwarmRegistryUniversalUpgradeable.TagType t3,) = swarmRegistry.swarms(s3); - (,,,, SwarmRegistryUniversalUpgradeable.TagType t4,) = swarmRegistry.swarms(s4); + (,,,, TagType t1,) = swarmRegistry.swarms(s1); + (,,,, TagType t2,) = swarmRegistry.swarms(s2); + (,,,, TagType t3,) = swarmRegistry.swarms(s3); + (,,,, TagType t4,) = swarmRegistry.swarms(s4); - assertEq(uint8(t1), uint8(SwarmRegistryUniversalUpgradeable.TagType.IBEACON_PAYLOAD_ONLY)); - assertEq(uint8(t2), uint8(SwarmRegistryUniversalUpgradeable.TagType.IBEACON_INCLUDES_MAC)); - assertEq(uint8(t3), uint8(SwarmRegistryUniversalUpgradeable.TagType.VENDOR_ID)); - assertEq(uint8(t4), uint8(SwarmRegistryUniversalUpgradeable.TagType.GENERIC)); + assertEq(uint8(t1), uint8(TagType.IBEACON_PAYLOAD_ONLY)); + assertEq(uint8(t2), uint8(TagType.IBEACON_INCLUDES_MAC)); + assertEq(uint8(t3), uint8(TagType.VENDOR_ID)); + assertEq(uint8(t4), uint8(TagType.GENERIC)); } function test_registerSwarm_bothFingerprintSizes() public { @@ -333,14 +332,14 @@ contract SwarmRegistryUniversalTest is Test { uint256 providerId = _registerProvider(providerOwner, "url"); uint256 s1 = _registerSwarm( - fleetOwner, fleetId1, providerId, new bytes(32), BITS_8, SwarmRegistryUniversalUpgradeable.TagType.GENERIC + fleetOwner, fleetId1, providerId, new bytes(32), BITS_8, TagType.GENERIC ); uint256 s2 = _registerSwarm( - fleetOwner, fleetId2, providerId, new bytes(64), BITS_16, SwarmRegistryUniversalUpgradeable.TagType.GENERIC + fleetOwner, fleetId2, providerId, new bytes(64), BITS_16, TagType.GENERIC ); - (,,, SwarmRegistryUniversalUpgradeable.FingerprintSize fp1,,) = swarmRegistry.swarms(s1); - (,,, SwarmRegistryUniversalUpgradeable.FingerprintSize fp2,,) = swarmRegistry.swarms(s2); + (,,, FingerprintSize fp1,,) = swarmRegistry.swarms(s1); + (,,, FingerprintSize fp2,,) = swarmRegistry.swarms(s2); assertEq(uint8(fp1), uint8(BITS_8)); assertEq(uint8(fp2), uint8(BITS_16)); @@ -356,7 +355,7 @@ contract SwarmRegistryUniversalTest is Test { vm.prank(fleetOwner); vm.expectRevert(SwarmRegistryUniversalUpgradeable.InvalidUuid.selector); swarmRegistry.registerSwarm( - bytes16(0), providerId, new bytes(32), BITS_8, SwarmRegistryUniversalUpgradeable.TagType.GENERIC + bytes16(0), providerId, new bytes(32), BITS_8, TagType.GENERIC ); } @@ -367,7 +366,7 @@ contract SwarmRegistryUniversalTest is Test { vm.prank(fleetOwner); vm.expectRevert(SwarmRegistryUniversalUpgradeable.ProviderDoesNotExist.selector); swarmRegistry.registerSwarm( - _getFleetUuid(fleetId), nonExistentProvider, new bytes(32), BITS_8, SwarmRegistryUniversalUpgradeable.TagType.GENERIC + _getFleetUuid(fleetId), nonExistentProvider, new bytes(32), BITS_8, TagType.GENERIC ); } @@ -377,7 +376,7 @@ contract SwarmRegistryUniversalTest is Test { vm.prank(caller); vm.expectRevert(SwarmRegistryUniversalUpgradeable.NotUuidOwner.selector); swarmRegistry.registerSwarm( - _getFleetUuid(fleetId), 1, new bytes(10), BITS_16, SwarmRegistryUniversalUpgradeable.TagType.GENERIC + _getFleetUuid(fleetId), 1, new bytes(10), BITS_16, TagType.GENERIC ); } @@ -388,7 +387,7 @@ contract SwarmRegistryUniversalTest is Test { vm.prank(fleetOwner); vm.expectRevert(SwarmRegistryUniversalUpgradeable.InvalidFilterSize.selector); swarmRegistry.registerSwarm( - _getFleetUuid(fleetId), providerId, new bytes(0), BITS_8, SwarmRegistryUniversalUpgradeable.TagType.GENERIC + _getFleetUuid(fleetId), providerId, new bytes(0), BITS_8, TagType.GENERIC ); } @@ -399,7 +398,7 @@ contract SwarmRegistryUniversalTest is Test { vm.prank(fleetOwner); vm.expectRevert(SwarmRegistryUniversalUpgradeable.FilterTooLarge.selector); swarmRegistry.registerSwarm( - _getFleetUuid(fleetId), providerId, new bytes(24577), BITS_8, SwarmRegistryUniversalUpgradeable.TagType.GENERIC + _getFleetUuid(fleetId), providerId, new bytes(24577), BITS_8, TagType.GENERIC ); } @@ -409,7 +408,7 @@ contract SwarmRegistryUniversalTest is Test { // Exactly MAX_FILTER_SIZE (24576) should succeed uint256 swarmId = _registerSwarm( - fleetOwner, fleetId, providerId, new bytes(24576), BITS_8, SwarmRegistryUniversalUpgradeable.TagType.GENERIC + fleetOwner, fleetId, providerId, new bytes(24576), BITS_8, TagType.GENERIC ); assertTrue(swarmId != 0); } @@ -420,7 +419,7 @@ contract SwarmRegistryUniversalTest is Test { // 1 byte filter uint256 swarmId = _registerSwarm( - fleetOwner, fleetId, providerId, new bytes(1), BITS_8, SwarmRegistryUniversalUpgradeable.TagType.GENERIC + fleetOwner, fleetId, providerId, new bytes(1), BITS_8, TagType.GENERIC ); assertTrue(swarmId != 0); } @@ -433,41 +432,41 @@ contract SwarmRegistryUniversalTest is Test { uint256 fleetId = _registerFleet(fleetOwner, "f1"); uint256 providerId = _registerProvider(providerOwner, "url1"); uint256 swarmId = _registerSwarm( - fleetOwner, fleetId, providerId, new bytes(50), BITS_8, SwarmRegistryUniversalUpgradeable.TagType.GENERIC + fleetOwner, fleetId, providerId, new bytes(50), BITS_8, TagType.GENERIC ); vm.expectEmit(true, true, true, true); - emit SwarmStatusChanged(swarmId, SwarmRegistryUniversalUpgradeable.SwarmStatus.ACCEPTED); + emit SwarmStatusChanged(swarmId, SwarmStatus.ACCEPTED); vm.prank(providerOwner); swarmRegistry.acceptSwarm(swarmId); - (,,,,, SwarmRegistryUniversalUpgradeable.SwarmStatus status) = swarmRegistry.swarms(swarmId); - assertEq(uint8(status), uint8(SwarmRegistryUniversalUpgradeable.SwarmStatus.ACCEPTED)); + (,,,,, SwarmStatus status) = swarmRegistry.swarms(swarmId); + assertEq(uint8(status), uint8(SwarmStatus.ACCEPTED)); } function test_rejectSwarm_setsStatusAndEmits() public { uint256 fleetId = _registerFleet(fleetOwner, "f1"); uint256 providerId = _registerProvider(providerOwner, "url1"); uint256 swarmId = _registerSwarm( - fleetOwner, fleetId, providerId, new bytes(50), BITS_8, SwarmRegistryUniversalUpgradeable.TagType.GENERIC + fleetOwner, fleetId, providerId, new bytes(50), BITS_8, TagType.GENERIC ); vm.expectEmit(true, true, true, true); - emit SwarmStatusChanged(swarmId, SwarmRegistryUniversalUpgradeable.SwarmStatus.REJECTED); + emit SwarmStatusChanged(swarmId, SwarmStatus.REJECTED); vm.prank(providerOwner); swarmRegistry.rejectSwarm(swarmId); - (,,,,, SwarmRegistryUniversalUpgradeable.SwarmStatus status) = swarmRegistry.swarms(swarmId); - assertEq(uint8(status), uint8(SwarmRegistryUniversalUpgradeable.SwarmStatus.REJECTED)); + (,,,,, SwarmStatus status) = swarmRegistry.swarms(swarmId); + assertEq(uint8(status), uint8(SwarmStatus.REJECTED)); } function test_RevertIf_acceptSwarm_notProviderOwner() public { uint256 fleetId = _registerFleet(fleetOwner, "f1"); uint256 providerId = _registerProvider(providerOwner, "url1"); uint256 swarmId = _registerSwarm( - fleetOwner, fleetId, providerId, new bytes(50), BITS_8, SwarmRegistryUniversalUpgradeable.TagType.GENERIC + fleetOwner, fleetId, providerId, new bytes(50), BITS_8, TagType.GENERIC ); vm.prank(caller); @@ -479,7 +478,7 @@ contract SwarmRegistryUniversalTest is Test { uint256 fleetId = _registerFleet(fleetOwner, "f1"); uint256 providerId = _registerProvider(providerOwner, "url1"); uint256 swarmId = _registerSwarm( - fleetOwner, fleetId, providerId, new bytes(50), BITS_8, SwarmRegistryUniversalUpgradeable.TagType.GENERIC + fleetOwner, fleetId, providerId, new bytes(50), BITS_8, TagType.GENERIC ); vm.prank(fleetOwner); // fleet owner != provider owner @@ -491,7 +490,7 @@ contract SwarmRegistryUniversalTest is Test { uint256 fleetId = _registerFleet(fleetOwner, "f1"); uint256 providerId = _registerProvider(providerOwner, "url1"); uint256 swarmId = _registerSwarm( - fleetOwner, fleetId, providerId, new bytes(50), BITS_8, SwarmRegistryUniversalUpgradeable.TagType.GENERIC + fleetOwner, fleetId, providerId, new bytes(50), BITS_8, TagType.GENERIC ); vm.prank(fleetOwner); @@ -503,7 +502,7 @@ contract SwarmRegistryUniversalTest is Test { uint256 fleetId = _registerFleet(fleetOwner, "f1"); uint256 providerId = _registerProvider(providerOwner, "url1"); uint256 swarmId = _registerSwarm( - fleetOwner, fleetId, providerId, new bytes(50), BITS_8, SwarmRegistryUniversalUpgradeable.TagType.GENERIC + fleetOwner, fleetId, providerId, new bytes(50), BITS_8, TagType.GENERIC ); vm.prank(providerOwner); @@ -512,15 +511,15 @@ contract SwarmRegistryUniversalTest is Test { vm.prank(providerOwner); swarmRegistry.acceptSwarm(swarmId); - (,,,,, SwarmRegistryUniversalUpgradeable.SwarmStatus status) = swarmRegistry.swarms(swarmId); - assertEq(uint8(status), uint8(SwarmRegistryUniversalUpgradeable.SwarmStatus.ACCEPTED)); + (,,,,, SwarmStatus status) = swarmRegistry.swarms(swarmId); + assertEq(uint8(status), uint8(SwarmStatus.ACCEPTED)); } function test_rejectSwarm_afterAccept() public { uint256 fleetId = _registerFleet(fleetOwner, "f1"); uint256 providerId = _registerProvider(providerOwner, "url1"); uint256 swarmId = _registerSwarm( - fleetOwner, fleetId, providerId, new bytes(50), BITS_8, SwarmRegistryUniversalUpgradeable.TagType.GENERIC + fleetOwner, fleetId, providerId, new bytes(50), BITS_8, TagType.GENERIC ); vm.prank(providerOwner); @@ -529,8 +528,8 @@ contract SwarmRegistryUniversalTest is Test { vm.prank(providerOwner); swarmRegistry.rejectSwarm(swarmId); - (,,,,, SwarmRegistryUniversalUpgradeable.SwarmStatus status) = swarmRegistry.swarms(swarmId); - assertEq(uint8(status), uint8(SwarmRegistryUniversalUpgradeable.SwarmStatus.REJECTED)); + (,,,,, SwarmStatus status) = swarmRegistry.swarms(swarmId); + assertEq(uint8(status), uint8(SwarmStatus.REJECTED)); } // ============================== @@ -555,7 +554,7 @@ contract SwarmRegistryUniversalTest is Test { _write16Bit(filter, h1, uint16(expectedFp)); uint256 swarmId = - _registerSwarm(fleetOwner, fleetId, providerId, filter, BITS_16, SwarmRegistryUniversalUpgradeable.TagType.GENERIC); + _registerSwarm(fleetOwner, fleetId, providerId, filter, BITS_16, TagType.GENERIC); bytes32 tagHash = keccak256(tagId); assertTrue(swarmRegistry.checkMembership(swarmId, tagHash), "Tag should be member"); @@ -582,7 +581,7 @@ contract SwarmRegistryUniversalTest is Test { _write8Bit(filter, h1, uint8(expectedFp)); uint256 swarmId = - _registerSwarm(fleetOwner, fleetId, providerId, filter, BITS_8, SwarmRegistryUniversalUpgradeable.TagType.GENERIC); + _registerSwarm(fleetOwner, fleetId, providerId, filter, BITS_8, TagType.GENERIC); assertTrue(swarmRegistry.checkMembership(swarmId, keccak256(tagId)), "8-bit valid tag should pass"); assertFalse(swarmRegistry.checkMembership(swarmId, keccak256(hex"FFFFFF")), "8-bit invalid tag should fail"); @@ -600,7 +599,7 @@ contract SwarmRegistryUniversalTest is Test { // All-zero filter: f1^f2^f3 = 0^0^0 = 0 bytes memory filter = new bytes(64); uint256 swarmId = - _registerSwarm(fleetOwner, fleetId, providerId, filter, BITS_16, SwarmRegistryUniversalUpgradeable.TagType.GENERIC); + _registerSwarm(fleetOwner, fleetId, providerId, filter, BITS_16, TagType.GENERIC); // Should not revert regardless of result swarmRegistry.checkMembership(swarmId, keccak256("test1")); @@ -614,7 +613,7 @@ contract SwarmRegistryUniversalTest is Test { // 1-byte filter with 16-bit fingerprint: m = 1/2 = 0, returns false immediately bytes memory filter = new bytes(1); uint256 swarmId = - _registerSwarm(fleetOwner, fleetId, providerId, filter, BITS_16, SwarmRegistryUniversalUpgradeable.TagType.GENERIC); + _registerSwarm(fleetOwner, fleetId, providerId, filter, BITS_16, TagType.GENERIC); // Should return false (not revert) because m == 0 assertFalse(swarmRegistry.checkMembership(swarmId, keccak256("test")), "m=0 should return false"); @@ -633,7 +632,7 @@ contract SwarmRegistryUniversalTest is Test { filter[99] = 0x01; uint256 swarmId = - _registerSwarm(fleetOwner, fleetId, providerId, filter, BITS_16, SwarmRegistryUniversalUpgradeable.TagType.GENERIC); + _registerSwarm(fleetOwner, fleetId, providerId, filter, BITS_16, TagType.GENERIC); bytes memory stored = swarmRegistry.getFilterData(swarmId); assertEq(stored.length, 100); @@ -657,13 +656,13 @@ contract SwarmRegistryUniversalTest is Test { uint256 providerId3 = _registerProvider(providerOwner, "url3"); uint256 s1 = _registerSwarm( - fleetOwner, fleetId, providerId1, new bytes(32), BITS_8, SwarmRegistryUniversalUpgradeable.TagType.GENERIC + fleetOwner, fleetId, providerId1, new bytes(32), BITS_8, TagType.GENERIC ); uint256 s2 = _registerSwarm( - fleetOwner, fleetId, providerId2, new bytes(64), BITS_16, SwarmRegistryUniversalUpgradeable.TagType.VENDOR_ID + fleetOwner, fleetId, providerId2, new bytes(64), BITS_16, TagType.VENDOR_ID ); uint256 s3 = _registerSwarm( - fleetOwner, fleetId, providerId3, new bytes(50), BITS_8, SwarmRegistryUniversalUpgradeable.TagType.IBEACON_PAYLOAD_ONLY + fleetOwner, fleetId, providerId3, new bytes(50), BITS_8, TagType.IBEACON_PAYLOAD_ONLY ); // IDs are distinct hashes @@ -693,7 +692,7 @@ contract SwarmRegistryUniversalTest is Test { uint256 providerId = _registerProvider(providerOwner, string(abi.encodePacked("url-", size))); uint256 swarmId = _registerSwarm( - fleetOwner, fleetId, providerId, new bytes(size), BITS_8, SwarmRegistryUniversalUpgradeable.TagType.GENERIC + fleetOwner, fleetId, providerId, new bytes(size), BITS_8, TagType.GENERIC ); (,, uint32 storedLen,,,) = swarmRegistry.swarms(swarmId); @@ -710,7 +709,7 @@ contract SwarmRegistryUniversalTest is Test { uint256 providerId2 = _registerProvider(providerOwner, "url2"); uint256 swarmId = _registerSwarm( - fleetOwner, fleetId, providerId1, new bytes(50), BITS_8, SwarmRegistryUniversalUpgradeable.TagType.GENERIC + fleetOwner, fleetId, providerId1, new bytes(50), BITS_8, TagType.GENERIC ); // Provider accepts @@ -725,9 +724,9 @@ contract SwarmRegistryUniversalTest is Test { swarmRegistry.updateSwarmProvider(swarmId, providerId2); // Check new provider and status reset - (, uint256 newProviderId,,,, SwarmRegistryUniversalUpgradeable.SwarmStatus status) = swarmRegistry.swarms(swarmId); + (, uint256 newProviderId,,,, SwarmStatus status) = swarmRegistry.swarms(swarmId); assertEq(newProviderId, providerId2); - assertEq(uint8(status), uint8(SwarmRegistryUniversalUpgradeable.SwarmStatus.REGISTERED)); + assertEq(uint8(status), uint8(SwarmStatus.REGISTERED)); } function test_RevertIf_updateSwarmProvider_swarmNotFound() public { @@ -744,7 +743,7 @@ contract SwarmRegistryUniversalTest is Test { uint256 providerId2 = _registerProvider(providerOwner, "url2"); uint256 swarmId = _registerSwarm( - fleetOwner, fleetId, providerId1, new bytes(50), BITS_8, SwarmRegistryUniversalUpgradeable.TagType.GENERIC + fleetOwner, fleetId, providerId1, new bytes(50), BITS_8, TagType.GENERIC ); vm.prank(caller); @@ -757,7 +756,7 @@ contract SwarmRegistryUniversalTest is Test { uint256 providerId = _registerProvider(providerOwner, "url1"); uint256 swarmId = _registerSwarm( - fleetOwner, fleetId, providerId, new bytes(50), BITS_8, SwarmRegistryUniversalUpgradeable.TagType.GENERIC + fleetOwner, fleetId, providerId, new bytes(50), BITS_8, TagType.GENERIC ); vm.prank(fleetOwner); @@ -773,7 +772,7 @@ contract SwarmRegistryUniversalTest is Test { uint256 fleetId = _registerFleet(fleetOwner, "f1"); uint256 providerId = _registerProvider(providerOwner, "url1"); uint256 swarmId = _registerSwarm( - fleetOwner, fleetId, providerId, new bytes(50), BITS_8, SwarmRegistryUniversalUpgradeable.TagType.GENERIC + fleetOwner, fleetId, providerId, new bytes(50), BITS_8, TagType.GENERIC ); vm.expectEmit(true, true, true, true); @@ -800,10 +799,10 @@ contract SwarmRegistryUniversalTest is Test { filter2[0] = 0x02; uint256 swarm1 = _registerSwarm( - fleetOwner, fleetId, providerId1, filter1, BITS_8, SwarmRegistryUniversalUpgradeable.TagType.GENERIC + fleetOwner, fleetId, providerId1, filter1, BITS_8, TagType.GENERIC ); uint256 swarm2 = _registerSwarm( - fleetOwner, fleetId, providerId2, filter2, BITS_8, SwarmRegistryUniversalUpgradeable.TagType.GENERIC + fleetOwner, fleetId, providerId2, filter2, BITS_8, TagType.GENERIC ); // Delete first swarm @@ -831,13 +830,13 @@ contract SwarmRegistryUniversalTest is Test { filter3[0] = 0x03; uint256 swarm1 = _registerSwarm( - fleetOwner, fleetId, providerId1, filter1, BITS_8, SwarmRegistryUniversalUpgradeable.TagType.GENERIC + fleetOwner, fleetId, providerId1, filter1, BITS_8, TagType.GENERIC ); uint256 swarm2 = _registerSwarm( - fleetOwner, fleetId, providerId2, filter2, BITS_8, SwarmRegistryUniversalUpgradeable.TagType.GENERIC + fleetOwner, fleetId, providerId2, filter2, BITS_8, TagType.GENERIC ); uint256 swarm3 = _registerSwarm( - fleetOwner, fleetId, providerId3, filter3, BITS_8, SwarmRegistryUniversalUpgradeable.TagType.GENERIC + fleetOwner, fleetId, providerId3, filter3, BITS_8, TagType.GENERIC ); // Delete middle swarm @@ -861,7 +860,7 @@ contract SwarmRegistryUniversalTest is Test { } uint256 swarmId = _registerSwarm( - fleetOwner, fleetId, providerId, filterData, BITS_8, SwarmRegistryUniversalUpgradeable.TagType.GENERIC + fleetOwner, fleetId, providerId, filterData, BITS_8, TagType.GENERIC ); // Delete swarm @@ -883,7 +882,7 @@ contract SwarmRegistryUniversalTest is Test { uint256 fleetId = _registerFleet(fleetOwner, "f1"); uint256 providerId = _registerProvider(providerOwner, "url1"); uint256 swarmId = _registerSwarm( - fleetOwner, fleetId, providerId, new bytes(50), BITS_8, SwarmRegistryUniversalUpgradeable.TagType.GENERIC + fleetOwner, fleetId, providerId, new bytes(50), BITS_8, TagType.GENERIC ); vm.prank(caller); @@ -896,7 +895,7 @@ contract SwarmRegistryUniversalTest is Test { uint256 providerId1 = _registerProvider(providerOwner, "url1"); uint256 providerId2 = _registerProvider(providerOwner, "url2"); uint256 swarmId = _registerSwarm( - fleetOwner, fleetId, providerId1, new bytes(50), BITS_8, SwarmRegistryUniversalUpgradeable.TagType.GENERIC + fleetOwner, fleetId, providerId1, new bytes(50), BITS_8, TagType.GENERIC ); // Update provider then delete @@ -925,11 +924,11 @@ contract SwarmRegistryUniversalTest is Test { filter3[0] = 0x03; uint256 s1 = - _registerSwarm(fleetOwner, fleetId, p1, filter1, BITS_8, SwarmRegistryUniversalUpgradeable.TagType.GENERIC); + _registerSwarm(fleetOwner, fleetId, p1, filter1, BITS_8, TagType.GENERIC); uint256 s2 = - _registerSwarm(fleetOwner, fleetId, p2, filter2, BITS_8, SwarmRegistryUniversalUpgradeable.TagType.GENERIC); + _registerSwarm(fleetOwner, fleetId, p2, filter2, BITS_8, TagType.GENERIC); uint256 s3 = - _registerSwarm(fleetOwner, fleetId, p3, filter3, BITS_8, SwarmRegistryUniversalUpgradeable.TagType.GENERIC); + _registerSwarm(fleetOwner, fleetId, p3, filter3, BITS_8, TagType.GENERIC); // Verify initial indices assertEq(swarmRegistry.swarmIndexInUuid(s1), 0); @@ -953,7 +952,7 @@ contract SwarmRegistryUniversalTest is Test { uint256 fleetId = _registerFleet(fleetOwner, "f1"); uint256 providerId = _registerProvider(providerOwner, "url1"); uint256 swarmId = _registerSwarm( - fleetOwner, fleetId, providerId, new bytes(50), BITS_8, SwarmRegistryUniversalUpgradeable.TagType.GENERIC + fleetOwner, fleetId, providerId, new bytes(50), BITS_8, TagType.GENERIC ); (bool fleetValid, bool providerValid) = swarmRegistry.isSwarmValid(swarmId); @@ -965,7 +964,7 @@ contract SwarmRegistryUniversalTest is Test { uint256 fleetId = _registerFleet(fleetOwner, "f1"); uint256 providerId = _registerProvider(providerOwner, "url1"); uint256 swarmId = _registerSwarm( - fleetOwner, fleetId, providerId, new bytes(50), BITS_8, SwarmRegistryUniversalUpgradeable.TagType.GENERIC + fleetOwner, fleetId, providerId, new bytes(50), BITS_8, TagType.GENERIC ); vm.prank(providerOwner); @@ -980,7 +979,7 @@ contract SwarmRegistryUniversalTest is Test { uint256 fleetId = _registerFleet(fleetOwner, "f1"); uint256 providerId = _registerProvider(providerOwner, "url1"); uint256 swarmId = _registerSwarm( - fleetOwner, fleetId, providerId, new bytes(50), BITS_8, SwarmRegistryUniversalUpgradeable.TagType.GENERIC + fleetOwner, fleetId, providerId, new bytes(50), BITS_8, TagType.GENERIC ); // Burn registered fleet token (operator = owner for fresh registration) @@ -1004,7 +1003,7 @@ contract SwarmRegistryUniversalTest is Test { uint256 fleetId = _registerFleet(fleetOwner, "f1"); uint256 providerId = _registerProvider(providerOwner, "url1"); uint256 swarmId = _registerSwarm( - fleetOwner, fleetId, providerId, new bytes(50), BITS_8, SwarmRegistryUniversalUpgradeable.TagType.GENERIC + fleetOwner, fleetId, providerId, new bytes(50), BITS_8, TagType.GENERIC ); // Burn registered fleet token → mints owned-only token @@ -1038,7 +1037,7 @@ contract SwarmRegistryUniversalTest is Test { uint256 fleetId = _registerFleet(fleetOwner, "f1"); uint256 providerId = _registerProvider(providerOwner, "url1"); uint256 swarmId = _registerSwarm( - fleetOwner, fleetId, providerId, new bytes(50), BITS_8, SwarmRegistryUniversalUpgradeable.TagType.GENERIC + fleetOwner, fleetId, providerId, new bytes(50), BITS_8, TagType.GENERIC ); vm.prank(providerOwner); @@ -1058,7 +1057,7 @@ contract SwarmRegistryUniversalTest is Test { uint256 fleetId = _registerFleet(fleetOwner, "f1"); uint256 providerId = _registerProvider(providerOwner, "url1"); uint256 swarmId = _registerSwarm( - fleetOwner, fleetId, providerId, new bytes(50), BITS_8, SwarmRegistryUniversalUpgradeable.TagType.GENERIC + fleetOwner, fleetId, providerId, new bytes(50), BITS_8, TagType.GENERIC ); // Burn registered fleet token → mints owned-only token @@ -1091,9 +1090,9 @@ contract SwarmRegistryUniversalTest is Test { filter2[0] = 0x02; uint256 s1 = - _registerSwarm(fleetOwner, fleetId, p1, filter1, BITS_8, SwarmRegistryUniversalUpgradeable.TagType.GENERIC); + _registerSwarm(fleetOwner, fleetId, p1, filter1, BITS_8, TagType.GENERIC); uint256 s2 = - _registerSwarm(fleetOwner, fleetId, p2, filter2, BITS_8, SwarmRegistryUniversalUpgradeable.TagType.GENERIC); + _registerSwarm(fleetOwner, fleetId, p2, filter2, BITS_8, TagType.GENERIC); // Burn provider of s1 vm.prank(providerOwner); @@ -1118,7 +1117,7 @@ contract SwarmRegistryUniversalTest is Test { } uint256 swarmId = _registerSwarm( - fleetOwner, fleetId, providerId, filter, BITS_8, SwarmRegistryUniversalUpgradeable.TagType.GENERIC + fleetOwner, fleetId, providerId, filter, BITS_8, TagType.GENERIC ); vm.prank(providerOwner); @@ -1141,7 +1140,7 @@ contract SwarmRegistryUniversalTest is Test { uint256 fleetId = _registerFleet(fleetOwner, "f1"); uint256 providerId = _registerProvider(providerOwner, "url1"); uint256 swarmId = _registerSwarm( - fleetOwner, fleetId, providerId, new bytes(50), BITS_8, SwarmRegistryUniversalUpgradeable.TagType.GENERIC + fleetOwner, fleetId, providerId, new bytes(50), BITS_8, TagType.GENERIC ); vm.expectRevert(SwarmRegistryUniversalUpgradeable.SwarmNotOrphaned.selector); @@ -1156,7 +1155,7 @@ contract SwarmRegistryUniversalTest is Test { uint256 fleetId = _registerFleet(fleetOwner, "f1"); uint256 providerId = _registerProvider(providerOwner, "url1"); uint256 swarmId = _registerSwarm( - fleetOwner, fleetId, providerId, new bytes(50), BITS_8, SwarmRegistryUniversalUpgradeable.TagType.GENERIC + fleetOwner, fleetId, providerId, new bytes(50), BITS_8, TagType.GENERIC ); vm.prank(providerOwner); @@ -1171,7 +1170,7 @@ contract SwarmRegistryUniversalTest is Test { uint256 fleetId = _registerFleet(fleetOwner, "f1"); uint256 providerId = _registerProvider(providerOwner, "url1"); uint256 swarmId = _registerSwarm( - fleetOwner, fleetId, providerId, new bytes(50), BITS_8, SwarmRegistryUniversalUpgradeable.TagType.GENERIC + fleetOwner, fleetId, providerId, new bytes(50), BITS_8, TagType.GENERIC ); // Burn registered fleet token → mints owned-only token @@ -1193,7 +1192,7 @@ contract SwarmRegistryUniversalTest is Test { uint256 fleetId = _registerFleet(fleetOwner, "f1"); uint256 providerId = _registerProvider(providerOwner, "url1"); uint256 swarmId = _registerSwarm( - fleetOwner, fleetId, providerId, new bytes(50), BITS_8, SwarmRegistryUniversalUpgradeable.TagType.GENERIC + fleetOwner, fleetId, providerId, new bytes(50), BITS_8, TagType.GENERIC ); vm.prank(providerOwner); @@ -1207,7 +1206,7 @@ contract SwarmRegistryUniversalTest is Test { uint256 fleetId = _registerFleet(fleetOwner, "f1"); uint256 providerId = _registerProvider(providerOwner, "url1"); uint256 swarmId = _registerSwarm( - fleetOwner, fleetId, providerId, new bytes(50), BITS_8, SwarmRegistryUniversalUpgradeable.TagType.GENERIC + fleetOwner, fleetId, providerId, new bytes(50), BITS_8, TagType.GENERIC ); // Burn registered fleet token → mints owned-only token @@ -1229,7 +1228,7 @@ contract SwarmRegistryUniversalTest is Test { uint256 fleetId = _registerFleet(fleetOwner, "f1"); uint256 providerId = _registerProvider(providerOwner, "url1"); uint256 swarmId = _registerSwarm( - fleetOwner, fleetId, providerId, new bytes(50), BITS_8, SwarmRegistryUniversalUpgradeable.TagType.GENERIC + fleetOwner, fleetId, providerId, new bytes(50), BITS_8, TagType.GENERIC ); vm.prank(providerOwner); @@ -1284,7 +1283,7 @@ contract SwarmRegistryUniversalTest is Test { filter[h1] = bytes1(uint8(expectedFp)); uint256 swarmId = _registerSwarm( - fleetOwner, fleetId, providerId, filter, BITS_8, SwarmRegistryUniversalUpgradeable.TagType.GENERIC + fleetOwner, fleetId, providerId, filter, BITS_8, TagType.GENERIC ); // This should exercise the 8-bit path in _readFingerprint @@ -1320,7 +1319,7 @@ contract SwarmRegistryUniversalTest is Test { // 1 byte filter with 16-bit: m = 1/2 = 0 bytes memory filter = new bytes(1); uint256 swarmId = - _registerSwarm(fleetOwner, fleetId, providerId, filter, BITS_16, SwarmRegistryUniversalUpgradeable.TagType.GENERIC); + _registerSwarm(fleetOwner, fleetId, providerId, filter, BITS_16, TagType.GENERIC); // Should return false without reverting assertFalse(swarmRegistry.checkMembership(swarmId, keccak256("anyTag"))); @@ -1332,7 +1331,7 @@ contract SwarmRegistryUniversalTest is Test { vm.prank(fleetOwner); vm.expectRevert(SwarmRegistryUniversalUpgradeable.ProviderDoesNotExist.selector); swarmRegistry.registerSwarm( - _getFleetUuid(fleetId), 0, new bytes(32), BITS_8, SwarmRegistryUniversalUpgradeable.TagType.GENERIC + _getFleetUuid(fleetId), 0, new bytes(32), BITS_8, TagType.GENERIC ); } } diff --git a/test/upgradeable/UpgradeableContracts.t.sol b/test/upgradeable/UpgradeableContracts.t.sol index 664e44c4..9b4b23d6 100644 --- a/test/upgradeable/UpgradeableContracts.t.sol +++ b/test/upgradeable/UpgradeableContracts.t.sol @@ -10,6 +10,7 @@ import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import {ServiceProviderUpgradeable} from "../../src/swarms/ServiceProviderUpgradeable.sol"; import {FleetIdentityUpgradeable} from "../../src/swarms/FleetIdentityUpgradeable.sol"; import {SwarmRegistryUniversalUpgradeable} from "../../src/swarms/SwarmRegistryUniversalUpgradeable.sol"; +import {SwarmStatus, TagType, FingerprintSize} from "../../src/swarms/interfaces/SwarmTypes.sol"; import {MockERC20} from "../__helpers__/MockERC20.sol"; @@ -373,8 +374,8 @@ contract UpgradeableContractsTest is Test { // Register swarm with correct parameters bytes memory filterData = hex"0102030405"; - SwarmRegistryUniversalUpgradeable.FingerprintSize fpSize = SwarmRegistryUniversalUpgradeable.FingerprintSize.BITS_16; - SwarmRegistryUniversalUpgradeable.TagType tagType = SwarmRegistryUniversalUpgradeable.TagType.IBEACON_PAYLOAD_ONLY; + FingerprintSize fpSize = FingerprintSize.BITS_16; + TagType tagType = TagType.IBEACON_PAYLOAD_ONLY; vm.prank(alice); uint256 swarmId = swarmRegistry.registerSwarm(uuid, providerId, filterData, fpSize, tagType); From 359003f2fc58a71840793d991b5f17fcf74cfbdd Mon Sep 17 00:00:00 2001 From: Alex Sedighi Date: Fri, 6 Mar 2026 19:00:07 +1300 Subject: [PATCH 14/15] ci: remove whole-project minimum-coverage check The zgosalvez/github-actions-report-lcov action was checking 95% coverage against the entire project (32%). The custom shell script step below correctly filters and checks coverage for src/swarms/* files only. --- .github/workflows/checks.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/checks.yml b/.github/workflows/checks.yml index baa8bfbf..4522b15b 100644 --- a/.github/workflows/checks.yml +++ b/.github/workflows/checks.yml @@ -69,7 +69,6 @@ jobs: uses: zgosalvez/github-actions-report-lcov@v4 with: coverage-files: coverage.lcov - minimum-coverage: 95 github-token: ${{ secrets.GITHUB_TOKEN }} update-comment: true working-directory: ./ From 5e596431c18c58c152fe31a28a669d3f67bfc7fd Mon Sep 17 00:00:00 2001 From: Alex Sedighi Date: Fri, 6 Mar 2026 19:03:01 +1300 Subject: [PATCH 15/15] docs(swarms): document interfaces directory - Add Interface Files section to data-model.md - Add interfaces/ link to README documentation table - Reference SwarmTypes.sol location in assistant-guide.md --- src/swarms/doc/README.md | 1 + src/swarms/doc/assistant-guide.md | 2 +- src/swarms/doc/data-model.md | 13 +++++++++++++ 3 files changed, 15 insertions(+), 1 deletion(-) diff --git a/src/swarms/doc/README.md b/src/swarms/doc/README.md index 497e196a..1af04581 100644 --- a/src/swarms/doc/README.md +++ b/src/swarms/doc/README.md @@ -114,6 +114,7 @@ The system provides **non-enumerating** tag verification—individual tags aren' | Document | Description | | :--------------------------------------------------- | :------------------------------------------------ | +| [interfaces/](../interfaces/) | Solidity interface files (`IFleetIdentity`, `IServiceProvider`, `ISwarmRegistry`, `SwarmTypes`) | | [data-model.md](data-model.md) | Contract interfaces, enums, storage layout | | [fleet-registration.md](fleet-registration.md) | Fleet & UUID registration, tier economics | | [swarm-operations.md](swarm-operations.md) | Swarm registration, filters, provider approval | diff --git a/src/swarms/doc/assistant-guide.md b/src/swarms/doc/assistant-guide.md index f664e2ef..7ff4b548 100644 --- a/src/swarms/doc/assistant-guide.md +++ b/src/swarms/doc/assistant-guide.md @@ -278,7 +278,7 @@ When a fleet or provider NFT is burned, swarms referencing it become _orphaned_: The system supports different ways of constructing the unique `TagID` based on the hardware capabilities. -**Enum: `TagType`** +**Enum: `TagType`** (defined in `interfaces/SwarmTypes.sol`) - **`0x00`: IBEACON_PAYLOAD_ONLY** - **Format**: `UUID (16b) || Major (2b) || Minor (2b)` diff --git a/src/swarms/doc/data-model.md b/src/swarms/doc/data-model.md index 2e448af2..6e4bd4e2 100644 --- a/src/swarms/doc/data-model.md +++ b/src/swarms/doc/data-model.md @@ -1,5 +1,18 @@ # Data Model & Contract Interfaces +## Interface Files + +Public interfaces for external integrators and cross-contract calls: + +| Interface | Description | +| :---------------------------------------------- | :--------------------------------------------- | +| `interfaces/IFleetIdentity.sol` | FleetIdentity public API (ERC721Enumerable) | +| `interfaces/IServiceProvider.sol` | ServiceProvider public API (ERC721) | +| `interfaces/ISwarmRegistry.sol` | Common registry interface (L1 & Universal) | +| `interfaces/SwarmTypes.sol` | Shared enums: `RegistrationLevel`, `SwarmStatus`, `TagType`, `FingerprintSize` | + +These interfaces define the expected API surface for UUPS upgradeable contracts. + ## Contract Classes ```mermaid