diff --git a/src/generated/FlareFtsoWords.pointers.sol b/src/generated/FlareFtsoWords.pointers.sol index 6f6a67f..179ce2b 100644 --- a/src/generated/FlareFtsoWords.pointers.sol +++ b/src/generated/FlareFtsoWords.pointers.sol @@ -10,7 +10,7 @@ pragma solidity ^0.8.25; // file needs the contract to exist so that it can be compiled. /// @dev Hash of the known bytecode. -bytes32 constant BYTECODE_HASH = bytes32(0x69ffc4160d6551b77f501f51e6e53d7825dcd7318519779f877261ebd37b7da0); +bytes32 constant BYTECODE_HASH = bytes32(0x068dba29da80c86736f8a1ea3cc5bae76f15405f03046cda8e9b99fe0a654acf); /// @dev The hash of the meta that describes the contract. bytes32 constant DESCRIBED_BY_META_HASH = bytes32(0x4ccb316fd35a2abbee0016f38926a4c95b6270b229c85a9acf8a3a52b36bfedc); diff --git a/src/lib/registry/LibFlareContractRegistry.sol b/src/lib/registry/LibFlareContractRegistry.sol index 40b4e87..0fb7558 100644 --- a/src/lib/registry/LibFlareContractRegistry.sol +++ b/src/lib/registry/LibFlareContractRegistry.sol @@ -30,23 +30,38 @@ string constant FTSO_V2_LTS_NAME = "FtsoV2"; /// contract registry. string constant FEE_CALCULATOR_NAME = "FeeCalculator"; +/// @notice Thrown when a name lookup in the Flare contract registry returns +/// address(0). IFlareContractRegistry documents address(0) as the not-found +/// sentinel; propagating a zero-typed contract handle silently would cause +/// every subsequent call to revert with a confusing low-level error. +/// @param name The registry name that resolved to address(0). +error ContractNotRegistered(string name); + library LibFlareContractRegistry { /// Sugar for getting the FTSO registry address from the Flare contract - /// registry. + /// registry. Reverts with ContractNotRegistered if the name is not found. function getFtsoRegistry() internal view returns (IFtsoRegistry) { - return IFtsoRegistry(FLARE_CONTRACT_REGISTRY.getContractAddressByName(FTSO_REGISTRY_NAME)); + address addr = FLARE_CONTRACT_REGISTRY.getContractAddressByName(FTSO_REGISTRY_NAME); + if (addr == address(0)) revert ContractNotRegistered(FTSO_REGISTRY_NAME); + return IFtsoRegistry(addr); } /// Sugar for getting the FTSO V2 LTS contract address from the Flare - /// contract registry. + /// contract registry. Reverts with ContractNotRegistered if the name is not + /// found. //forge-lint: disable-next-line(mixed-case-function) function getFtsoV2LTS() internal view returns (FtsoV2Interface) { - return FtsoV2Interface(FLARE_CONTRACT_REGISTRY.getContractAddressByName(FTSO_V2_LTS_NAME)); + address addr = FLARE_CONTRACT_REGISTRY.getContractAddressByName(FTSO_V2_LTS_NAME); + if (addr == address(0)) revert ContractNotRegistered(FTSO_V2_LTS_NAME); + return FtsoV2Interface(addr); } /// Sugar for getting the FeeCalculator contract address from the Flare - /// contract registry. + /// contract registry. Reverts with ContractNotRegistered if the name is not + /// found. function getFeeCalculator() internal view returns (IFeeCalculator) { - return IFeeCalculator(FLARE_CONTRACT_REGISTRY.getContractAddressByName(FEE_CALCULATOR_NAME)); + address addr = FLARE_CONTRACT_REGISTRY.getContractAddressByName(FEE_CALCULATOR_NAME); + if (addr == address(0)) revert ContractNotRegistered(FEE_CALCULATOR_NAME); + return IFeeCalculator(addr); } } diff --git a/test/src/lib/registry/LibFlareContractRegistry.t.sol b/test/src/lib/registry/LibFlareContractRegistry.t.sol index ba0d4ac..63e5b3f 100644 --- a/test/src/lib/registry/LibFlareContractRegistry.t.sol +++ b/test/src/lib/registry/LibFlareContractRegistry.t.sol @@ -7,16 +7,21 @@ import {LibFork} from "test/fork/LibFork.sol"; import { LibFlareContractRegistry, IFtsoRegistry, + FLARE_CONTRACT_REGISTRY, FTSO_REGISTRY_NAME, FTSO_V2_LTS_NAME, - FEE_CALCULATOR_NAME + FEE_CALCULATOR_NAME, + ContractNotRegistered } from "src/lib/registry/LibFlareContractRegistry.sol"; +import {LibFlareContractRegistryExternal} from "test/src/lib/registry/LibFlareContractRegistryExternal.sol"; import {FtsoV2Interface} from "src/vendor/flare-smart-contracts-v2/userInterfaces/LTS/FtsoV2Interface.sol"; import {IFeeCalculator} from "src/vendor/flare-smart-contracts-v2/userInterfaces/IFeeCalculator.sol"; uint256 constant BLOCK_NUMBER = 31843105; contract LibFlareContractRegistryTest is Test { + LibFlareContractRegistryExternal internal external_ = new LibFlareContractRegistryExternal(); + constructor() { vm.createSelectFork(LibFork.rpcUrlFlare(vm), BLOCK_NUMBER); } @@ -26,6 +31,44 @@ contract LibFlareContractRegistryTest is Test { assertEq(address(ftsoRegistry), address(0x13DC2b5053857AE17a4f95aFF55530b267F3E040)); } + /// When the Flare contract registry resolves the FtsoRegistry name to + /// address(0) (the documented not-found sentinel), getFtsoRegistry MUST + /// revert with the specific ContractNotRegistered error rather than + /// returning a zero-typed handle. Removing the guard in the library makes + /// this test fail (the getter would return address(0) instead of + /// reverting). + function testGetFtsoRegistryZeroAddressReverts() external { + vm.mockCall( + address(FLARE_CONTRACT_REGISTRY), + abi.encodeWithSelector(FLARE_CONTRACT_REGISTRY.getContractAddressByName.selector, FTSO_REGISTRY_NAME), + abi.encode(address(0)) + ); + vm.expectRevert(abi.encodeWithSelector(ContractNotRegistered.selector, FTSO_REGISTRY_NAME)); + external_.getFtsoRegistry(); + } + + /// As above, for the FtsoV2 LTS getter. + function testGetFtsoV2LTSZeroAddressReverts() external { + vm.mockCall( + address(FLARE_CONTRACT_REGISTRY), + abi.encodeWithSelector(FLARE_CONTRACT_REGISTRY.getContractAddressByName.selector, FTSO_V2_LTS_NAME), + abi.encode(address(0)) + ); + vm.expectRevert(abi.encodeWithSelector(ContractNotRegistered.selector, FTSO_V2_LTS_NAME)); + external_.getFtsoV2LTS(); + } + + /// As above, for the FeeCalculator getter. + function testGetFeeCalculatorZeroAddressReverts() external { + vm.mockCall( + address(FLARE_CONTRACT_REGISTRY), + abi.encodeWithSelector(FLARE_CONTRACT_REGISTRY.getContractAddressByName.selector, FEE_CALCULATOR_NAME), + abi.encode(address(0)) + ); + vm.expectRevert(abi.encodeWithSelector(ContractNotRegistered.selector, FEE_CALCULATOR_NAME)); + external_.getFeeCalculator(); + } + function testGetFtsoV2LTS() external view { FtsoV2Interface ftsoV2 = LibFlareContractRegistry.getFtsoV2LTS(); assertEq(address(ftsoV2), address(0xB18d3A5e5A85C65cE47f977D7F486B79F99D3d32)); diff --git a/test/src/lib/registry/LibFlareContractRegistryExternal.sol b/test/src/lib/registry/LibFlareContractRegistryExternal.sol new file mode 100644 index 0000000..3b07d34 --- /dev/null +++ b/test/src/lib/registry/LibFlareContractRegistryExternal.sol @@ -0,0 +1,23 @@ +// SPDX-License-Identifier: LicenseRef-DCL-1.0 +// SPDX-FileCopyrightText: Copyright (c) 2020 Rain Open Source Software Ltd +pragma solidity =0.8.25; + +import {LibFlareContractRegistry, IFtsoRegistry} from "src/lib/registry/LibFlareContractRegistry.sol"; + +/// External wrapper around the internal library getters so that calling them via +/// `this.()` produces a dedicated external call frame. `vm.expectRevert` +/// asserts the *next call frame* reverts; an inlined internal library call has +/// no frame of its own, so the wrapper is required to observe the revert. +contract LibFlareContractRegistryExternal { + function getFtsoRegistry() external view returns (address) { + return address(LibFlareContractRegistry.getFtsoRegistry()); + } + + function getFtsoV2LTS() external view returns (address) { + return address(LibFlareContractRegistry.getFtsoV2LTS()); + } + + function getFeeCalculator() external view returns (address) { + return address(LibFlareContractRegistry.getFeeCalculator()); + } +}