From 0da84b13d0e6255f45aa77357a8771018b55368a Mon Sep 17 00:00:00 2001 From: Vectorized Date: Mon, 24 Mar 2025 00:16:29 +0000 Subject: [PATCH 1/4] LibTransient Registry --- src/utils/LibTransient.sol | 99 +++++++++++++++++++++++++++ src/utils/g/LibTransient.sol | 99 +++++++++++++++++++++++++++ test/LibTransient.t.sol | 126 +++++++++++++++++++++++++++++++++++ 3 files changed, 324 insertions(+) diff --git a/src/utils/LibTransient.sol b/src/utils/LibTransient.sol index f114c9404..621da5a7e 100644 --- a/src/utils/LibTransient.sol +++ b/src/utils/LibTransient.sol @@ -51,6 +51,10 @@ library LibTransient { /// `bytes4(keccak256("_LIB_TRANSIENT_COMPAT_SLOT_SEED"))`. uint256 private constant _LIB_TRANSIENT_COMPAT_SLOT_SEED = 0x5a0b45f2; + /// @dev The canonical address of the transient registry. + /// See: https://gist.github.com/Vectorized/4ab665d7a234ef5aaaff2e5091ec261f + address internal constant REGISTRY = 0x000000000000297f64C7F8d9595e43257908F170; + /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ /* UINT256 OPERATIONS */ /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ @@ -690,6 +694,101 @@ library LibTransient { _compat(ptr)._spacer = 0; } + /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ + /* TRANSIENT REGISTRY OPERATIONS */ + /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ + + /// @dev Sets the value for the key. + /// If the key does not exist, its admin will be set to the caller. + /// If the key already exist, its value will be overwritten, + /// and the caller must be the current admin for the key. + /// Reverts with empty data if the registry has not been deployed. + function registrySet(bytes32 key, bytes memory value) internal { + /// @solidity memory-safe-assembly + assembly { + let m := mload(0x40) + mstore(m, 0xaac438c0) // `set(bytes32,bytes)`. + mstore(add(m, 0x20), key) + mstore(add(m, 0x40), 0x40) + let n := mload(value) + mstore(add(m, 0x60), n) + for { let i := 0 } lt(i, n) { i := add(i, 0x20) } { + mstore(add(add(m, 0x80), i), mload(add(add(value, 0x20), i))) + } + if iszero( + mul( + returndatasize(), + call(gas(), REGISTRY, 0, add(m, 0x1c), add(n, 0x64), 0x00, 0x20) + ) + ) { revert(0x00, returndatasize()) } + } + } + + /// @dev Returns the value for the key. + /// Reverts if the key does not exist. + /// Reverts with empty data if the registry has not been deployed. + function registryGet(bytes32 key) internal view returns (bytes memory result) { + /// @solidity memory-safe-assembly + assembly { + result := mload(0x40) + mstore(0x00, 0x8eaa6ac0) // `get(bytes32)`. + mstore(0x20, key) + if iszero(mul(returndatasize(), staticcall(gas(), REGISTRY, 0x1c, 0x24, 0x00, 0x20))) { + revert(0x00, returndatasize()) + } + // We can safely assume that the bytes will be containing the 0x20 offset. + returndatacopy(result, 0x20, sub(returndatasize(), 0x20)) + mstore(0x40, add(result, returndatasize())) // Allocate memory. + } + } + + /// @dev Clears the admin and the value for the key. + /// The caller must be the current admin of the key. + /// Reverts with empty data if the registry has not been deployed. + function registryClear(bytes32 key) internal { + /// @solidity memory-safe-assembly + assembly { + mstore(0x00, 0x97040a45) // `clear(bytes32)`. + mstore(0x20, key) + if iszero(mul(returndatasize(), call(gas(), REGISTRY, 0, 0x1c, 0x24, 0x00, 0x20))) { + revert(0x00, returndatasize()) + } + } + } + + /// @dev Returns the admin of the key. + /// Returns `address(0)` if the key does not exist. + /// Reverts with empty data if the registry has not been deployed. + function registryAdminOf(bytes32 key) internal view returns (address result) { + /// @solidity memory-safe-assembly + assembly { + mstore(0x00, 0xc5344411) // `adminOf(bytes32)`. + mstore(0x20, key) + if iszero(mul(returndatasize(), staticcall(gas(), REGISTRY, 0x1c, 0x24, 0x00, 0x20))) { + revert(0x00, returndatasize()) + } + result := mload(0x00) + } + } + + /// @dev Changes the admin of the key. + /// The caller must be the current admin of the key. + /// The new admin must not be `address(0)`. + /// Reverts with empty data if the registry has not been deployed. + function registryChangeAdmin(bytes32 key, address newAdmin) internal { + /// @solidity memory-safe-assembly + assembly { + let m := mload(0x40) // Cache the free memory pointer. + mstore(0x00, 0x053b1ca3) // `changeAdmin(bytes32,address)`. + mstore(0x20, key) + mstore(0x40, shr(96, shl(96, newAdmin))) + if iszero(mul(returndatasize(), call(gas(), REGISTRY, 0, 0x1c, 0x44, 0x00, 0x20))) { + revert(0x00, returndatasize()) + } + mstore(0x40, m) // Restore the free memory pointer. + } + } + /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ /* PRIVATE HELPERS */ /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ diff --git a/src/utils/g/LibTransient.sol b/src/utils/g/LibTransient.sol index 327e47cea..6cc661b3e 100644 --- a/src/utils/g/LibTransient.sol +++ b/src/utils/g/LibTransient.sol @@ -60,6 +60,10 @@ library LibTransient { /// `bytes4(keccak256("_LIB_TRANSIENT_COMPAT_SLOT_SEED"))`. uint256 private constant _LIB_TRANSIENT_COMPAT_SLOT_SEED = 0x5a0b45f2; + /// @dev The canonical address of the transient registry. + /// See: https://gist.github.com/Vectorized/4ab665d7a234ef5aaaff2e5091ec261f + address internal constant REGISTRY = 0x000000000000297f64C7F8d9595e43257908F170; + /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ /* UINT256 OPERATIONS */ /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ @@ -699,6 +703,101 @@ library LibTransient { _compat(ptr)._spacer = 0; } + /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ + /* TRANSIENT REGISTRY OPERATIONS */ + /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ + + /// @dev Sets the value for the key. + /// If the key does not exist, its admin will be set to the caller. + /// If the key already exist, its value will be overwritten, + /// and the caller must be the current admin for the key. + /// Reverts with empty data if the registry has not been deployed. + function registrySet(bytes32 key, bytes memory value) internal { + /// @solidity memory-safe-assembly + assembly { + let m := mload(0x40) + mstore(m, 0xaac438c0) // `set(bytes32,bytes)`. + mstore(add(m, 0x20), key) + mstore(add(m, 0x40), 0x40) + let n := mload(value) + mstore(add(m, 0x60), n) + for { let i := 0 } lt(i, n) { i := add(i, 0x20) } { + mstore(add(add(m, 0x80), i), mload(add(add(value, 0x20), i))) + } + if iszero( + mul( + returndatasize(), + call(gas(), REGISTRY, 0, add(m, 0x1c), add(n, 0x64), 0x00, 0x20) + ) + ) { revert(0x00, returndatasize()) } + } + } + + /// @dev Returns the value for the key. + /// Reverts if the key does not exist. + /// Reverts with empty data if the registry has not been deployed. + function registryGet(bytes32 key) internal view returns (bytes memory result) { + /// @solidity memory-safe-assembly + assembly { + result := mload(0x40) + mstore(0x00, 0x8eaa6ac0) // `get(bytes32)`. + mstore(0x20, key) + if iszero(mul(returndatasize(), staticcall(gas(), REGISTRY, 0x1c, 0x24, 0x00, 0x20))) { + revert(0x00, returndatasize()) + } + // We can safely assume that the bytes will be containing the 0x20 offset. + returndatacopy(result, 0x20, sub(returndatasize(), 0x20)) + mstore(0x40, add(result, returndatasize())) // Allocate memory. + } + } + + /// @dev Clears the admin and the value for the key. + /// The caller must be the current admin of the key. + /// Reverts with empty data if the registry has not been deployed. + function registryClear(bytes32 key) internal { + /// @solidity memory-safe-assembly + assembly { + mstore(0x00, 0x97040a45) // `clear(bytes32)`. + mstore(0x20, key) + if iszero(mul(returndatasize(), call(gas(), REGISTRY, 0, 0x1c, 0x24, 0x00, 0x20))) { + revert(0x00, returndatasize()) + } + } + } + + /// @dev Returns the admin of the key. + /// Returns `address(0)` if the key does not exist. + /// Reverts with empty data if the registry has not been deployed. + function registryAdminOf(bytes32 key) internal view returns (address result) { + /// @solidity memory-safe-assembly + assembly { + mstore(0x00, 0xc5344411) // `adminOf(bytes32)`. + mstore(0x20, key) + if iszero(mul(returndatasize(), staticcall(gas(), REGISTRY, 0x1c, 0x24, 0x00, 0x20))) { + revert(0x00, returndatasize()) + } + result := mload(0x00) + } + } + + /// @dev Changes the admin of the key. + /// The caller must be the current admin of the key. + /// The new admin must not be `address(0)`. + /// Reverts with empty data if the registry has not been deployed. + function registryChangeAdmin(bytes32 key, address newAdmin) internal { + /// @solidity memory-safe-assembly + assembly { + let m := mload(0x40) // Cache the free memory pointer. + mstore(0x00, 0x053b1ca3) // `changeAdmin(bytes32,address)`. + mstore(0x20, key) + mstore(0x40, shr(96, shl(96, newAdmin))) + if iszero(mul(returndatasize(), call(gas(), REGISTRY, 0, 0x1c, 0x44, 0x00, 0x20))) { + revert(0x00, returndatasize()) + } + mstore(0x40, m) // Restore the free memory pointer. + } + } + /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ /* PRIVATE HELPERS */ /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ diff --git a/test/LibTransient.t.sol b/test/LibTransient.t.sol index c061d96e6..0ab86090d 100644 --- a/test/LibTransient.t.sol +++ b/test/LibTransient.t.sol @@ -3,6 +3,23 @@ pragma solidity ^0.8.24; import "./utils/SoladyTest.sol"; import {LibTransient} from "../src/utils/LibTransient.sol"; +import {LibClone} from "../src/utils/LibClone.sol"; + +contract A { + address public immutable b; + + constructor() { + b = abi.decode(LibTransient.registryGet("b"), (address)); + } +} + +contract B { + address public immutable a; + + constructor() { + a = abi.decode(LibTransient.registryGet("a"), (address)); + } +} contract LibTransientTest is SoladyTest { using LibTransient for *; @@ -248,4 +265,113 @@ contract LibTransientTest is SoladyTest { } LibTransient.tBytes(uint256(0)).setCalldataCompat(data); } + + function testRegistry(bytes32 key, bytes memory value) public { + _etchTransientRegistry(); + if (_randomChance(2)) { + vm.expectRevert(bytes4(keccak256("TransientRegistryUnauthorized()"))); + LibTransient.registryClear(key); + } + + LibTransient.registrySet(key, value); + assertEq(LibTransient.registryGet(key), value); + assertEq(LibTransient.registryAdminOf(key), address(this)); + + if (_randomChance(2)) { + address newAdmin = _randomUniqueHashedAddress(); + vm.prank(newAdmin); + vm.expectRevert(bytes4(keccak256("TransientRegistryUnauthorized()"))); + LibTransient.registrySet(key, value); + } + + if (_randomChance(2)) { + vm.expectRevert(bytes4(keccak256("TransientRegistryNewAdminIsZeroAddress()"))); + LibTransient.registryChangeAdmin(key, address(0)); + } + + if (_randomChance(2)) { + address newAdmin = _randomUniqueHashedAddress(); + uint256 newAdminRaw = uint256(uint160(newAdmin)); + if (_randomChance(2)) newAdminRaw |= _random() << 160; + + bool success; + if (newAdminRaw >> 160 == 0) { + if (_randomChance(2)) { + (success,) = LibTransient.REGISTRY.call( + abi.encodeWithSignature("changeAdmin(bytes32,address)", key, newAdminRaw) + ); + assertTrue(success); + } else { + LibTransient.registryChangeAdmin(key, newAdmin); + } + } else { + (success,) = LibTransient.REGISTRY.call( + abi.encodeWithSignature("changeAdmin(bytes32,address)", key, newAdminRaw) + ); + assertFalse(success); + newAdminRaw = (newAdminRaw << 96) >> 96; + (success,) = LibTransient.REGISTRY.call( + abi.encodeWithSignature("changeAdmin(bytes32,address)", key, newAdminRaw) + ); + assertTrue(success); + } + + assertEq(LibTransient.registryAdminOf(key), newAdmin); + if (_randomChance(2)) return; + + bytes memory anotherValue = _randomBytes(); + vm.prank(newAdmin); + LibTransient.registrySet(key, anotherValue); + assertEq(LibTransient.registryGet(key), anotherValue); + assertEq(LibTransient.registryAdminOf(key), newAdmin); + + vm.prank(newAdmin); + LibTransient.registryChangeAdmin(key, address(this)); + } + + if (_randomChance(2)) { + if (_randomChance(2)) LibTransient.registryClear(key); + bytes memory anotherValue = _randomBytes(); + LibTransient.registrySet(key, anotherValue); + assertEq(LibTransient.registryGet(key), anotherValue); + assertEq(LibTransient.registryAdminOf(key), address(this)); + } + + if (_randomChance(2)) { + LibTransient.registryClear(key); + vm.expectRevert(bytes4(keccak256("TransientRegistryKeyDoesNotExist()"))); + LibTransient.registryGet(key); + assertEq(LibTransient.registryAdminOf(key), address(0)); + + if (_randomChance(2)) return; + + address newAdmin = _randomUniqueHashedAddress(); + vm.prank(newAdmin); + LibTransient.registrySet(key, value); + assertEq(LibTransient.registryGet(key), value); + assertEq(LibTransient.registryAdminOf(key), newAdmin); + } + } + + function testRegistryAB() public { + _etchTransientRegistry(); + bytes32 aInitCodeHash = keccak256(type(A).creationCode); + bytes32 bInitCodeHash = keccak256(type(B).creationCode); + address aAddress = LibClone.predictDeterministicAddress(aInitCodeHash, 0, _NICKS_FACTORY); + address bAddress = LibClone.predictDeterministicAddress(bInitCodeHash, 0, _NICKS_FACTORY); + LibTransient.registrySet("a", abi.encode(aAddress)); + LibTransient.registrySet("b", abi.encode(bAddress)); + A a = new A(); + B b = new B(); + assertEq(a.b(), bAddress); + assertEq(b.a(), aAddress); + } + + function _etchTransientRegistry() internal { + bytes32 salt = 0x00000000000000000000000000000000000000001ef0fa4e834693009a3bcdbc; + bytes memory initializationCode = + hex"6080604052348015600e575f5ffd5b506104d48061001c5f395ff3fe608060405234801561000f575f5ffd5b5060043610610064575f3560e01c806397040a451161004d57806397040a45146100b0578063aac438c0146100c3578063c5344411146100d6575f5ffd5b8063053b1ca3146100685780638eaa6ac014610090575b5f5ffd5b61007b610076366004610395565b61010e565b60405190151581526020015b60405180910390f35b6100a361009e3660046103db565b61016e565b60405161008791906103f2565b61007b6100be3660046103db565b610227565b61007b6100d1366004610427565b61029f565b6100e96100e43660046103db565b610361565b60405173ffffffffffffffffffffffffffffffffffffffff9091168152602001610087565b5f8161012157634396ac1b5f526004601cfd5b825f527f2c96949beeb8aca2ef85b169c5bca920576b836c1cb3edaa443380aff09df99b60205260405f20805c33146101615763860170335f526004601cfd5b82815d5060015f5260205ff35b6060815f527f2c96949beeb8aca2ef85b169c5bca920576b836c1cb3edaa443380aff09df99b60205260405f205c6101ad57639bdc798f5f526004601cfd5b7fc8f6675aac5818d398110f4d0e7276685c19f1a74e66eed262c8a6aa9aabaedf60205260405f20604051602081015f8152825c601c8201528051806020830101601d821061021757845f528260205f2003603c84015b8082015c81526020018281106102045750505b5f81526020845283810360200184f35b5f815f527f2c96949beeb8aca2ef85b169c5bca920576b836c1cb3edaa443380aff09df99b60205260405f2033815c146102685763860170335f526004601cfd5b5f815d507fc8f6675aac5818d398110f4d0e7276685c19f1a74e66eed262c8a6aa9aabaedf6020525f60405f205d60015f5260205ff35b5f835f527f2c96949beeb8aca2ef85b169c5bca920576b836c1cb3edaa443380aff09df99b60205260405f20805c80156102e7573381146102e75763860170335f526004601cfd5b5033815d507fc8f6675aac5818d398110f4d0e7276685c19f1a74e66eed262c8a6aa9aabaedf60205260405f20833560201c8360e01b17815d601d831061035757805f528284016020858560201c5f036020175f200301601c86015b80358282015d602001828110610343575050505b5060015f5260205ff35b5f815f527f2c96949beeb8aca2ef85b169c5bca920576b836c1cb3edaa443380aff09df99b60205260405f205c5f5260205ff35b5f5f604083850312156103a6575f5ffd5b82359150602083013573ffffffffffffffffffffffffffffffffffffffff811681146103d0575f5ffd5b809150509250929050565b5f602082840312156103eb575f5ffd5b5035919050565b602081525f82518060208401528060208501604085015e5f604082850101526040601f19601f83011684010191505092915050565b5f5f5f60408486031215610439575f5ffd5b83359250602084013567ffffffffffffffff811115610456575f5ffd5b8401601f81018613610466575f5ffd5b803567ffffffffffffffff81111561047c575f5ffd5b86602082840101111561048d575f5ffd5b93966020919091019550929350505056fea2646970667358221220af8785b9665e5c7f00368ff4d9720b2d4f4b6d2d9eb7be97936fba46cc7e6dcd64736f6c634300081c0033"; + address deployment = _nicksCreate2(0, salt, initializationCode); + assertEq(deployment, LibTransient.REGISTRY); + } } From bf74ceb3bb70a3f3b9b19e09cc6e043773b38156 Mon Sep 17 00:00:00 2001 From: Vectorized Date: Mon, 24 Mar 2025 00:46:00 +0000 Subject: [PATCH 2/4] Add test --- test/LibTransient.t.sol | 107 +++++++++++++++++++++++++++++----------- 1 file changed, 79 insertions(+), 28 deletions(-) diff --git a/test/LibTransient.t.sol b/test/LibTransient.t.sol index 0ab86090d..fb5a06b46 100644 --- a/test/LibTransient.t.sol +++ b/test/LibTransient.t.sol @@ -270,23 +270,22 @@ contract LibTransientTest is SoladyTest { _etchTransientRegistry(); if (_randomChance(2)) { vm.expectRevert(bytes4(keccak256("TransientRegistryUnauthorized()"))); - LibTransient.registryClear(key); + this.registryClear(key); } - LibTransient.registrySet(key, value); - assertEq(LibTransient.registryGet(key), value); - assertEq(LibTransient.registryAdminOf(key), address(this)); + this.registrySet(key, value); + assertEq(this.registryGet(key), value); + assertEq(this.registryAdminOf(key), address(this)); if (_randomChance(2)) { address newAdmin = _randomUniqueHashedAddress(); - vm.prank(newAdmin); vm.expectRevert(bytes4(keccak256("TransientRegistryUnauthorized()"))); - LibTransient.registrySet(key, value); + this.registrySet(newAdmin, key, value); } if (_randomChance(2)) { vm.expectRevert(bytes4(keccak256("TransientRegistryNewAdminIsZeroAddress()"))); - LibTransient.registryChangeAdmin(key, address(0)); + this.registryChangeAdmin(key, address(0)); } if (_randomChance(2)) { @@ -302,7 +301,7 @@ contract LibTransientTest is SoladyTest { ); assertTrue(success); } else { - LibTransient.registryChangeAdmin(key, newAdmin); + this.registryChangeAdmin(key, newAdmin); } } else { (success,) = LibTransient.REGISTRY.call( @@ -316,40 +315,37 @@ contract LibTransientTest is SoladyTest { assertTrue(success); } - assertEq(LibTransient.registryAdminOf(key), newAdmin); + assertEq(this.registryAdminOf(key), newAdmin); if (_randomChance(2)) return; bytes memory anotherValue = _randomBytes(); - vm.prank(newAdmin); - LibTransient.registrySet(key, anotherValue); - assertEq(LibTransient.registryGet(key), anotherValue); - assertEq(LibTransient.registryAdminOf(key), newAdmin); + this.registrySet(newAdmin, key, anotherValue); + assertEq(this.registryGet(key), anotherValue); + assertEq(this.registryAdminOf(key), newAdmin); - vm.prank(newAdmin); - LibTransient.registryChangeAdmin(key, address(this)); + this.registryChangeAdmin(newAdmin, key, address(this)); } if (_randomChance(2)) { - if (_randomChance(2)) LibTransient.registryClear(key); + if (_randomChance(2)) this.registryClear(key); bytes memory anotherValue = _randomBytes(); - LibTransient.registrySet(key, anotherValue); - assertEq(LibTransient.registryGet(key), anotherValue); - assertEq(LibTransient.registryAdminOf(key), address(this)); + this.registrySet(key, anotherValue); + assertEq(this.registryGet(key), anotherValue); + assertEq(this.registryAdminOf(key), address(this)); } if (_randomChance(2)) { - LibTransient.registryClear(key); + this.registryClear(key); vm.expectRevert(bytes4(keccak256("TransientRegistryKeyDoesNotExist()"))); - LibTransient.registryGet(key); - assertEq(LibTransient.registryAdminOf(key), address(0)); + this.registryGet(key); + assertEq(this.registryAdminOf(key), address(0)); if (_randomChance(2)) return; address newAdmin = _randomUniqueHashedAddress(); - vm.prank(newAdmin); - LibTransient.registrySet(key, value); - assertEq(LibTransient.registryGet(key), value); - assertEq(LibTransient.registryAdminOf(key), newAdmin); + this.registrySet(newAdmin, key, value); + assertEq(this.registryGet(key), value); + assertEq(this.registryAdminOf(key), newAdmin); } } @@ -359,14 +355,69 @@ contract LibTransientTest is SoladyTest { bytes32 bInitCodeHash = keccak256(type(B).creationCode); address aAddress = LibClone.predictDeterministicAddress(aInitCodeHash, 0, _NICKS_FACTORY); address bAddress = LibClone.predictDeterministicAddress(bInitCodeHash, 0, _NICKS_FACTORY); - LibTransient.registrySet("a", abi.encode(aAddress)); - LibTransient.registrySet("b", abi.encode(bAddress)); + this.registrySet("a", abi.encode(aAddress)); + this.registrySet("b", abi.encode(bAddress)); A a = new A(); B b = new B(); assertEq(a.b(), bAddress); assertEq(b.a(), aAddress); } + function testRegistryNotDeployed() public { + bytes memory value = _randomBytes(); + bytes memory empty; + + vm.expectRevert(empty); + this.registrySet(bytes32(_randomUniform()), value); + + vm.expectRevert(empty); + this.registryGet(bytes32(_randomUniform())); + + vm.expectRevert(empty); + this.registryClear(bytes32(_randomUniform())); + + vm.expectRevert(empty); + this.registryChangeAdmin(bytes32(_randomUniform()), _randomUniqueHashedAddress()); + + vm.expectRevert(empty); + this.registryAdminOf(bytes32(_randomUniform())); + } + + function registrySet(bytes32 hash, bytes memory value) public { + LibTransient.registrySet(hash, value); + } + + function registrySet(address pranker, bytes32 hash, bytes memory value) public { + vm.prank(pranker); + registrySet(hash, value); + } + + function registryGet(bytes32 hash) public view returns (bytes memory) { + return LibTransient.registryGet(hash); + } + + function registryClear(address pranker, bytes32 hash) public { + vm.prank(pranker); + registryClear(hash); + } + + function registryClear(bytes32 hash) public { + LibTransient.registryClear(hash); + } + + function registryChangeAdmin(address pranker, bytes32 hash, address newAdmin) public { + vm.prank(pranker); + registryChangeAdmin(hash, newAdmin); + } + + function registryChangeAdmin(bytes32 hash, address newAdmin) public { + LibTransient.registryChangeAdmin(hash, newAdmin); + } + + function registryAdminOf(bytes32 hash) public view returns (address) { + return LibTransient.registryAdminOf(hash); + } + function _etchTransientRegistry() internal { bytes32 salt = 0x00000000000000000000000000000000000000001ef0fa4e834693009a3bcdbc; bytes memory initializationCode = From c1efc920ddf88bf24f3d8838c2308e81b831e012 Mon Sep 17 00:00:00 2001 From: Vectorized Date: Mon, 24 Mar 2025 01:00:06 +0000 Subject: [PATCH 3/4] Regen docs --- docs/utils/libtransient.md | 75 +++++++++++++++++++++++++++++++++++++- 1 file changed, 74 insertions(+), 1 deletion(-) diff --git a/docs/utils/libtransient.md b/docs/utils/libtransient.md index c46cd8785..0e8344f89 100644 --- a/docs/utils/libtransient.md +++ b/docs/utils/libtransient.md @@ -75,6 +75,18 @@ struct TBytes { Pointer struct to a `bytes` in transient storage. +## Constants + +### REGISTRY + +```solidity +address internal constant REGISTRY = + 0x000000000000297f64C7F8d9595e43257908F170 +``` + +The canonical address of the transient registry. +See: https://gist.github.com/Vectorized/4ab665d7a234ef5aaaff2e5091ec261f + ## Uint256 Operations ### tUint256(bytes32) @@ -742,4 +754,65 @@ Clears the value at transient `ptr`. function clearCompat(TBytes storage ptr) internal ``` -Clears the value at transient `ptr`. \ No newline at end of file +Clears the value at transient `ptr`. + +## Transient Registry Operations + +### registrySet(bytes32,bytes) + +```solidity +function registrySet(bytes32 key, bytes memory value) internal +``` + +Sets the value for the key. +If the key does not exist, its admin will be set to the caller. +If the key already exist, its value will be overwritten, +and the caller must be the current admin for the key. +Reverts with empty data if the registry has not been deployed. + +### registryGet(bytes32) + +```solidity +function registryGet(bytes32 key) + internal + view + returns (bytes memory result) +``` + +Returns the value for the key. +Reverts if the key does not exist. +Reverts with empty data if the registry has not been deployed. + +### registryClear(bytes32) + +```solidity +function registryClear(bytes32 key) internal +``` + +Clears the admin and the value for the key. +The caller must be the current admin of the key. +Reverts with empty data if the registry has not been deployed. + +### registryAdminOf(bytes32) + +```solidity +function registryAdminOf(bytes32 key) + internal + view + returns (address result) +``` + +Returns the admin of the key. +Returns `address(0)` if the key does not exist. +Reverts with empty data if the registry has not been deployed. + +### registryChangeAdmin(bytes32,address) + +```solidity +function registryChangeAdmin(bytes32 key, address newAdmin) internal +``` + +Changes the admin of the key. +The caller must be the current admin of the key. +The new admin must not be `address(0)`. +Reverts with empty data if the registry has not been deployed. \ No newline at end of file From c839c51c821f98acc1c721d6420e0713f99a0066 Mon Sep 17 00:00:00 2001 From: Vectorized Date: Mon, 24 Mar 2025 01:02:11 +0000 Subject: [PATCH 4/4] Strengthen test --- test/LibTransient.t.sol | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/test/LibTransient.t.sol b/test/LibTransient.t.sol index fb5a06b46..11af51399 100644 --- a/test/LibTransient.t.sol +++ b/test/LibTransient.t.sol @@ -385,6 +385,7 @@ contract LibTransientTest is SoladyTest { function registrySet(bytes32 hash, bytes memory value) public { LibTransient.registrySet(hash, value); + _checkMemory(); } function registrySet(address pranker, bytes32 hash, bytes memory value) public { @@ -392,8 +393,9 @@ contract LibTransientTest is SoladyTest { registrySet(hash, value); } - function registryGet(bytes32 hash) public view returns (bytes memory) { - return LibTransient.registryGet(hash); + function registryGet(bytes32 hash) public view returns (bytes memory result) { + result = LibTransient.registryGet(hash); + _checkMemory(result); } function registryClear(address pranker, bytes32 hash) public { @@ -403,6 +405,7 @@ contract LibTransientTest is SoladyTest { function registryClear(bytes32 hash) public { LibTransient.registryClear(hash); + _checkMemory(); } function registryChangeAdmin(address pranker, bytes32 hash, address newAdmin) public { @@ -412,6 +415,7 @@ contract LibTransientTest is SoladyTest { function registryChangeAdmin(bytes32 hash, address newAdmin) public { LibTransient.registryChangeAdmin(hash, newAdmin); + _checkMemory(); } function registryAdminOf(bytes32 hash) public view returns (address) {