| 1 | // SPDX-License-Identifier: MIT |
| 2 | pragma solidity >=0.7.0 <0.9.0; |
| 3 | |
| 4 | import {Test, StdUtils} from "../src/Test.sol"; |
| 5 | |
| 6 | contract StdUtilsMock is StdUtils { |
| 7 | // We deploy a mock version so we can properly test expected reverts. |
| 8 | function exposed_getTokenBalances(address token, address[] memory addresses) |
| 9 | external |
| 10 | returns (uint256[] memory balances) |
| 11 | { |
| 12 | return getTokenBalances(token, addresses); |
| 13 | } |
| 14 | |
| 15 | function exposed_bound(int256 num, int256 min, int256 max) external pure returns (int256) { |
| 16 | return bound(num, min, max); |
| 17 | } |
| 18 | |
| 19 | function exposed_bound(uint256 num, uint256 min, uint256 max) external pure returns (uint256) { |
| 20 | return bound(num, min, max); |
| 21 | } |
| 22 | |
| 23 | function exposed_bytesToUint(bytes memory b) external pure returns (uint256) { |
| 24 | return bytesToUint(b); |
| 25 | } |
| 26 | } |
| 27 | |
| 28 | contract StdUtilsTest is Test { |
| 29 | /*////////////////////////////////////////////////////////////////////////// |
| 30 | BOUND UINT |
| 31 | //////////////////////////////////////////////////////////////////////////*/ |
| 32 | |
| 33 | function test_Bound() public pure { |
| 34 | assertEq(bound(uint256(5), 0, 4), 0); |
| 35 | assertEq(bound(uint256(0), 69, 69), 69); |
| 36 | assertEq(bound(uint256(0), 68, 69), 68); |
| 37 | assertEq(bound(uint256(10), 150, 190), 174); |
| 38 | assertEq(bound(uint256(300), 2800, 3200), 3107); |
| 39 | assertEq(bound(uint256(9999), 1337, 6666), 4669); |
| 40 | } |
| 41 | |
| 42 | function test_Bound_WithinRange() public pure { |
| 43 | assertEq(bound(uint256(51), 50, 150), 51); |
| 44 | assertEq(bound(uint256(51), 50, 150), bound(bound(uint256(51), 50, 150), 50, 150)); |
| 45 | assertEq(bound(uint256(149), 50, 150), 149); |
| 46 | assertEq(bound(uint256(149), 50, 150), bound(bound(uint256(149), 50, 150), 50, 150)); |
| 47 | } |
| 48 | |
| 49 | function test_Bound_EdgeCoverage() public pure { |
| 50 | assertEq(bound(uint256(0), 50, 150), 50); |
| 51 | assertEq(bound(uint256(1), 50, 150), 51); |
| 52 | assertEq(bound(uint256(2), 50, 150), 52); |
| 53 | assertEq(bound(uint256(3), 50, 150), 53); |
| 54 | assertEq(bound(type(uint256).max, 50, 150), 150); |
| 55 | assertEq(bound(type(uint256).max - 1, 50, 150), 149); |
| 56 | assertEq(bound(type(uint256).max - 2, 50, 150), 148); |
| 57 | assertEq(bound(type(uint256).max - 3, 50, 150), 147); |
| 58 | } |
| 59 | |
| 60 | function testFuzz_Bound_DistributionIsEven(uint256 min, uint256 size) public pure { |
| 61 | size = size % 100 + 1; |
| 62 | min = bound(min, UINT256_MAX / 2, UINT256_MAX / 2 + size); |
| 63 | uint256 max = min + size - 1; |
| 64 | uint256 result; |
| 65 | |
| 66 | for (uint256 i = 1; i <= size * 4; ++i) { |
| 67 | // x > max |
| 68 | result = bound(max + i, min, max); |
| 69 | assertEq(result, min + (i - 1) % size); |
| 70 | // x < min |
| 71 | result = bound(min - i, min, max); |
| 72 | assertEq(result, max - (i - 1) % size); |
| 73 | } |
| 74 | } |
| 75 | |
| 76 | function testFuzz_Bound(uint256 num, uint256 min, uint256 max) public pure { |
| 77 | if (min > max) (min, max) = (max, min); |
| 78 | |
| 79 | uint256 result = bound(num, min, max); |
| 80 | |
| 81 | assertGe(result, min); |
| 82 | assertLe(result, max); |
| 83 | assertEq(result, bound(result, min, max)); |
| 84 | if (num >= min && num <= max) assertEq(result, num); |
| 85 | } |
| 86 | |
| 87 | function test_BoundUint256Max() public pure { |
| 88 | assertEq(bound(0, type(uint256).max - 1, type(uint256).max), type(uint256).max - 1); |
| 89 | assertEq(bound(1, type(uint256).max - 1, type(uint256).max), type(uint256).max); |
| 90 | } |
| 91 | |
| 92 | function test_RevertIf_BoundMaxLessThanMin() public { |
| 93 | // We deploy a mock version so we can properly test the revert. |
| 94 | StdUtilsMock stdUtils = new StdUtilsMock(); |
| 95 | |
| 96 | vm.expectRevert(bytes("StdUtils bound(uint256,uint256,uint256): Max is less than min.")); |
| 97 | stdUtils.exposed_bound(uint256(5), 100, 10); |
| 98 | } |
| 99 | |
| 100 | function testFuzz_RevertIf_BoundMaxLessThanMin(uint256 num, uint256 min, uint256 max) public { |
| 101 | // We deploy a mock version so we can properly test the revert. |
| 102 | StdUtilsMock stdUtils = new StdUtilsMock(); |
| 103 | |
| 104 | vm.assume(min > max); |
| 105 | vm.expectRevert(bytes("StdUtils bound(uint256,uint256,uint256): Max is less than min.")); |
| 106 | stdUtils.exposed_bound(num, min, max); |
| 107 | } |
| 108 | |
| 109 | /*////////////////////////////////////////////////////////////////////////// |
| 110 | BOUND INT |
| 111 | //////////////////////////////////////////////////////////////////////////*/ |
| 112 | |
| 113 | function test_BoundInt() public pure { |
| 114 | assertEq(bound(-3, 0, 4), 2); |
| 115 | assertEq(bound(0, -69, -69), -69); |
| 116 | assertEq(bound(0, -69, -68), -68); |
| 117 | assertEq(bound(-10, 150, 190), 154); |
| 118 | assertEq(bound(-300, 2800, 3200), 2908); |
| 119 | assertEq(bound(9999, -1337, 6666), 1995); |
| 120 | } |
| 121 | |
| 122 | function test_BoundInt_WithinRange() public pure { |
| 123 | assertEq(bound(51, -50, 150), 51); |
| 124 | assertEq(bound(51, -50, 150), bound(bound(51, -50, 150), -50, 150)); |
| 125 | assertEq(bound(149, -50, 150), 149); |
| 126 | assertEq(bound(149, -50, 150), bound(bound(149, -50, 150), -50, 150)); |
| 127 | } |
| 128 | |
| 129 | function test_BoundInt_EdgeCoverage() public pure { |
| 130 | assertEq(bound(type(int256).min, -50, 150), -50); |
| 131 | assertEq(bound(type(int256).min + 1, -50, 150), -49); |
| 132 | assertEq(bound(type(int256).min + 2, -50, 150), -48); |
| 133 | assertEq(bound(type(int256).min + 3, -50, 150), -47); |
| 134 | assertEq(bound(type(int256).min, 10, 150), 10); |
| 135 | assertEq(bound(type(int256).min + 1, 10, 150), 11); |
| 136 | assertEq(bound(type(int256).min + 2, 10, 150), 12); |
| 137 | assertEq(bound(type(int256).min + 3, 10, 150), 13); |
| 138 | |
| 139 | assertEq(bound(type(int256).max, -50, 150), 150); |
| 140 | assertEq(bound(type(int256).max - 1, -50, 150), 149); |
| 141 | assertEq(bound(type(int256).max - 2, -50, 150), 148); |
| 142 | assertEq(bound(type(int256).max - 3, -50, 150), 147); |
| 143 | assertEq(bound(type(int256).max, -50, -10), -10); |
| 144 | assertEq(bound(type(int256).max - 1, -50, -10), -11); |
| 145 | assertEq(bound(type(int256).max - 2, -50, -10), -12); |
| 146 | assertEq(bound(type(int256).max - 3, -50, -10), -13); |
| 147 | } |
| 148 | |
| 149 | function testFuzz_BoundInt_DistributionIsEven(int256 min, uint256 size) public pure { |
| 150 | size = size % 100 + 1; |
| 151 | min = bound(min, -int256(size / 2), int256(size - size / 2)); |
| 152 | int256 max = min + int256(size) - 1; |
| 153 | int256 result; |
| 154 | |
| 155 | for (uint256 i = 1; i <= size * 4; ++i) { |
| 156 | // x > max |
| 157 | result = bound(max + int256(i), min, max); |
| 158 | assertEq(result, min + int256((i - 1) % size)); |
| 159 | // x < min |
| 160 | result = bound(min - int256(i), min, max); |
| 161 | assertEq(result, max - int256((i - 1) % size)); |
| 162 | } |
| 163 | } |
| 164 | |
| 165 | function testFuzz_BoundInt(int256 num, int256 min, int256 max) public pure { |
| 166 | if (min > max) (min, max) = (max, min); |
| 167 | |
| 168 | int256 result = bound(num, min, max); |
| 169 | |
| 170 | assertGe(result, min); |
| 171 | assertLe(result, max); |
| 172 | assertEq(result, bound(result, min, max)); |
| 173 | if (num >= min && num <= max) assertEq(result, num); |
| 174 | } |
| 175 | |
| 176 | function test_BoundIntInt256Max() public pure { |
| 177 | assertEq(bound(0, type(int256).max - 1, type(int256).max), type(int256).max - 1); |
| 178 | assertEq(bound(1, type(int256).max - 1, type(int256).max), type(int256).max); |
| 179 | } |
| 180 | |
| 181 | function test_BoundIntInt256Min() public pure { |
| 182 | assertEq(bound(0, type(int256).min, type(int256).min + 1), type(int256).min); |
| 183 | assertEq(bound(1, type(int256).min, type(int256).min + 1), type(int256).min + 1); |
| 184 | } |
| 185 | |
| 186 | function test_RevertIf_BoundIntMaxLessThanMin() public { |
| 187 | // We deploy a mock version so we can properly test the revert. |
| 188 | StdUtilsMock stdUtils = new StdUtilsMock(); |
| 189 | |
| 190 | vm.expectRevert(bytes("StdUtils bound(int256,int256,int256): Max is less than min.")); |
| 191 | stdUtils.exposed_bound(-5, 100, 10); |
| 192 | } |
| 193 | |
| 194 | function testFuzz_RevertIf_BoundIntMaxLessThanMin(int256 num, int256 min, int256 max) public { |
| 195 | // We deploy a mock version so we can properly test the revert. |
| 196 | StdUtilsMock stdUtils = new StdUtilsMock(); |
| 197 | |
| 198 | vm.assume(min > max); |
| 199 | vm.expectRevert(bytes("StdUtils bound(int256,int256,int256): Max is less than min.")); |
| 200 | stdUtils.exposed_bound(num, min, max); |
| 201 | } |
| 202 | |
| 203 | /*////////////////////////////////////////////////////////////////////////// |
| 204 | BOUND PRIVATE KEY |
| 205 | //////////////////////////////////////////////////////////////////////////*/ |
| 206 | |
| 207 | function test_BoundPrivateKey() public pure { |
| 208 | assertEq(boundPrivateKey(0), 1); |
| 209 | assertEq(boundPrivateKey(1), 1); |
| 210 | assertEq(boundPrivateKey(300), 300); |
| 211 | assertEq(boundPrivateKey(9999), 9999); |
| 212 | assertEq(boundPrivateKey(SECP256K1_ORDER - 1), SECP256K1_ORDER - 1); |
| 213 | assertEq(boundPrivateKey(SECP256K1_ORDER), 1); |
| 214 | assertEq(boundPrivateKey(SECP256K1_ORDER + 1), 2); |
| 215 | assertEq(boundPrivateKey(UINT256_MAX), UINT256_MAX & SECP256K1_ORDER - 1); // x&y is equivalent to x-x%y |
| 216 | } |
| 217 | |
| 218 | /*////////////////////////////////////////////////////////////////////////// |
| 219 | BYTES TO UINT |
| 220 | //////////////////////////////////////////////////////////////////////////*/ |
| 221 | |
| 222 | function test_BytesToUint() external pure { |
| 223 | bytes memory maxUint = hex"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"; |
| 224 | bytes memory two = hex"02"; |
| 225 | bytes memory millionEther = hex"d3c21bcecceda1000000"; |
| 226 | |
| 227 | assertEq(bytesToUint(maxUint), type(uint256).max); |
| 228 | assertEq(bytesToUint(two), 2); |
| 229 | assertEq(bytesToUint(millionEther), 1_000_000 ether); |
| 230 | } |
| 231 | |
| 232 | function test_RevertIf_BytesLengthExceeds32() external { |
| 233 | // We deploy a mock version so we can properly test the revert. |
| 234 | StdUtilsMock stdUtils = new StdUtilsMock(); |
| 235 | |
| 236 | bytes memory thirty3Bytes = hex"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"; |
| 237 | vm.expectRevert("StdUtils bytesToUint(bytes): Bytes length exceeds 32."); |
| 238 | stdUtils.exposed_bytesToUint(thirty3Bytes); |
| 239 | } |
| 240 | |
| 241 | /*////////////////////////////////////////////////////////////////////////// |
| 242 | COMPUTE CREATE ADDRESS |
| 243 | //////////////////////////////////////////////////////////////////////////*/ |
| 244 | |
| 245 | function test_ComputeCreateAddress() external pure { |
| 246 | address deployer = 0x6C9FC64A53c1b71FB3f9Af64d1ae3A4931A5f4E9; |
| 247 | uint256 nonce = 14; |
| 248 | address createAddress = computeCreateAddress(deployer, nonce); |
| 249 | assertEq(createAddress, 0x68b3465833fb72A70ecDF485E0e4C7bD8665Fc45); |
| 250 | } |
| 251 | |
| 252 | /*////////////////////////////////////////////////////////////////////////// |
| 253 | COMPUTE CREATE2 ADDRESS |
| 254 | //////////////////////////////////////////////////////////////////////////*/ |
| 255 | |
| 256 | function test_ComputeCreate2Address() external pure { |
| 257 | bytes32 salt = bytes32(uint256(31415)); |
| 258 | bytes32 initcodeHash = keccak256(abi.encode(0x6080)); |
| 259 | address deployer = 0x6C9FC64A53c1b71FB3f9Af64d1ae3A4931A5f4E9; |
| 260 | address create2Address = computeCreate2Address(salt, initcodeHash, deployer); |
| 261 | assertEq(create2Address, 0xB147a5d25748fda14b463EB04B111027C290f4d3); |
| 262 | } |
| 263 | |
| 264 | function test_ComputeCreate2AddressWithDefaultDeployer() external pure { |
| 265 | bytes32 salt = 0xc290c670fde54e5ef686f9132cbc8711e76a98f0333a438a92daa442c71403c0; |
| 266 | bytes32 initcodeHash = hashInitCode(hex"6080", ""); |
| 267 | assertEq(initcodeHash, 0x1a578b7a4b0b5755db6d121b4118d4bc68fe170dca840c59bc922f14175a76b0); |
| 268 | address create2Address = computeCreate2Address(salt, initcodeHash); |
| 269 | assertEq(create2Address, 0xc0ffEe2198a06235aAbFffe5Db0CacF1717f5Ac6); |
| 270 | } |
| 271 | } |
| 272 | |
| 273 | contract StdUtilsForkTest is Test { |
| 274 | /*////////////////////////////////////////////////////////////////////////// |
| 275 | GET TOKEN BALANCES |
| 276 | //////////////////////////////////////////////////////////////////////////*/ |
| 277 | |
| 278 | address internal SHIB = 0x95aD61b0a150d79219dCF64E1E6Cc01f0B64C4cE; |
| 279 | address internal SHIB_HOLDER_0 = 0x855F5981e831D83e6A4b4EBFCAdAa68D92333170; |
| 280 | address internal SHIB_HOLDER_1 = 0x8F509A90c2e47779cA408Fe00d7A72e359229AdA; |
| 281 | address internal SHIB_HOLDER_2 = 0x0e3bbc0D04fF62211F71f3e4C45d82ad76224385; |
| 282 | |
| 283 | address internal USDC = 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48; |
| 284 | address internal USDC_HOLDER_0 = 0xDa9CE944a37d218c3302F6B82a094844C6ECEb17; |
| 285 | address internal USDC_HOLDER_1 = 0x3e67F4721E6d1c41a015f645eFa37BEd854fcf52; |
| 286 | |
| 287 | function setUp() public { |
| 288 | // All tests of the `getTokenBalances` method are fork tests using live contracts. |
| 289 | vm.createSelectFork({urlOrAlias: "mainnet", blockNumber: 16_428_900}); |
| 290 | } |
| 291 | |
| 292 | function test_RevertIf_CannotGetTokenBalances_NonTokenContract() external { |
| 293 | // We deploy a mock version so we can properly test the revert. |
| 294 | StdUtilsMock stdUtils = new StdUtilsMock(); |
| 295 | |
| 296 | // The UniswapV2Factory contract has neither a `balanceOf` function nor a fallback function, |
| 297 | // so the `balanceOf` call should revert. |
| 298 | address token = address(0x5C69bEe701ef814a2B6a3EDD4B1652CB9cc5aA6f); |
| 299 | address[] memory addresses = new address[](1); |
| 300 | addresses[0] = USDC_HOLDER_0; |
| 301 | |
| 302 | vm.expectRevert("Multicall3: call failed"); |
| 303 | stdUtils.exposed_getTokenBalances(token, addresses); |
| 304 | } |
| 305 | |
| 306 | function test_RevertIf_CannotGetTokenBalances_EOA() external { |
| 307 | // We deploy a mock version so we can properly test the revert. |
| 308 | StdUtilsMock stdUtils = new StdUtilsMock(); |
| 309 | |
| 310 | address eoa = vm.addr({privateKey: 1}); |
| 311 | address[] memory addresses = new address[](1); |
| 312 | addresses[0] = USDC_HOLDER_0; |
| 313 | vm.expectRevert("StdUtils getTokenBalances(address,address[]): Token address is not a contract."); |
| 314 | stdUtils.exposed_getTokenBalances(eoa, addresses); |
| 315 | } |
| 316 | |
| 317 | function test_GetTokenBalances_Empty() external { |
| 318 | address[] memory addresses = new address[](0); |
| 319 | uint256[] memory balances = getTokenBalances(USDC, addresses); |
| 320 | assertEq(balances.length, 0); |
| 321 | } |
| 322 | |
| 323 | function test_GetTokenBalances_USDC() external { |
| 324 | address[] memory addresses = new address[](2); |
| 325 | addresses[0] = USDC_HOLDER_0; |
| 326 | addresses[1] = USDC_HOLDER_1; |
| 327 | uint256[] memory balances = getTokenBalances(USDC, addresses); |
| 328 | assertEq(balances[0], 159_000_000_000_000); |
| 329 | assertEq(balances[1], 131_350_000_000_000); |
| 330 | } |
| 331 | |
| 332 | function test_GetTokenBalances_SHIB() external { |
| 333 | address[] memory addresses = new address[](3); |
| 334 | addresses[0] = SHIB_HOLDER_0; |
| 335 | addresses[1] = SHIB_HOLDER_1; |
| 336 | addresses[2] = SHIB_HOLDER_2; |
| 337 | uint256[] memory balances = getTokenBalances(SHIB, addresses); |
| 338 | assertEq(balances[0], 3_323_256_285_484.42e18); |
| 339 | assertEq(balances[1], 1_271_702_771_149.99999928e18); |
| 340 | assertEq(balances[2], 606_357_106_247e18); |
| 341 | } |
| 342 | } |