|
| 1 | +// SPDX-License-Identifier: UNLICENSED |
| 2 | +pragma solidity ^0.8.19; |
| 3 | + |
| 4 | +import {SymTest} from "halmos-cheatcodes/SymTest.sol"; |
| 5 | +import {Test} from "forge-std/Test.sol"; |
| 6 | + |
| 7 | +import {IdRegistry} from "../../src/IdRegistry.sol"; |
| 8 | + |
| 9 | +contract IdRegistrySymTest is SymTest, Test { |
| 10 | + IdRegistry idRegistry; |
| 11 | + address trustedCaller; |
| 12 | + address x; |
| 13 | + address y; |
| 14 | + |
| 15 | + function setUp() public { |
| 16 | + // Setup IdRegistry |
| 17 | + idRegistry = new IdRegistry(address(this)); |
| 18 | + |
| 19 | + trustedCaller = address(0x1000); |
| 20 | + idRegistry.setTrustedCaller(trustedCaller); |
| 21 | + |
| 22 | + // Register fids |
| 23 | + vm.prank(trustedCaller); |
| 24 | + idRegistry.trustedRegister(address(0x1001), address(0x2001)); |
| 25 | + vm.prank(trustedCaller); |
| 26 | + idRegistry.trustedRegister(address(0x1002), address(0x2002)); |
| 27 | + |
| 28 | + assert(idRegistry.idOf(address(0x1001)) == 1); |
| 29 | + assert(idRegistry.idOf(address(0x1002)) == 2); |
| 30 | + |
| 31 | + assert(idRegistry.recoveryOf(1) == address(0x2001)); |
| 32 | + assert(idRegistry.recoveryOf(2) == address(0x2002)); |
| 33 | + |
| 34 | + // Create symbolic addresses |
| 35 | + x = svm.createAddress("x"); |
| 36 | + y = svm.createAddress("y"); |
| 37 | + } |
| 38 | + |
| 39 | + function check_Invariants(bytes4 selector, address caller) public { |
| 40 | + _initState(); |
| 41 | + vm.assume(x != y); |
| 42 | + |
| 43 | + // Record pre-state |
| 44 | + uint256 oldIdX = idRegistry.idOf(x); |
| 45 | + uint256 oldIdY = idRegistry.idOf(y); |
| 46 | + uint256 oldIdCounter = idRegistry.idCounter(); |
| 47 | + bool oldPaused = idRegistry.paused(); |
| 48 | + |
| 49 | + // Execute an arbitrary tx to IdRegistry |
| 50 | + vm.prank(caller); |
| 51 | + (bool success,) = address(idRegistry).call(_calldataFor(selector)); |
| 52 | + vm.assume(success); // ignore reverting cases |
| 53 | + |
| 54 | + // Record post-state |
| 55 | + uint256 newIdX = idRegistry.idOf(x); |
| 56 | + uint256 newIdY = idRegistry.idOf(y); |
| 57 | + uint256 newIdCounter = idRegistry.idCounter(); |
| 58 | + |
| 59 | + // Ensure that there is no recovery address associated with fid 0. |
| 60 | + assert(idRegistry.recoveryOf(0) == address(0)); |
| 61 | + |
| 62 | + // Ensure that idCounter never decreases. |
| 63 | + assert(newIdCounter >= oldIdCounter); |
| 64 | + |
| 65 | + // If a new fid is registered, ensure that: |
| 66 | + // - IdRegistry must not be paused. |
| 67 | + // - idCounter must increase by 1. |
| 68 | + // - The new fid must not be registered for an existing fid owner. |
| 69 | + // - The existing fids must be preserved. |
| 70 | + if (newIdCounter > oldIdCounter) { |
| 71 | + assert(oldPaused == false); |
| 72 | + assert(newIdCounter - oldIdCounter == 1); |
| 73 | + assert(newIdX == oldIdX || oldIdX == 0); |
| 74 | + assert(newIdX == oldIdX || newIdY == oldIdY); |
| 75 | + } |
| 76 | + } |
| 77 | + |
| 78 | + function check_Transfer(address caller, address to, address other) public { |
| 79 | + _initState(); |
| 80 | + vm.assume(other != caller && other != to); |
| 81 | + |
| 82 | + // Record pre-state |
| 83 | + uint256 oldIdCaller = idRegistry.idOf(caller); |
| 84 | + uint256 oldIdTo = idRegistry.idOf(to); |
| 85 | + uint256 oldIdOther = idRegistry.idOf(other); |
| 86 | + |
| 87 | + // Execute transfer with symbolic arguments |
| 88 | + vm.prank(caller); |
| 89 | + idRegistry.transfer(to, svm.createUint256("deadline"), svm.createBytes(65, "sig")); |
| 90 | + |
| 91 | + // Record post-state |
| 92 | + uint256 newIdCaller = idRegistry.idOf(caller); |
| 93 | + uint256 newIdTo = idRegistry.idOf(to); |
| 94 | + uint256 newIdOther = idRegistry.idOf(other); |
| 95 | + |
| 96 | + // Ensure that the fid has been transferred from the `caller` to the `to`. |
| 97 | + assert(newIdTo == oldIdCaller); |
| 98 | + if (caller != to) { |
| 99 | + assert(oldIdCaller != 0 && newIdCaller == 0); |
| 100 | + assert(oldIdTo == 0 && newIdTo != 0); |
| 101 | + } |
| 102 | + |
| 103 | + // Ensure that the other fids are not affected. |
| 104 | + assert(newIdOther == oldIdOther); |
| 105 | + } |
| 106 | + |
| 107 | + function check_Recovery(address caller, address from, address to, address other) public { |
| 108 | + _initState(); |
| 109 | + vm.assume(other != from && other != to); |
| 110 | + |
| 111 | + // Record pre-state |
| 112 | + uint256 oldIdFrom = idRegistry.idOf(from); |
| 113 | + uint256 oldIdTo = idRegistry.idOf(to); |
| 114 | + uint256 oldIdOther = idRegistry.idOf(other); |
| 115 | + address oldRecoveryFrom = idRegistry.recoveryOf(oldIdFrom); |
| 116 | + |
| 117 | + // Execute recover with symbolic arguments |
| 118 | + vm.prank(caller); |
| 119 | + idRegistry.recover(from, to, svm.createUint256("deadline"), svm.createBytes(65, "sig")); |
| 120 | + |
| 121 | + // Record post-state |
| 122 | + uint256 newIdFrom = idRegistry.idOf(from); |
| 123 | + uint256 newIdTo = idRegistry.idOf(to); |
| 124 | + uint256 newIdOther = idRegistry.idOf(other); |
| 125 | + |
| 126 | + // Ensure that the caller is the recovery address |
| 127 | + assert(caller == oldRecoveryFrom); |
| 128 | + |
| 129 | + // Ensure that the fid has been transferred from the `from` to the `to`. |
| 130 | + assert(newIdTo == oldIdFrom); |
| 131 | + if (from != to) { |
| 132 | + assert(oldIdFrom != 0 && newIdFrom == 0); |
| 133 | + assert(oldIdTo == 0 && newIdTo != 0); |
| 134 | + } |
| 135 | + |
| 136 | + // Ensure that the other fids are not affected. |
| 137 | + assert(newIdOther == oldIdOther); |
| 138 | + } |
| 139 | + |
| 140 | + /*////////////////////////////////////////////////////////////// |
| 141 | + HELPERS |
| 142 | + //////////////////////////////////////////////////////////////*/ |
| 143 | + |
| 144 | + /** |
| 145 | + * @dev Initialize IdRegistry with symbolic arguments for state. |
| 146 | + */ |
| 147 | + function _initState() public { |
| 148 | + if (svm.createBool("disableTrustedOnly?")) { |
| 149 | + idRegistry.disableTrustedOnly(); |
| 150 | + } |
| 151 | + if (svm.createBool("pause?")) { |
| 152 | + idRegistry.pause(); |
| 153 | + } |
| 154 | + } |
| 155 | + |
| 156 | + /** |
| 157 | + * @dev Generates valid calldata for a given function selector. |
| 158 | + */ |
| 159 | + function _calldataFor(bytes4 selector) internal returns (bytes memory) { |
| 160 | + bytes memory args; |
| 161 | + if (selector == idRegistry.registerFor.selector) { |
| 162 | + args = abi.encode( |
| 163 | + svm.createAddress("to"), |
| 164 | + svm.createAddress("recovery"), |
| 165 | + svm.createUint256("deadline"), |
| 166 | + svm.createBytes(65, "sig") |
| 167 | + ); |
| 168 | + } else if (selector == idRegistry.transfer.selector) { |
| 169 | + args = abi.encode(svm.createAddress("to"), svm.createUint256("deadline"), svm.createBytes(65, "sig")); |
| 170 | + } else if (selector == idRegistry.transferFor.selector) { |
| 171 | + args = abi.encode( |
| 172 | + svm.createAddress("from"), |
| 173 | + svm.createAddress("to"), |
| 174 | + svm.createUint256("fromDeadline"), |
| 175 | + svm.createBytes(65, "fromSig"), |
| 176 | + svm.createUint256("toDeadline"), |
| 177 | + svm.createBytes(65, "toSig") |
| 178 | + ); |
| 179 | + } else if (selector == idRegistry.changeRecoveryAddressFor.selector) { |
| 180 | + args = abi.encode( |
| 181 | + svm.createAddress("owner"), |
| 182 | + svm.createAddress("recovery"), |
| 183 | + svm.createUint256("deadline"), |
| 184 | + svm.createBytes(65, "sig") |
| 185 | + ); |
| 186 | + } else if (selector == idRegistry.recover.selector) { |
| 187 | + args = abi.encode( |
| 188 | + svm.createAddress("from"), |
| 189 | + svm.createAddress("to"), |
| 190 | + svm.createUint256("deadline"), |
| 191 | + svm.createBytes(65, "sig") |
| 192 | + ); |
| 193 | + } else if (selector == idRegistry.recoverFor.selector) { |
| 194 | + args = abi.encode( |
| 195 | + svm.createAddress("from"), |
| 196 | + svm.createAddress("to"), |
| 197 | + svm.createUint256("recoveryDeadline"), |
| 198 | + svm.createBytes(65, "recoverySig"), |
| 199 | + svm.createUint256("toDeadline"), |
| 200 | + svm.createBytes(65, "toSig") |
| 201 | + ); |
| 202 | + } else if (selector == idRegistry.verifyFidSignature.selector) { |
| 203 | + args = abi.encode( |
| 204 | + svm.createAddress("custodyAddress"), |
| 205 | + svm.createUint256("fid"), |
| 206 | + svm.createBytes32("digest"), |
| 207 | + svm.createBytes(65, "sig") |
| 208 | + ); |
| 209 | + } else { |
| 210 | + args = svm.createBytes(1024, "data"); |
| 211 | + } |
| 212 | + |
| 213 | + return abi.encodePacked(selector, args); |
| 214 | + } |
| 215 | +} |
0 commit comments