Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 33 additions & 0 deletions test/concrete/DirtyUpperByteBeacon.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
// SPDX-License-Identifier: LicenseRef-DCL-1.0
// SPDX-FileCopyrightText: Copyright (c) 2020 Rain Open Source Software Ltd
pragma solidity =0.8.25;

/// @dev Beacon test fixture whose `implementation()` and `owner()`
/// selectors exist and return successfully, but with a 32-byte word
/// holding a real address below a dirty byte: only bits 160-167 are
/// set above the 160 address bits. A gate widened to any width of
/// 168 bits or more accepts this word and truncates it to the
/// embedded address, so both public predicates would then compare an
/// attacker-chosen address where they must report failure. Pins the
/// second cell of the reject-side partition, alongside
/// `OneAboveMaxAddressBeacon`'s exact-boundary `2 ** 160`.
///
/// Cannot `is IBeacon, IOwnable` — return types deliberately differ
/// from the interfaces. That mismatch is the test condition.
contract DirtyUpperByteBeacon {
/// @dev The dirty word both selectors answer with: `addr` with all
/// eight of bits 160-167 set above it.
uint256 private immutable _dirtyWord;

constructor(address addr) {
_dirtyWord = (uint256(0xFF) << 160) | uint256(uint160(addr));
}

function implementation() external view returns (uint256) {
return _dirtyWord;
}

function owner() external view returns (uint256) {
return _dirtyWord;
}
}
24 changes: 24 additions & 0 deletions test/concrete/OneAboveMaxAddressBeacon.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
// SPDX-License-Identifier: LicenseRef-DCL-1.0
// SPDX-FileCopyrightText: Copyright (c) 2020 Rain Open Source Software Ltd
pragma solidity =0.8.25;

/// @dev Beacon test fixture whose `implementation()` and `owner()`
/// selectors exist and return successfully, but with exactly
/// `2 ** 160` — the first value the dirty-bits gate
/// (`raw > type(uint160).max`) must reject. Only bit 160 is set, so
/// any widening of the gate past 160 bits accepts this word and
/// truncates it to `address(0)`. Pins the reject side of the boundary
/// whose accept side `testMatchesAtMaxAddressBoundary` pins with
/// `2 ** 160 - 1`.
///
/// Cannot `is IBeacon, IOwnable` — return types deliberately differ
/// from the interfaces. That mismatch is the test condition.
contract OneAboveMaxAddressBeacon {
function implementation() external pure returns (uint256) {
return uint256(type(uint160).max) + 1;
}

function owner() external pure returns (uint256) {
return uint256(type(uint160).max) + 1;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ import {EmptyContract} from "test/concrete/EmptyContract.sol";
import {RevertingBeacon} from "test/concrete/RevertingBeacon.sol";
import {BogusBeacon} from "test/concrete/BogusBeacon.sol";
import {WrongLengthBeacon} from "test/concrete/WrongLengthBeacon.sol";
import {OneAboveMaxAddressBeacon} from "test/concrete/OneAboveMaxAddressBeacon.sol";
import {DirtyUpperByteBeacon} from "test/concrete/DirtyUpperByteBeacon.sol";
import {PermissiveFallbackContract} from "test/concrete/PermissiveFallbackContract.sol";
import {
RevertingWithAddressBeacon,
Expand Down Expand Up @@ -146,6 +148,32 @@ contract LibExtrospectERC1967BeaconProxyIsBeaconImplementationBytecodeTest is Te
);
}

/// `2 ** 160` is the first value the dirty-bits gate
/// (`raw > type(uint160).max`) must reject — the reject side of the
/// boundary whose accept side `testMatchesAtMaxAddressBoundary`
/// pins. The expected hash is `keccak256("")`: exactly what a gate
/// widened past 160 bits would match after truncating `2 ** 160`
/// to the codeless `address(0)`.
function testReturnsFalseAtOneAboveMaxAddressBoundary() external {
OneAboveMaxAddressBeacon beacon = new OneAboveMaxAddressBeacon();
assertFalse(LibExtrospectERC1967BeaconProxy.isBeaconImplementationBytecode(address(beacon), keccak256("")));
}

/// A 32-byte return holding a real deployed address with only bits
/// 160-167 set above it must be rejected as dirty, not truncated.
/// The expected hash is that of the embedded address's runtime
/// bytecode: exactly what a gate widened to 168 bits or more would
/// match after truncating the word to that address.
function testReturnsFalseOnDirtyUpperByteAboveRealAddress() external {
EmptyContract impl = new EmptyContract();
DirtyUpperByteBeacon beacon = new DirtyUpperByteBeacon(address(impl));
assertFalse(
LibExtrospectERC1967BeaconProxy.isBeaconImplementationBytecode(
address(beacon), keccak256(address(impl).code)
)
);
}

/// A beacon whose `implementation()` returns more than 32 bytes
/// must also fail the predicate, even if the first 32 bytes
/// happen to decode as a valid address. Expected is `keccak256("")`
Expand Down
23 changes: 23 additions & 0 deletions test/src/lib/LibExtrospectERC1967BeaconProxy.isBeaconOwner.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ import {EmptyContract} from "test/concrete/EmptyContract.sol";
import {RevertingBeacon} from "test/concrete/RevertingBeacon.sol";
import {BogusBeacon} from "test/concrete/BogusBeacon.sol";
import {WrongLengthBeacon} from "test/concrete/WrongLengthBeacon.sol";
import {OneAboveMaxAddressBeacon} from "test/concrete/OneAboveMaxAddressBeacon.sol";
import {DirtyUpperByteBeacon} from "test/concrete/DirtyUpperByteBeacon.sol";
import {PermissiveFallbackContract} from "test/concrete/PermissiveFallbackContract.sol";
import {
RevertingWithAddressBeacon,
Expand Down Expand Up @@ -88,6 +90,27 @@ contract LibExtrospectERC1967BeaconProxyIsBeaconOwnerTest is Test {
assertTrue(LibExtrospectERC1967BeaconProxy.isBeaconOwner(address(beacon), maxAddr));
}

/// `2 ** 160` is the first value the dirty-bits gate
/// (`raw > type(uint160).max`) must reject — the reject side of the
/// boundary whose accept side `testMatchesAtMaxAddressBoundary`
/// pins. The expected owner is `address(0)`: exactly what a gate
/// widened past 160 bits would truncate `2 ** 160` to and report
/// as a match.
function testReturnsFalseAtOneAboveMaxAddressBoundary() external {
OneAboveMaxAddressBeacon beacon = new OneAboveMaxAddressBeacon();
assertFalse(LibExtrospectERC1967BeaconProxy.isBeaconOwner(address(beacon), address(0)));
}

/// A 32-byte return holding a real address with only bits 160-167
/// set above it must be rejected as dirty, not truncated. The
/// expected owner is the embedded address itself: exactly what a
/// gate widened to 168 bits or more would truncate the word to and
/// report as a match.
function testReturnsFalseOnDirtyUpperByteAboveRealAddress(address own) external {
DirtyUpperByteBeacon beacon = new DirtyUpperByteBeacon(own);
assertFalse(LibExtrospectERC1967BeaconProxy.isBeaconOwner(address(beacon), own));
}

/// A beacon whose `owner()` returns more than 32 bytes must also
/// fail the predicate, even if the first 32 bytes happen to
/// decode as a valid address. The expected owner here is
Expand Down
Loading