From 24e3cb404cf0c8b5ac7ef86fb16b8519d132a682 Mon Sep 17 00:00:00 2001
From: Lukas <lukasstrassel@googlemail.com>
Date: Thu, 2 Jan 2025 20:15:37 +0100
Subject: [PATCH 1/8] feat: remove custom proxy admin

---
 .../transparent-proxy/ProxyAdmin.sol          |  45 -------
 .../TransparentProxyFactoryBase.sol           |  24 ++--
 .../TransparentUpgradeableProxy.sol           | 125 ------------------
 .../interfaces/IProxyAdminOzV4.sol            |  15 +++
 .../interfaces/ITransparentProxyFactory.sol   |   8 +-
 test/PermissionlessRescuable.t.sol            |   2 +-
 test/TransparentProxyFactory.t.sol            |  12 +-
 test/UpgradeableOwnableWithGuardian.t.sol     |   2 +-
 .../test/TransparentProxyFactoryZkSync.t.sol  |  15 +--
 9 files changed, 47 insertions(+), 201 deletions(-)
 delete mode 100644 src/contracts/transparent-proxy/ProxyAdmin.sol
 delete mode 100644 src/contracts/transparent-proxy/TransparentUpgradeableProxy.sol
 create mode 100644 src/contracts/transparent-proxy/interfaces/IProxyAdminOzV4.sol

diff --git a/src/contracts/transparent-proxy/ProxyAdmin.sol b/src/contracts/transparent-proxy/ProxyAdmin.sol
deleted file mode 100644
index 161ca6e..0000000
--- a/src/contracts/transparent-proxy/ProxyAdmin.sol
+++ /dev/null
@@ -1,45 +0,0 @@
-// SPDX-License-Identifier: MIT
-// OpenZeppelin Contracts (last updated v5.0.0) (proxy/transparent/ProxyAdmin.sol)
-
-pragma solidity ^0.8.20;
-
-import {ITransparentUpgradeableProxy} from './TransparentUpgradeableProxy.sol';
-import {Ownable} from 'openzeppelin-contracts/contracts/access/Ownable.sol';
-
-/**
- * @dev This is an auxiliary contract meant to be assigned as the admin of a {TransparentUpgradeableProxy}. For an
- * explanation of why you would want to use this see the documentation for {TransparentUpgradeableProxy}.
- */
-contract ProxyAdmin is Ownable {
-  /**
-   * @dev The version of the upgrade interface of the contract. If this getter is missing, both `upgrade(address)`
-   * and `upgradeAndCall(address,bytes)` are present, and `upgradeTo` must be used if no function should be called,
-   * while `upgradeAndCall` will invoke the `receive` function if the second argument is the empty byte string.
-   * If the getter returns `"5.0.0"`, only `upgradeAndCall(address,bytes)` is present, and the second argument must
-   * be the empty byte string if no function should be called, making it impossible to invoke the `receive` function
-   * during an upgrade.
-   */
-  string public constant UPGRADE_INTERFACE_VERSION = '5.0.0';
-
-  /**
-   * @dev Sets the initial owner who can perform upgrades.
-   */
-  constructor(address initialOwner) Ownable(initialOwner) {}
-
-  /**
-   * @dev Upgrades `proxy` to `implementation` and calls a function on the new implementation.
-   * See {TransparentUpgradeableProxy-_dispatchUpgradeToAndCall}.
-   *
-   * Requirements:
-   *
-   * - This contract must be the admin of `proxy`.
-   * - If `data` is empty, `msg.value` must be zero.
-   */
-  function upgradeAndCall(
-    ITransparentUpgradeableProxy proxy,
-    address implementation,
-    bytes memory data
-  ) public payable virtual onlyOwner {
-    proxy.upgradeToAndCall{value: msg.value}(implementation, data);
-  }
-}
diff --git a/src/contracts/transparent-proxy/TransparentProxyFactoryBase.sol b/src/contracts/transparent-proxy/TransparentProxyFactoryBase.sol
index 68ec22d..db3f5ae 100644
--- a/src/contracts/transparent-proxy/TransparentProxyFactoryBase.sol
+++ b/src/contracts/transparent-proxy/TransparentProxyFactoryBase.sol
@@ -1,9 +1,9 @@
 // SPDX-License-Identifier: MIT
 pragma solidity >=0.8.0;
 
+import {TransparentUpgradeableProxy} from 'openzeppelin-contracts/contracts/proxy/transparent/TransparentUpgradeableProxy.sol';
+import {ProxyAdmin} from 'openzeppelin-contracts/contracts/proxy/transparent/ProxyAdmin.sol';
 import {ITransparentProxyFactory} from './interfaces/ITransparentProxyFactory.sol';
-import {TransparentUpgradeableProxy} from './TransparentUpgradeableProxy.sol';
-import {ProxyAdmin} from './ProxyAdmin.sol';
 
 /**
  * @title TransparentProxyFactory
@@ -15,10 +15,14 @@ import {ProxyAdmin} from './ProxyAdmin.sol';
  **/
 abstract contract TransparentProxyFactoryBase is ITransparentProxyFactory {
   /// @inheritdoc ITransparentProxyFactory
-  function create(address logic, ProxyAdmin admin, bytes calldata data) external returns (address) {
-    address proxy = address(new TransparentUpgradeableProxy(logic, admin, data));
+  function create(
+    address logic,
+    address adminOwner,
+    bytes calldata data
+  ) external returns (address) {
+    address proxy = address(new TransparentUpgradeableProxy(logic, adminOwner, data));
 
-    emit ProxyCreated(proxy, logic, address(admin));
+    emit ProxyCreated(proxy, logic, address(adminOwner));
     return proxy;
   }
 
@@ -33,13 +37,13 @@ abstract contract TransparentProxyFactoryBase is ITransparentProxyFactory {
   /// @inheritdoc ITransparentProxyFactory
   function createDeterministic(
     address logic,
-    ProxyAdmin admin,
+    address adminOwner,
     bytes calldata data,
     bytes32 salt
   ) external returns (address) {
-    address proxy = address(new TransparentUpgradeableProxy{salt: salt}(logic, admin, data));
+    address proxy = address(new TransparentUpgradeableProxy{salt: salt}(logic, adminOwner, data));
 
-    emit ProxyDeterministicCreated(proxy, logic, address(admin), salt);
+    emit ProxyDeterministicCreated(proxy, logic, address(adminOwner), salt);
     return proxy;
   }
 
@@ -57,7 +61,7 @@ abstract contract TransparentProxyFactoryBase is ITransparentProxyFactory {
   /// @inheritdoc ITransparentProxyFactory
   function predictCreateDeterministic(
     address logic,
-    ProxyAdmin admin,
+    address admin,
     bytes calldata data,
     bytes32 salt
   ) public view returns (address) {
@@ -66,7 +70,7 @@ abstract contract TransparentProxyFactoryBase is ITransparentProxyFactory {
         address(this),
         salt,
         type(TransparentUpgradeableProxy).creationCode,
-        abi.encode(logic, address(admin), data)
+        abi.encode(logic, admin, data)
       );
   }
 
diff --git a/src/contracts/transparent-proxy/TransparentUpgradeableProxy.sol b/src/contracts/transparent-proxy/TransparentUpgradeableProxy.sol
deleted file mode 100644
index a7c97dc..0000000
--- a/src/contracts/transparent-proxy/TransparentUpgradeableProxy.sol
+++ /dev/null
@@ -1,125 +0,0 @@
-// SPDX-License-Identifier: MIT
-// OpenZeppelin Contracts (last updated v5.0.0) (proxy/transparent/TransparentUpgradeableProxy.sol)
-
-/**
- * Adaptation of OpenZeppelin's TransparentUpgradeableProxy contract by BGD Labs.
- * The original contract creates a new proxy admin per contract.
- * In the context of AAVE this is suboptimal, as it is more efficient to have a single proxy admin for all proxies.
- * This way, if an executor is ever migrated to a new address only the ownership of that single proxy admin has to ever change.
- */
-pragma solidity ^0.8.20;
-
-import {ERC1967Utils} from 'openzeppelin-contracts/contracts/proxy/ERC1967/ERC1967Utils.sol';
-import {ERC1967Proxy} from 'openzeppelin-contracts/contracts/proxy/ERC1967/ERC1967Proxy.sol';
-import {IERC1967} from 'openzeppelin-contracts/contracts/interfaces/IERC1967.sol';
-import {ProxyAdmin} from './ProxyAdmin.sol';
-
-/**
- * @dev Interface for {TransparentUpgradeableProxy}. In order to implement transparency, {TransparentUpgradeableProxy}
- * does not implement this interface directly, and its upgradeability mechanism is implemented by an internal dispatch
- * mechanism. The compiler is unaware that these functions are implemented by {TransparentUpgradeableProxy} and will not
- * include them in the ABI so this interface must be used to interact with it.
- */
-interface ITransparentUpgradeableProxy is IERC1967 {
-  function upgradeToAndCall(address, bytes calldata) external payable;
-}
-
-/**
- * @dev This contract implements a proxy that is upgradeable through an associated {ProxyAdmin} instance.
- *
- * To avoid https://medium.com/nomic-labs-blog/malicious-backdoors-in-ethereum-proxies-62629adf3357[proxy selector
- * clashing], which can potentially be used in an attack, this contract uses the
- * https://blog.openzeppelin.com/the-transparent-proxy-pattern/[transparent proxy pattern]. This pattern implies two
- * things that go hand in hand:
- *
- * 1. If any account other than the admin calls the proxy, the call will be forwarded to the implementation, even if
- * that call matches the {ITransparentUpgradeableProxy-upgradeToAndCall} function exposed by the proxy itself.
- * 2. If the admin calls the proxy, it can call the `upgradeToAndCall` function but any other call won't be forwarded to
- * the implementation. If the admin tries to call a function on the implementation it will fail with an error indicating
- * the proxy admin cannot fallback to the target implementation.
- *
- * These properties mean that the admin account can only be used for upgrading the proxy, so it's best if it's a
- * dedicated account that is not used for anything else. This will avoid headaches due to sudden errors when trying to
- * call a function from the proxy implementation. You should think of the `ProxyAdmin` instance as the administrative
- * interface of the proxy, including the ability to change who can trigger upgrades by transferring ownership.
- *
- * NOTE: The real interface of this proxy is that defined in `ITransparentUpgradeableProxy`. This contract does not
- * inherit from that interface, and instead `upgradeToAndCall` is implicitly implemented using a custom dispatch
- * mechanism in `_fallback`. Consequently, the compiler will not produce an ABI for this contract. This is necessary to
- * fully implement transparency without decoding reverts caused by selector clashes between the proxy and the
- * implementation.
- *
- * NOTE: This proxy does not inherit from {Context} deliberately. The {ProxyAdmin} of this contract won't send a
- * meta-transaction in any way, and any other meta-transaction setup should be made in the implementation contract.
- *
- * IMPORTANT: This contract avoids unnecessary storage reads by setting the admin only during construction as an
- * immutable variable, preventing any changes thereafter. However, the admin slot defined in ERC-1967 can still be
- * overwritten by the implementation logic pointed to by this proxy. In such cases, the contract may end up in an
- * undesirable state where the admin slot is different from the actual admin.
- *
- * WARNING: It is not recommended to extend this contract to add additional external functions. If you do so, the
- * compiler will not check that there are no selector conflicts, due to the note above. A selector clash between any new
- * function and the functions declared in {ITransparentUpgradeableProxy} will be resolved in favor of the new one. This
- * could render the `upgradeToAndCall` function inaccessible, preventing upgradeability and compromising transparency.
- */
-contract TransparentUpgradeableProxy is ERC1967Proxy {
-  // An immutable address for the admin to avoid unnecessary SLOADs before each call
-  // at the expense of removing the ability to change the admin once it's set.
-  // This is acceptable if the admin is always a ProxyAdmin instance or similar contract
-  // with its own ability to transfer the permissions to another account.
-  address private immutable _admin;
-
-  /**
-   * @dev The proxy caller is the current admin, and can't fallback to the proxy target.
-   */
-  error ProxyDeniedAdminAccess();
-
-  /**
-   * @dev Initializes an upgradeable proxy managed by an instance of a {ProxyAdmin} with an `initialOwner`,
-   * backed by the implementation at `_logic`, and optionally initialized with `_data` as explained in
-   * {ERC1967Proxy-constructor}.
-   */
-  constructor(
-    address _logic,
-    ProxyAdmin initialOwner,
-    bytes memory _data
-  ) payable ERC1967Proxy(_logic, _data) {
-    _admin = address(initialOwner);
-    // Set the storage value and emit an event for ERC-1967 compatibility
-    ERC1967Utils.changeAdmin(_proxyAdmin());
-  }
-
-  /**
-   * @dev Returns the admin of this proxy.
-   */
-  function _proxyAdmin() internal virtual returns (address) {
-    return _admin;
-  }
-
-  /**
-   * @dev If caller is the admin process the call internally, otherwise transparently fallback to the proxy behavior.
-   */
-  function _fallback() internal virtual override {
-    if (msg.sender == _proxyAdmin()) {
-      if (msg.sig != ITransparentUpgradeableProxy.upgradeToAndCall.selector) {
-        revert ProxyDeniedAdminAccess();
-      } else {
-        _dispatchUpgradeToAndCall();
-      }
-    } else {
-      super._fallback();
-    }
-  }
-
-  /**
-   * @dev Upgrade the implementation of the proxy. See {ERC1967Utils-upgradeToAndCall}.
-   *
-   * Requirements:
-   *
-   * - If `data` is empty, `msg.value` must be zero.
-   */
-  function _dispatchUpgradeToAndCall() private {
-    (address newImplementation, bytes memory data) = abi.decode(msg.data[4:], (address, bytes));
-    ERC1967Utils.upgradeToAndCall(newImplementation, data);
-  }
-}
diff --git a/src/contracts/transparent-proxy/interfaces/IProxyAdminOzV4.sol b/src/contracts/transparent-proxy/interfaces/IProxyAdminOzV4.sol
new file mode 100644
index 0000000..b9e092b
--- /dev/null
+++ b/src/contracts/transparent-proxy/interfaces/IProxyAdminOzV4.sol
@@ -0,0 +1,15 @@
+// SPDX-License-Identifier: MIT
+pragma solidity ^0.8.10;
+
+/**
+ * The package relies on OZ-v5, some legacy contracts rely on the oz v4 versions of `TransparentUpgradeableProxy` and `ProxyAdmin`.
+ * While we no longer recommend deploying new instances of these, we expose the interface to allow interacting with existing contracts.
+ */
+
+interface IProxyAdminOzV4 {
+  function changeProxyAdmin(address proxy, address newAdmin) external;
+
+  function upgrade(address proxy, address implementation) external;
+
+  function upgradeAndCall(address proxy, address implementation, bytes memory data) external;
+}
diff --git a/src/contracts/transparent-proxy/interfaces/ITransparentProxyFactory.sol b/src/contracts/transparent-proxy/interfaces/ITransparentProxyFactory.sol
index 52fe49c..d3151e3 100644
--- a/src/contracts/transparent-proxy/interfaces/ITransparentProxyFactory.sol
+++ b/src/contracts/transparent-proxy/interfaces/ITransparentProxyFactory.sol
@@ -1,7 +1,7 @@
 // SPDX-License-Identifier: MIT
 pragma solidity >=0.8.0;
 
-import {ProxyAdmin} from '../ProxyAdmin.sol';
+import {ProxyAdmin} from 'openzeppelin-contracts/contracts/proxy/transparent/ProxyAdmin.sol';
 
 interface ITransparentProxyFactory {
   event ProxyCreated(address proxy, address indexed logic, address indexed proxyAdmin);
@@ -28,7 +28,7 @@ interface ITransparentProxyFactory {
    *             for an `initialize` function being `function initialize(uint256 foo) external initializer;`
    * @return address The address of the proxy deployed
    **/
-  function create(address logic, ProxyAdmin admin, bytes memory data) external returns (address);
+  function create(address logic, address admin, bytes memory data) external returns (address);
 
   /**
    * @notice Creates a proxyAdmin instance, and transfers ownership to provided owner
@@ -51,7 +51,7 @@ interface ITransparentProxyFactory {
    **/
   function createDeterministic(
     address logic,
-    ProxyAdmin admin,
+    address admin,
     bytes memory data,
     bytes32 salt
   ) external returns (address);
@@ -80,7 +80,7 @@ interface ITransparentProxyFactory {
    **/
   function predictCreateDeterministic(
     address logic,
-    ProxyAdmin admin,
+    address admin,
     bytes calldata data,
     bytes32 salt
   ) external view returns (address);
diff --git a/test/PermissionlessRescuable.t.sol b/test/PermissionlessRescuable.t.sol
index 6ee4821..1791c3a 100644
--- a/test/PermissionlessRescuable.t.sol
+++ b/test/PermissionlessRescuable.t.sol
@@ -60,7 +60,7 @@ contract PermissionlessRescuableTest is Test {
     rescuable = new PermissionlessRescuable(fundsReceiver, address(restrictedMockToken));
   }
 
-  function test_whoShouldReceiveFunds() public {
+  function test_whoShouldReceiveFunds() public view {
     assertEq(rescuable.whoShouldReceiveFunds(), fundsReceiver);
   }
 
diff --git a/test/TransparentProxyFactory.t.sol b/test/TransparentProxyFactory.t.sol
index 5108103..5738727 100644
--- a/test/TransparentProxyFactory.t.sol
+++ b/test/TransparentProxyFactory.t.sol
@@ -3,9 +3,9 @@ pragma solidity ^0.8.0;
 
 import 'forge-std/Test.sol';
 import {Ownable} from 'openzeppelin-contracts/contracts/access/Ownable.sol';
-import {ProxyAdmin} from '../src/contracts/transparent-proxy/ProxyAdmin.sol';
+import {TransparentUpgradeableProxy} from 'openzeppelin-contracts/contracts/proxy/transparent/TransparentUpgradeableProxy.sol';
+import {ProxyAdmin} from 'openzeppelin-contracts/contracts/proxy/transparent/ProxyAdmin.sol';
 import {TransparentProxyFactory} from '../src/contracts/transparent-proxy/TransparentProxyFactory.sol';
-import {TransparentUpgradeableProxy} from '../src/contracts/transparent-proxy/TransparentUpgradeableProxy.sol';
 import {MockImpl} from '../src/mocks/MockImpl.sol';
 
 contract TestTransparentProxyFactory is Test {
@@ -26,12 +26,12 @@ contract TestTransparentProxyFactory is Test {
 
     address predictedAddress1 = factory.predictCreateDeterministic(
       address(mockImpl),
-      ProxyAdmin(admin),
+      admin,
       data,
       salt
     );
 
-    address proxy1 = factory.createDeterministic(address(mockImpl), ProxyAdmin(admin), data, salt);
+    address proxy1 = factory.createDeterministic(address(mockImpl), admin, data, salt);
 
     assertEq(predictedAddress1, proxy1);
     assertEq(MockImpl(proxy1).getFoo(), FOO);
@@ -53,14 +53,14 @@ contract TestTransparentProxyFactory is Test {
 
     address predictedAddress1 = factory.predictCreateDeterministic(
       address(mockImpl),
-      ProxyAdmin(deterministicProxyAdmin),
+      deterministicProxyAdmin,
       data,
       proxySalt
     );
 
     address proxy1 = factory.createDeterministic(
       address(mockImpl),
-      ProxyAdmin(deterministicProxyAdmin),
+      deterministicProxyAdmin,
       data,
       proxySalt
     );
diff --git a/test/UpgradeableOwnableWithGuardian.t.sol b/test/UpgradeableOwnableWithGuardian.t.sol
index 0c666b8..61b0915 100644
--- a/test/UpgradeableOwnableWithGuardian.t.sol
+++ b/test/UpgradeableOwnableWithGuardian.t.sol
@@ -26,7 +26,7 @@ contract TestOfUpgradableOwnableWithGuardian is Test {
     ImplOwnableWithGuardian(address(withGuardian)).initialize(owner, guardian);
   }
 
-  function test_initializer() external {
+  function test_initializer() external view {
     assertEq(withGuardian.owner(), owner);
     assertEq(withGuardian.guardian(), guardian);
   }
diff --git a/zksync/test/TransparentProxyFactoryZkSync.t.sol b/zksync/test/TransparentProxyFactoryZkSync.t.sol
index b16d39c..ed08916 100644
--- a/zksync/test/TransparentProxyFactoryZkSync.t.sol
+++ b/zksync/test/TransparentProxyFactoryZkSync.t.sol
@@ -3,28 +3,25 @@ pragma solidity ^0.8.24;
 
 import {Test} from 'forge-std/Test.sol';
 import {TransparentProxyFactoryZkSync} from '../src/contracts/transparent-proxy/TransparentProxyFactoryZkSync.sol';
-import {TransparentUpgradeableProxy} from '../../src/contracts/transparent-proxy/TransparentUpgradeableProxy.sol';
+import {TransparentUpgradeableProxy} from 'openzeppelin-contracts/contracts/proxy/transparent/TransparentUpgradeableProxy.sol';
 import {Ownable} from 'openzeppelin-contracts/contracts/access/Ownable.sol';
-import {ProxyAdmin} from '../../src/contracts/transparent-proxy/ProxyAdmin.sol';
 import {MockImpl} from '../../src/mocks/MockImpl.sol';
 
 contract TestTransparentProxyFactoryZkSync is Test {
   TransparentProxyFactoryZkSync internal factory;
   MockImpl internal mockImpl;
-  ProxyAdmin internal proxyAdmin;
   address internal owner = makeAddr('owner');
 
   function setUp() public {
     factory = new TransparentProxyFactoryZkSync();
     mockImpl = new MockImpl();
-    proxyAdmin = new ProxyAdmin(owner);
   }
 
   function testCreate() public {
     uint256 FOO = 2;
     bytes memory data = abi.encodeWithSelector(mockImpl.initialize.selector, FOO);
 
-    address proxy = factory.create(address(mockImpl), proxyAdmin, data);
+    address proxy = factory.create(address(mockImpl), owner, data);
     assertTrue(proxy.code.length != 0);
   }
 
@@ -34,12 +31,12 @@ contract TestTransparentProxyFactoryZkSync is Test {
 
     address predictedAddress1 = factory.predictCreateDeterministic(
       address(mockImpl),
-      proxyAdmin,
+      owner,
       data,
       salt
     );
 
-    address proxy1 = factory.createDeterministic(address(mockImpl), proxyAdmin, data, salt);
+    address proxy1 = factory.createDeterministic(address(mockImpl), owner, data, salt);
 
     assertEq(predictedAddress1, proxy1);
     assertTrue(proxy1.code.length != 0);
@@ -61,14 +58,14 @@ contract TestTransparentProxyFactoryZkSync is Test {
 
     address predictedAddress1 = factory.predictCreateDeterministic(
       address(mockImpl),
-      ProxyAdmin(deterministicProxyAdmin),
+      deterministicProxyAdmin,
       data,
       proxySalt
     );
 
     address proxy1 = factory.createDeterministic(
       address(mockImpl),
-      ProxyAdmin(deterministicProxyAdmin),
+      deterministicProxyAdmin,
       data,
       proxySalt
     );

From 20328348c0e7d40736ecccecd335d80eb535f1af Mon Sep 17 00:00:00 2001
From: Lukas <lukasstrassel@googlemail.com>
Date: Thu, 2 Jan 2025 20:17:56 +0100
Subject: [PATCH 2/8] fix: modify test

---
 test/TransparentProxyFactory.t.sol | 9 ++-------
 1 file changed, 2 insertions(+), 7 deletions(-)

diff --git a/test/TransparentProxyFactory.t.sol b/test/TransparentProxyFactory.t.sol
index 5738727..c679b5d 100644
--- a/test/TransparentProxyFactory.t.sol
+++ b/test/TransparentProxyFactory.t.sol
@@ -53,17 +53,12 @@ contract TestTransparentProxyFactory is Test {
 
     address predictedAddress1 = factory.predictCreateDeterministic(
       address(mockImpl),
-      deterministicProxyAdmin,
+      owner,
       data,
       proxySalt
     );
 
-    address proxy1 = factory.createDeterministic(
-      address(mockImpl),
-      deterministicProxyAdmin,
-      data,
-      proxySalt
-    );
+    address proxy1 = factory.createDeterministic(address(mockImpl), owner, data, proxySalt);
 
     assertEq(predictedAddress1, proxy1);
     assertEq(MockImpl(proxy1).getFoo(), FOO);

From 86e66c0e2536499dbfa8a9a1415bc60a5606db37 Mon Sep 17 00:00:00 2001
From: Lukas <lukasstrassel@googlemail.com>
Date: Mon, 20 Jan 2025 13:22:05 +0100
Subject: [PATCH 3/8] fix: add registry

---
 .../TransparentProxyFactoryBase.sol           |   31 +
 src/mocks/ERC721.sol                          | 1787 +++++++++--------
 test/TransparentProxyFactory.t.sol            |   23 +-
 3 files changed, 944 insertions(+), 897 deletions(-)

diff --git a/src/contracts/transparent-proxy/TransparentProxyFactoryBase.sol b/src/contracts/transparent-proxy/TransparentProxyFactoryBase.sol
index db3f5ae..17778c7 100644
--- a/src/contracts/transparent-proxy/TransparentProxyFactoryBase.sol
+++ b/src/contracts/transparent-proxy/TransparentProxyFactoryBase.sol
@@ -14,6 +14,12 @@ import {ITransparentProxyFactory} from './interfaces/ITransparentProxyFactory.so
  * @dev Highly recommended to pass as `admin` on creation an OZ ProxyAdmin instance
  **/
 abstract contract TransparentProxyFactoryBase is ITransparentProxyFactory {
+  mapping(address proxy => address admin) internal _proxyToAdmin;
+
+  function getProxyAdmin(address proxy) external view returns (address) {
+    return _proxyToAdmin[proxy];
+  }
+
   /// @inheritdoc ITransparentProxyFactory
   function create(
     address logic,
@@ -21,8 +27,10 @@ abstract contract TransparentProxyFactoryBase is ITransparentProxyFactory {
     bytes calldata data
   ) external returns (address) {
     address proxy = address(new TransparentUpgradeableProxy(logic, adminOwner, data));
+    _storeProxyInRegistry(proxy);
 
     emit ProxyCreated(proxy, logic, address(adminOwner));
+
     return proxy;
   }
 
@@ -42,6 +50,7 @@ abstract contract TransparentProxyFactoryBase is ITransparentProxyFactory {
     bytes32 salt
   ) external returns (address) {
     address proxy = address(new TransparentUpgradeableProxy{salt: salt}(logic, adminOwner, data));
+    _storeProxyInRegistry(proxy);
 
     emit ProxyDeterministicCreated(proxy, logic, address(adminOwner), salt);
     return proxy;
@@ -94,4 +103,26 @@ abstract contract TransparentProxyFactoryBase is ITransparentProxyFactory {
     bytes memory creationCode,
     bytes memory constructorArgs
   ) internal pure virtual returns (address);
+
+  function _storeProxyInRegistry(address proxy) internal {
+    _proxyToAdmin[proxy] = _predictCreate1Address(proxy);
+  }
+
+  function _predictCreate1Address(address proxy) internal virtual returns (address) {
+    return
+      address(
+        uint160(
+          uint256(
+            keccak256(
+              abi.encodePacked(
+                bytes1(0xd6), // RLP prefix for a list with total length 22
+                bytes1(0x94), // RLP prefix for an address (20 bytes)
+                proxy, // 20-byte address
+                uint8(1) // 1-byte nonce
+              )
+            )
+          )
+        )
+      );
+  }
 }
diff --git a/src/mocks/ERC721.sol b/src/mocks/ERC721.sol
index ec5281b..878b507 100644
--- a/src/mocks/ERC721.sol
+++ b/src/mocks/ERC721.sol
@@ -25,913 +25,914 @@ pragma solidity ^0.8.4;
 /// - Check that the overridden function is actually used in the function you want to
 ///   change the behavior of. Much of the code has been manually inlined for performance.
 abstract contract ERC721 {
-    /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
-    /*                         CONSTANTS                          */
-    /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
-
-    /// @dev An account can hold up to 4294967295 tokens.
-    uint256 internal constant _MAX_ACCOUNT_BALANCE = 0xffffffff;
-
-    /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
-    /*                       CUSTOM ERRORS                        */
-    /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
-
-    /// @dev Only the token owner or an approved account can manage the token.
-    error NotOwnerNorApproved();
-
-    /// @dev The token does not exist.
-    error TokenDoesNotExist();
-
-    /// @dev The token already exists.
-    error TokenAlreadyExists();
-
-    /// @dev Cannot query the balance for the zero address.
-    error BalanceQueryForZeroAddress();
-
-    /// @dev Cannot mint or transfer to the zero address.
-    error TransferToZeroAddress();
-
-    /// @dev The token must be owned by `from`.
-    error TransferFromIncorrectOwner();
-
-    /// @dev The recipient's balance has overflowed.
-    error AccountBalanceOverflow();
-
-    /// @dev Cannot safely transfer to a contract that does not implement
-    /// the ERC721Receiver interface.
-    error TransferToNonERC721ReceiverImplementer();
-
-    /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
-    /*                           EVENTS                           */
-    /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
-
-    /// @dev Emitted when token `id` is transferred from `from` to `to`.
-    event Transfer(address indexed from, address indexed to, uint256 indexed id);
-
-    /// @dev Emitted when `owner` enables `account` to manage the `id` token.
-    event Approval(address indexed owner, address indexed account, uint256 indexed id);
-
-    /// @dev Emitted when `owner` enables or disables `operator` to manage all of their tokens.
-    event ApprovalForAll(address indexed owner, address indexed operator, bool isApproved);
-
-    /// @dev `keccak256(bytes("Transfer(address,address,uint256)"))`.
-    uint256 private constant _TRANSFER_EVENT_SIGNATURE =
-        0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef;
-
-    /// @dev `keccak256(bytes("Approval(address,address,uint256)"))`.
-    uint256 private constant _APPROVAL_EVENT_SIGNATURE =
-        0x8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925;
-
-    /// @dev `keccak256(bytes("ApprovalForAll(address,address,bool)"))`.
-    uint256 private constant _APPROVAL_FOR_ALL_EVENT_SIGNATURE =
-        0x17307eab39ab6107e8899845ad3d59bd9653f200f220920489ca2b5937696c31;
-
-    /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
-    /*                          STORAGE                           */
-    /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
-
-    /// @dev The ownership data slot of `id` is given by:
-    /// ```
-    ///     mstore(0x00, id)
-    ///     mstore(0x1c, _ERC721_MASTER_SLOT_SEED)
-    ///     let ownershipSlot := add(id, add(id, keccak256(0x00, 0x20)))
-    /// ```
-    /// Bits Layout:
-    /// - [0..159]   `addr`
-    /// - [160..255] `extraData`
-    ///
-    /// The approved address slot is given by: `add(1, ownershipSlot)`.
-    ///
-    /// See: https://notes.ethereum.org/%40vbuterin/verkle_tree_eip
-    ///
-    /// The balance slot of `owner` is given by:
-    /// ```
-    ///     mstore(0x1c, _ERC721_MASTER_SLOT_SEED)
-    ///     mstore(0x00, owner)
-    ///     let balanceSlot := keccak256(0x0c, 0x1c)
-    /// ```
-    /// Bits Layout:
-    /// - [0..31]   `balance`
-    /// - [32..255] `aux`
-    ///
-    /// The `operator` approval slot of `owner` is given by:
-    /// ```
-    ///     mstore(0x1c, or(_ERC721_MASTER_SLOT_SEED, operator))
-    ///     mstore(0x00, owner)
-    ///     let operatorApprovalSlot := keccak256(0x0c, 0x30)
-    /// ```
-    uint256 private constant _ERC721_MASTER_SLOT_SEED = 0x7d8825530a5a2e7a << 192;
-
-    /// @dev Pre-shifted and pre-masked constant.
-    uint256 private constant _ERC721_MASTER_SLOT_SEED_MASKED = 0x0a5a2e7a00000000;
-
-    /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
-    /*                      ERC721 METADATA                       */
-    /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
-
-    /// @dev Returns the token collection name.
-    function name() public view virtual returns (string memory);
-
-    /// @dev Returns the token collection symbol.
-    function symbol() public view virtual returns (string memory);
-
-    /// @dev Returns the Uniform Resource Identifier (URI) for token `id`.
-    function tokenURI(uint256 id) public view virtual returns (string memory);
-
-    /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
-    /*                           ERC721                           */
-    /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
-
-    /// @dev Returns the owner of token `id`.
-    ///
-    /// Requirements:
-    /// - Token `id` must exist.
-    function ownerOf(uint256 id) public view virtual returns (address result) {
-        result = _ownerOf(id);
-        /// @solidity memory-safe-assembly
-        assembly {
-            if iszero(result) {
-                mstore(0x00, 0xceea21b6) // `TokenDoesNotExist()`.
-                revert(0x1c, 0x04)
-            }
+  /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
+  /*                         CONSTANTS                          */
+  /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
+
+  /// @dev An account can hold up to 4294967295 tokens.
+  uint256 internal constant _MAX_ACCOUNT_BALANCE = 0xffffffff;
+
+  /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
+  /*                       CUSTOM ERRORS                        */
+  /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
+
+  /// @dev Only the token owner or an approved account can manage the token.
+  error NotOwnerNorApproved();
+
+  /// @dev The token does not exist.
+  error TokenDoesNotExist();
+
+  /// @dev The token already exists.
+  error TokenAlreadyExists();
+
+  /// @dev Cannot query the balance for the zero address.
+  error BalanceQueryForZeroAddress();
+
+  /// @dev Cannot mint or transfer to the zero address.
+  error TransferToZeroAddress();
+
+  /// @dev The token must be owned by `from`.
+  error TransferFromIncorrectOwner();
+
+  /// @dev The recipient's balance has overflowed.
+  error AccountBalanceOverflow();
+
+  /// @dev Cannot safely transfer to a contract that does not implement
+  /// the ERC721Receiver interface.
+  error TransferToNonERC721ReceiverImplementer();
+
+  /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
+  /*                           EVENTS                           */
+  /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
+
+  /// @dev Emitted when token `id` is transferred from `from` to `to`.
+  event Transfer(address indexed from, address indexed to, uint256 indexed id);
+
+  /// @dev Emitted when `owner` enables `account` to manage the `id` token.
+  event Approval(address indexed owner, address indexed account, uint256 indexed id);
+
+  /// @dev Emitted when `owner` enables or disables `operator` to manage all of their tokens.
+  event ApprovalForAll(address indexed owner, address indexed operator, bool isApproved);
+
+  /// @dev `keccak256(bytes("Transfer(address,address,uint256)"))`.
+  uint256 private constant _TRANSFER_EVENT_SIGNATURE =
+    0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef;
+
+  /// @dev `keccak256(bytes("Approval(address,address,uint256)"))`.
+  uint256 private constant _APPROVAL_EVENT_SIGNATURE =
+    0x8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925;
+
+  /// @dev `keccak256(bytes("ApprovalForAll(address,address,bool)"))`.
+  uint256 private constant _APPROVAL_FOR_ALL_EVENT_SIGNATURE =
+    0x17307eab39ab6107e8899845ad3d59bd9653f200f220920489ca2b5937696c31;
+
+  /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
+  /*                          STORAGE                           */
+  /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
+
+  /// @dev The ownership data slot of `id` is given by:
+  /// ```
+  ///     mstore(0x00, id)
+  ///     mstore(0x1c, _ERC721_MASTER_SLOT_SEED)
+  ///     let ownershipSlot := add(id, add(id, keccak256(0x00, 0x20)))
+  /// ```
+  /// Bits Layout:
+  /// - [0..159]   `addr`
+  /// - [160..255] `extraData`
+  ///
+  /// The approved address slot is given by: `add(1, ownershipSlot)`.
+  ///
+  /// See: https://notes.ethereum.org/%40vbuterin/verkle_tree_eip
+  ///
+  /// The balance slot of `owner` is given by:
+  /// ```
+  ///     mstore(0x1c, _ERC721_MASTER_SLOT_SEED)
+  ///     mstore(0x00, owner)
+  ///     let balanceSlot := keccak256(0x0c, 0x1c)
+  /// ```
+  /// Bits Layout:
+  /// - [0..31]   `balance`
+  /// - [32..255] `aux`
+  ///
+  /// The `operator` approval slot of `owner` is given by:
+  /// ```
+  ///     mstore(0x1c, or(_ERC721_MASTER_SLOT_SEED, operator))
+  ///     mstore(0x00, owner)
+  ///     let operatorApprovalSlot := keccak256(0x0c, 0x30)
+  /// ```
+  uint256 private constant _ERC721_MASTER_SLOT_SEED = 0x7d8825530a5a2e7a << 192;
+
+  /// @dev Pre-shifted and pre-masked constant.
+  uint256 private constant _ERC721_MASTER_SLOT_SEED_MASKED = 0x0a5a2e7a00000000;
+
+  /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
+  /*                      ERC721 METADATA                       */
+  /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
+
+  /// @dev Returns the token collection name.
+  function name() public view virtual returns (string memory);
+
+  /// @dev Returns the token collection symbol.
+  function symbol() public view virtual returns (string memory);
+
+  /// @dev Returns the Uniform Resource Identifier (URI) for token `id`.
+  function tokenURI(uint256 id) public view virtual returns (string memory);
+
+  /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
+  /*                           ERC721                           */
+  /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
+
+  /// @dev Returns the owner of token `id`.
+  ///
+  /// Requirements:
+  /// - Token `id` must exist.
+  function ownerOf(uint256 id) public view virtual returns (address result) {
+    result = _ownerOf(id);
+    /// @solidity memory-safe-assembly
+    assembly {
+      if iszero(result) {
+        mstore(0x00, 0xceea21b6) // `TokenDoesNotExist()`.
+        revert(0x1c, 0x04)
+      }
+    }
+  }
+
+  /// @dev Returns the number of tokens owned by `owner`.
+  ///
+  /// Requirements:
+  /// - `owner` must not be the zero address.
+  function balanceOf(address owner) public view virtual returns (uint256 result) {
+    /// @solidity memory-safe-assembly
+    assembly {
+      // Revert if the `owner` is the zero address.
+      if iszero(owner) {
+        mstore(0x00, 0x8f4eb604) // `BalanceQueryForZeroAddress()`.
+        revert(0x1c, 0x04)
+      }
+      mstore(0x1c, _ERC721_MASTER_SLOT_SEED)
+      mstore(0x00, owner)
+      result := and(sload(keccak256(0x0c, 0x1c)), _MAX_ACCOUNT_BALANCE)
+    }
+  }
+
+  /// @dev Returns the account approved to manage token `id`.
+  ///
+  /// Requirements:
+  /// - Token `id` must exist.
+  function getApproved(uint256 id) public view virtual returns (address result) {
+    /// @solidity memory-safe-assembly
+    assembly {
+      mstore(0x00, id)
+      mstore(0x1c, _ERC721_MASTER_SLOT_SEED)
+      let ownershipSlot := add(id, add(id, keccak256(0x00, 0x20)))
+      if iszero(shl(96, sload(ownershipSlot))) {
+        mstore(0x00, 0xceea21b6) // `TokenDoesNotExist()`.
+        revert(0x1c, 0x04)
+      }
+      result := sload(add(1, ownershipSlot))
+    }
+  }
+
+  /// @dev Sets `account` as the approved account to manage token `id`.
+  ///
+  /// Requirements:
+  /// - Token `id` must exist.
+  /// - The caller must be the owner of the token,
+  ///   or an approved operator for the token owner.
+  ///
+  /// Emits an {Approval} event.
+  function approve(address account, uint256 id) public payable virtual {
+    _approve(msg.sender, account, id);
+  }
+
+  /// @dev Returns whether `operator` is approved to manage the tokens of `owner`.
+  function isApprovedForAll(
+    address owner,
+    address operator
+  ) public view virtual returns (bool result) {
+    /// @solidity memory-safe-assembly
+    assembly {
+      mstore(0x1c, operator)
+      mstore(0x08, _ERC721_MASTER_SLOT_SEED_MASKED)
+      mstore(0x00, owner)
+      result := sload(keccak256(0x0c, 0x30))
+    }
+  }
+
+  /// @dev Sets whether `operator` is approved to manage the tokens of the caller.
+  ///
+  /// Emits an {ApprovalForAll} event.
+  function setApprovalForAll(address operator, bool isApproved) public virtual {
+    /// @solidity memory-safe-assembly
+    assembly {
+      // Convert to 0 or 1.
+      isApproved := iszero(iszero(isApproved))
+      // Update the `isApproved` for (`msg.sender`, `operator`).
+      mstore(0x1c, operator)
+      mstore(0x08, _ERC721_MASTER_SLOT_SEED_MASKED)
+      mstore(0x00, caller())
+      sstore(keccak256(0x0c, 0x30), isApproved)
+      // Emit the {ApprovalForAll} event.
+      mstore(0x00, isApproved)
+      // forgefmt: disable-next-item
+      log3(0x00, 0x20, _APPROVAL_FOR_ALL_EVENT_SIGNATURE, caller(), shr(96, shl(96, operator)))
+    }
+  }
+
+  /// @dev Transfers token `id` from `from` to `to`.
+  ///
+  /// Requirements:
+  ///
+  /// - Token `id` must exist.
+  /// - `from` must be the owner of the token.
+  /// - `to` cannot be the zero address.
+  /// - The caller must be the owner of the token, or be approved to manage the token.
+  ///
+  /// Emits a {Transfer} event.
+  function transferFrom(address from, address to, uint256 id) public payable virtual {
+    _beforeTokenTransfer(from, to, id);
+    /// @solidity memory-safe-assembly
+    assembly {
+      // Clear the upper 96 bits.
+      let bitmaskAddress := shr(96, not(0))
+      from := and(bitmaskAddress, from)
+      to := and(bitmaskAddress, to)
+      // Load the ownership data.
+      mstore(0x00, id)
+      mstore(0x1c, or(_ERC721_MASTER_SLOT_SEED, caller()))
+      let ownershipSlot := add(id, add(id, keccak256(0x00, 0x20)))
+      let ownershipPacked := sload(ownershipSlot)
+      let owner := and(bitmaskAddress, ownershipPacked)
+      // Revert if the token does not exist, or if `from` is not the owner.
+      if iszero(mul(owner, eq(owner, from))) {
+        // `TokenDoesNotExist()`, `TransferFromIncorrectOwner()`.
+        mstore(shl(2, iszero(owner)), 0xceea21b6a1148100)
+        revert(0x1c, 0x04)
+      }
+      // Load, check, and update the token approval.
+      {
+        mstore(0x00, from)
+        let approvedAddress := sload(add(1, ownershipSlot))
+        // Revert if the caller is not the owner, nor approved.
+        if iszero(or(eq(caller(), from), eq(caller(), approvedAddress))) {
+          if iszero(sload(keccak256(0x0c, 0x30))) {
+            mstore(0x00, 0x4b6e7f18) // `NotOwnerNorApproved()`.
+            revert(0x1c, 0x04)
+          }
         }
-    }
-
-    /// @dev Returns the number of tokens owned by `owner`.
-    ///
-    /// Requirements:
-    /// - `owner` must not be the zero address.
-    function balanceOf(address owner) public view virtual returns (uint256 result) {
-        /// @solidity memory-safe-assembly
-        assembly {
-            // Revert if the `owner` is the zero address.
-            if iszero(owner) {
-                mstore(0x00, 0x8f4eb604) // `BalanceQueryForZeroAddress()`.
-                revert(0x1c, 0x04)
-            }
-            mstore(0x1c, _ERC721_MASTER_SLOT_SEED)
-            mstore(0x00, owner)
-            result := and(sload(keccak256(0x0c, 0x1c)), _MAX_ACCOUNT_BALANCE)
-        }
-    }
-
-    /// @dev Returns the account approved to manage token `id`.
-    ///
-    /// Requirements:
-    /// - Token `id` must exist.
-    function getApproved(uint256 id) public view virtual returns (address result) {
-        /// @solidity memory-safe-assembly
-        assembly {
-            mstore(0x00, id)
-            mstore(0x1c, _ERC721_MASTER_SLOT_SEED)
-            let ownershipSlot := add(id, add(id, keccak256(0x00, 0x20)))
-            if iszero(shl(96, sload(ownershipSlot))) {
-                mstore(0x00, 0xceea21b6) // `TokenDoesNotExist()`.
-                revert(0x1c, 0x04)
-            }
-            result := sload(add(1, ownershipSlot))
-        }
-    }
-
-    /// @dev Sets `account` as the approved account to manage token `id`.
-    ///
-    /// Requirements:
-    /// - Token `id` must exist.
-    /// - The caller must be the owner of the token,
-    ///   or an approved operator for the token owner.
-    ///
-    /// Emits an {Approval} event.
-    function approve(address account, uint256 id) public payable virtual {
-        _approve(msg.sender, account, id);
-    }
-
-    /// @dev Returns whether `operator` is approved to manage the tokens of `owner`.
-    function isApprovedForAll(address owner, address operator)
-        public
-        view
-        virtual
-        returns (bool result)
-    {
-        /// @solidity memory-safe-assembly
-        assembly {
-            mstore(0x1c, operator)
-            mstore(0x08, _ERC721_MASTER_SLOT_SEED_MASKED)
-            mstore(0x00, owner)
-            result := sload(keccak256(0x0c, 0x30))
-        }
-    }
-
-    /// @dev Sets whether `operator` is approved to manage the tokens of the caller.
-    ///
-    /// Emits an {ApprovalForAll} event.
-    function setApprovalForAll(address operator, bool isApproved) public virtual {
-        /// @solidity memory-safe-assembly
-        assembly {
-            // Convert to 0 or 1.
-            isApproved := iszero(iszero(isApproved))
-            // Update the `isApproved` for (`msg.sender`, `operator`).
-            mstore(0x1c, operator)
-            mstore(0x08, _ERC721_MASTER_SLOT_SEED_MASKED)
-            mstore(0x00, caller())
-            sstore(keccak256(0x0c, 0x30), isApproved)
-            // Emit the {ApprovalForAll} event.
-            mstore(0x00, isApproved)
-            // forgefmt: disable-next-item
-            log3(0x00, 0x20, _APPROVAL_FOR_ALL_EVENT_SIGNATURE, caller(), shr(96, shl(96, operator)))
+        // Delete the approved address if any.
+        if approvedAddress {
+          sstore(add(1, ownershipSlot), 0)
         }
-    }
-
-    /// @dev Transfers token `id` from `from` to `to`.
-    ///
-    /// Requirements:
-    ///
-    /// - Token `id` must exist.
-    /// - `from` must be the owner of the token.
-    /// - `to` cannot be the zero address.
-    /// - The caller must be the owner of the token, or be approved to manage the token.
-    ///
-    /// Emits a {Transfer} event.
-    function transferFrom(address from, address to, uint256 id) public payable virtual {
-        _beforeTokenTransfer(from, to, id);
-        /// @solidity memory-safe-assembly
-        assembly {
-            // Clear the upper 96 bits.
-            let bitmaskAddress := shr(96, not(0))
-            from := and(bitmaskAddress, from)
-            to := and(bitmaskAddress, to)
-            // Load the ownership data.
-            mstore(0x00, id)
-            mstore(0x1c, or(_ERC721_MASTER_SLOT_SEED, caller()))
-            let ownershipSlot := add(id, add(id, keccak256(0x00, 0x20)))
-            let ownershipPacked := sload(ownershipSlot)
-            let owner := and(bitmaskAddress, ownershipPacked)
-            // Revert if the token does not exist, or if `from` is not the owner.
-            if iszero(mul(owner, eq(owner, from))) {
-                // `TokenDoesNotExist()`, `TransferFromIncorrectOwner()`.
-                mstore(shl(2, iszero(owner)), 0xceea21b6a1148100)
-                revert(0x1c, 0x04)
-            }
-            // Load, check, and update the token approval.
-            {
-                mstore(0x00, from)
-                let approvedAddress := sload(add(1, ownershipSlot))
-                // Revert if the caller is not the owner, nor approved.
-                if iszero(or(eq(caller(), from), eq(caller(), approvedAddress))) {
-                    if iszero(sload(keccak256(0x0c, 0x30))) {
-                        mstore(0x00, 0x4b6e7f18) // `NotOwnerNorApproved()`.
-                        revert(0x1c, 0x04)
-                    }
-                }
-                // Delete the approved address if any.
-                if approvedAddress { sstore(add(1, ownershipSlot), 0) }
-            }
-            // Update with the new owner.
-            sstore(ownershipSlot, xor(ownershipPacked, xor(from, to)))
-            // Decrement the balance of `from`.
-            {
-                let fromBalanceSlot := keccak256(0x0c, 0x1c)
-                sstore(fromBalanceSlot, sub(sload(fromBalanceSlot), 1))
-            }
-            // Increment the balance of `to`.
-            {
-                mstore(0x00, to)
-                let toBalanceSlot := keccak256(0x0c, 0x1c)
-                let toBalanceSlotPacked := add(sload(toBalanceSlot), 1)
-                // Revert if `to` is the zero address, or if the account balance overflows.
-                if iszero(mul(to, and(toBalanceSlotPacked, _MAX_ACCOUNT_BALANCE))) {
-                    // `TransferToZeroAddress()`, `AccountBalanceOverflow()`.
-                    mstore(shl(2, iszero(to)), 0xea553b3401336cea)
-                    revert(0x1c, 0x04)
-                }
-                sstore(toBalanceSlot, toBalanceSlotPacked)
-            }
-            // Emit the {Transfer} event.
-            log4(codesize(), 0x00, _TRANSFER_EVENT_SIGNATURE, from, to, id)
+      }
+      // Update with the new owner.
+      sstore(ownershipSlot, xor(ownershipPacked, xor(from, to)))
+      // Decrement the balance of `from`.
+      {
+        let fromBalanceSlot := keccak256(0x0c, 0x1c)
+        sstore(fromBalanceSlot, sub(sload(fromBalanceSlot), 1))
+      }
+      // Increment the balance of `to`.
+      {
+        mstore(0x00, to)
+        let toBalanceSlot := keccak256(0x0c, 0x1c)
+        let toBalanceSlotPacked := add(sload(toBalanceSlot), 1)
+        // Revert if `to` is the zero address, or if the account balance overflows.
+        if iszero(mul(to, and(toBalanceSlotPacked, _MAX_ACCOUNT_BALANCE))) {
+          // `TransferToZeroAddress()`, `AccountBalanceOverflow()`.
+          mstore(shl(2, iszero(to)), 0xea553b3401336cea)
+          revert(0x1c, 0x04)
         }
-        _afterTokenTransfer(from, to, id);
-    }
-
-    /// @dev Equivalent to `safeTransferFrom(from, to, id, "")`.
-    function safeTransferFrom(address from, address to, uint256 id) public payable virtual {
-        transferFrom(from, to, id);
-        if (_hasCode(to)) _checkOnERC721Received(from, to, id, "");
-    }
-
-    /// @dev Transfers token `id` from `from` to `to`.
-    ///
-    /// Requirements:
-    ///
-    /// - Token `id` must exist.
-    /// - `from` must be the owner of the token.
-    /// - `to` cannot be the zero address.
-    /// - The caller must be the owner of the token, or be approved to manage the token.
-    /// - If `to` refers to a smart contract, it must implement
-    ///   {IERC721Receiver-onERC721Received}, which is called upon a safe transfer.
-    ///
-    /// Emits a {Transfer} event.
-    function safeTransferFrom(address from, address to, uint256 id, bytes calldata data)
-        public
-        payable
-        virtual
-    {
-        transferFrom(from, to, id);
-        if (_hasCode(to)) _checkOnERC721Received(from, to, id, data);
-    }
-
-    /// @dev Returns true if this contract implements the interface defined by `interfaceId`.
-    /// See: https://eips.ethereum.org/EIPS/eip-165
-    /// This function call must use less than 30000 gas.
-    function supportsInterface(bytes4 interfaceId) public view virtual returns (bool result) {
-        /// @solidity memory-safe-assembly
-        assembly {
-            let s := shr(224, interfaceId)
-            // ERC165: 0x01ffc9a7, ERC721: 0x80ac58cd, ERC721Metadata: 0x5b5e139f.
-            result := or(or(eq(s, 0x01ffc9a7), eq(s, 0x80ac58cd)), eq(s, 0x5b5e139f))
+        sstore(toBalanceSlot, toBalanceSlotPacked)
+      }
+      // Emit the {Transfer} event.
+      log4(codesize(), 0x00, _TRANSFER_EVENT_SIGNATURE, from, to, id)
+    }
+    _afterTokenTransfer(from, to, id);
+  }
+
+  /// @dev Equivalent to `safeTransferFrom(from, to, id, "")`.
+  function safeTransferFrom(address from, address to, uint256 id) public payable virtual {
+    transferFrom(from, to, id);
+    if (_hasCode(to)) _checkOnERC721Received(from, to, id, '');
+  }
+
+  /// @dev Transfers token `id` from `from` to `to`.
+  ///
+  /// Requirements:
+  ///
+  /// - Token `id` must exist.
+  /// - `from` must be the owner of the token.
+  /// - `to` cannot be the zero address.
+  /// - The caller must be the owner of the token, or be approved to manage the token.
+  /// - If `to` refers to a smart contract, it must implement
+  ///   {IERC721Receiver-onERC721Received}, which is called upon a safe transfer.
+  ///
+  /// Emits a {Transfer} event.
+  function safeTransferFrom(
+    address from,
+    address to,
+    uint256 id,
+    bytes calldata data
+  ) public payable virtual {
+    transferFrom(from, to, id);
+    if (_hasCode(to)) _checkOnERC721Received(from, to, id, data);
+  }
+
+  /// @dev Returns true if this contract implements the interface defined by `interfaceId`.
+  /// See: https://eips.ethereum.org/EIPS/eip-165
+  /// This function call must use less than 30000 gas.
+  function supportsInterface(bytes4 interfaceId) public view virtual returns (bool result) {
+    /// @solidity memory-safe-assembly
+    assembly {
+      let s := shr(224, interfaceId)
+      // ERC165: 0x01ffc9a7, ERC721: 0x80ac58cd, ERC721Metadata: 0x5b5e139f.
+      result := or(or(eq(s, 0x01ffc9a7), eq(s, 0x80ac58cd)), eq(s, 0x5b5e139f))
+    }
+  }
+
+  /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
+  /*                  INTERNAL QUERY FUNCTIONS                  */
+  /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
+
+  /// @dev Returns if token `id` exists.
+  function _exists(uint256 id) internal view virtual returns (bool result) {
+    /// @solidity memory-safe-assembly
+    assembly {
+      mstore(0x00, id)
+      mstore(0x1c, _ERC721_MASTER_SLOT_SEED)
+      result := iszero(iszero(shl(96, sload(add(id, add(id, keccak256(0x00, 0x20)))))))
+    }
+  }
+
+  /// @dev Returns the owner of token `id`.
+  /// Returns the zero address instead of reverting if the token does not exist.
+  function _ownerOf(uint256 id) internal view virtual returns (address result) {
+    /// @solidity memory-safe-assembly
+    assembly {
+      mstore(0x00, id)
+      mstore(0x1c, _ERC721_MASTER_SLOT_SEED)
+      result := shr(96, shl(96, sload(add(id, add(id, keccak256(0x00, 0x20))))))
+    }
+  }
+
+  /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
+  /*            INTERNAL DATA HITCHHIKING FUNCTIONS             */
+  /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
+
+  // For performance, no events are emitted for the hitchhiking setters.
+  // Please emit your own events if required.
+
+  /// @dev Returns the auxiliary data for `owner`.
+  /// Minting, transferring, burning the tokens of `owner` will not change the auxiliary data.
+  /// Auxiliary data can be set for any address, even if it does not have any tokens.
+  function _getAux(address owner) internal view virtual returns (uint224 result) {
+    /// @solidity memory-safe-assembly
+    assembly {
+      mstore(0x1c, _ERC721_MASTER_SLOT_SEED)
+      mstore(0x00, owner)
+      result := shr(32, sload(keccak256(0x0c, 0x1c)))
+    }
+  }
+
+  /// @dev Set the auxiliary data for `owner` to `value`.
+  /// Minting, transferring, burning the tokens of `owner` will not change the auxiliary data.
+  /// Auxiliary data can be set for any address, even if it does not have any tokens.
+  function _setAux(address owner, uint224 value) internal virtual {
+    /// @solidity memory-safe-assembly
+    assembly {
+      mstore(0x1c, _ERC721_MASTER_SLOT_SEED)
+      mstore(0x00, owner)
+      let balanceSlot := keccak256(0x0c, 0x1c)
+      let packed := sload(balanceSlot)
+      sstore(balanceSlot, xor(packed, shl(32, xor(value, shr(32, packed)))))
+    }
+  }
+
+  /// @dev Returns the extra data for token `id`.
+  /// Minting, transferring, burning a token will not change the extra data.
+  /// The extra data can be set on a non-existent token.
+  function _getExtraData(uint256 id) internal view virtual returns (uint96 result) {
+    /// @solidity memory-safe-assembly
+    assembly {
+      mstore(0x00, id)
+      mstore(0x1c, _ERC721_MASTER_SLOT_SEED)
+      result := shr(160, sload(add(id, add(id, keccak256(0x00, 0x20)))))
+    }
+  }
+
+  /// @dev Sets the extra data for token `id` to `value`.
+  /// Minting, transferring, burning a token will not change the extra data.
+  /// The extra data can be set on a non-existent token.
+  function _setExtraData(uint256 id, uint96 value) internal virtual {
+    /// @solidity memory-safe-assembly
+    assembly {
+      mstore(0x00, id)
+      mstore(0x1c, _ERC721_MASTER_SLOT_SEED)
+      let ownershipSlot := add(id, add(id, keccak256(0x00, 0x20)))
+      let packed := sload(ownershipSlot)
+      sstore(ownershipSlot, xor(packed, shl(160, xor(value, shr(160, packed)))))
+    }
+  }
+
+  /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
+  /*                  INTERNAL MINT FUNCTIONS                   */
+  /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
+
+  /// @dev Mints token `id` to `to`.
+  ///
+  /// Requirements:
+  ///
+  /// - Token `id` must not exist.
+  /// - `to` cannot be the zero address.
+  ///
+  /// Emits a {Transfer} event.
+  function _mint(address to, uint256 id) internal virtual {
+    _beforeTokenTransfer(address(0), to, id);
+    /// @solidity memory-safe-assembly
+    assembly {
+      // Clear the upper 96 bits.
+      to := shr(96, shl(96, to))
+      // Load the ownership data.
+      mstore(0x00, id)
+      mstore(0x1c, _ERC721_MASTER_SLOT_SEED)
+      let ownershipSlot := add(id, add(id, keccak256(0x00, 0x20)))
+      let ownershipPacked := sload(ownershipSlot)
+      // Revert if the token already exists.
+      if shl(96, ownershipPacked) {
+        mstore(0x00, 0xc991cbb1) // `TokenAlreadyExists()`.
+        revert(0x1c, 0x04)
+      }
+      // Update with the owner.
+      sstore(ownershipSlot, or(ownershipPacked, to))
+      // Increment the balance of the owner.
+      {
+        mstore(0x00, to)
+        let balanceSlot := keccak256(0x0c, 0x1c)
+        let balanceSlotPacked := add(sload(balanceSlot), 1)
+        // Revert if `to` is the zero address, or if the account balance overflows.
+        if iszero(mul(to, and(balanceSlotPacked, _MAX_ACCOUNT_BALANCE))) {
+          // `TransferToZeroAddress()`, `AccountBalanceOverflow()`.
+          mstore(shl(2, iszero(to)), 0xea553b3401336cea)
+          revert(0x1c, 0x04)
         }
-    }
-
-    /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
-    /*                  INTERNAL QUERY FUNCTIONS                  */
-    /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
-
-    /// @dev Returns if token `id` exists.
-    function _exists(uint256 id) internal view virtual returns (bool result) {
-        /// @solidity memory-safe-assembly
-        assembly {
-            mstore(0x00, id)
-            mstore(0x1c, _ERC721_MASTER_SLOT_SEED)
-            result := iszero(iszero(shl(96, sload(add(id, add(id, keccak256(0x00, 0x20)))))))
+        sstore(balanceSlot, balanceSlotPacked)
+      }
+      // Emit the {Transfer} event.
+      log4(codesize(), 0x00, _TRANSFER_EVENT_SIGNATURE, 0, to, id)
+    }
+    _afterTokenTransfer(address(0), to, id);
+  }
+
+  /// @dev Mints token `id` to `to`, and updates the extra data for token `id` to `value`.
+  /// Does NOT check if token `id` already exists (assumes `id` is auto-incrementing).
+  ///
+  /// Requirements:
+  ///
+  /// - `to` cannot be the zero address.
+  ///
+  /// Emits a {Transfer} event.
+  function _mintAndSetExtraDataUnchecked(address to, uint256 id, uint96 value) internal virtual {
+    _beforeTokenTransfer(address(0), to, id);
+    /// @solidity memory-safe-assembly
+    assembly {
+      // Clear the upper 96 bits.
+      to := shr(96, shl(96, to))
+      // Update with the owner and extra data.
+      mstore(0x00, id)
+      mstore(0x1c, _ERC721_MASTER_SLOT_SEED)
+      sstore(add(id, add(id, keccak256(0x00, 0x20))), or(shl(160, value), to))
+      // Increment the balance of the owner.
+      {
+        mstore(0x00, to)
+        let balanceSlot := keccak256(0x0c, 0x1c)
+        let balanceSlotPacked := add(sload(balanceSlot), 1)
+        // Revert if `to` is the zero address, or if the account balance overflows.
+        if iszero(mul(to, and(balanceSlotPacked, _MAX_ACCOUNT_BALANCE))) {
+          // `TransferToZeroAddress()`, `AccountBalanceOverflow()`.
+          mstore(shl(2, iszero(to)), 0xea553b3401336cea)
+          revert(0x1c, 0x04)
         }
-    }
-
-    /// @dev Returns the owner of token `id`.
-    /// Returns the zero address instead of reverting if the token does not exist.
-    function _ownerOf(uint256 id) internal view virtual returns (address result) {
-        /// @solidity memory-safe-assembly
-        assembly {
-            mstore(0x00, id)
-            mstore(0x1c, _ERC721_MASTER_SLOT_SEED)
-            result := shr(96, shl(96, sload(add(id, add(id, keccak256(0x00, 0x20))))))
+        sstore(balanceSlot, balanceSlotPacked)
+      }
+      // Emit the {Transfer} event.
+      log4(codesize(), 0x00, _TRANSFER_EVENT_SIGNATURE, 0, to, id)
+    }
+    _afterTokenTransfer(address(0), to, id);
+  }
+
+  /// @dev Equivalent to `_safeMint(to, id, "")`.
+  function _safeMint(address to, uint256 id) internal virtual {
+    _safeMint(to, id, '');
+  }
+
+  /// @dev Mints token `id` to `to`.
+  ///
+  /// Requirements:
+  ///
+  /// - Token `id` must not exist.
+  /// - `to` cannot be the zero address.
+  /// - If `to` refers to a smart contract, it must implement
+  ///   {IERC721Receiver-onERC721Received}, which is called upon a safe transfer.
+  ///
+  /// Emits a {Transfer} event.
+  function _safeMint(address to, uint256 id, bytes memory data) internal virtual {
+    _mint(to, id);
+    if (_hasCode(to)) _checkOnERC721Received(address(0), to, id, data);
+  }
+
+  /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
+  /*                  INTERNAL BURN FUNCTIONS                   */
+  /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
+
+  /// @dev Equivalent to `_burn(address(0), id)`.
+  function _burn(uint256 id) internal virtual {
+    _burn(address(0), id);
+  }
+
+  /// @dev Destroys token `id`, using `by`.
+  ///
+  /// Requirements:
+  ///
+  /// - Token `id` must exist.
+  /// - If `by` is not the zero address,
+  ///   it must be the owner of the token, or be approved to manage the token.
+  ///
+  /// Emits a {Transfer} event.
+  function _burn(address by, uint256 id) internal virtual {
+    address owner = ownerOf(id);
+    _beforeTokenTransfer(owner, address(0), id);
+    /// @solidity memory-safe-assembly
+    assembly {
+      // Clear the upper 96 bits.
+      by := shr(96, shl(96, by))
+      // Load the ownership data.
+      mstore(0x00, id)
+      mstore(0x1c, or(_ERC721_MASTER_SLOT_SEED, by))
+      let ownershipSlot := add(id, add(id, keccak256(0x00, 0x20)))
+      let ownershipPacked := sload(ownershipSlot)
+      // Reload the owner in case it is changed in `_beforeTokenTransfer`.
+      owner := shr(96, shl(96, ownershipPacked))
+      // Revert if the token does not exist.
+      if iszero(owner) {
+        mstore(0x00, 0xceea21b6) // `TokenDoesNotExist()`.
+        revert(0x1c, 0x04)
+      }
+      // Load and check the token approval.
+      {
+        mstore(0x00, owner)
+        let approvedAddress := sload(add(1, ownershipSlot))
+        // If `by` is not the zero address, do the authorization check.
+        // Revert if the `by` is not the owner, nor approved.
+        if iszero(or(iszero(by), or(eq(by, owner), eq(by, approvedAddress)))) {
+          if iszero(sload(keccak256(0x0c, 0x30))) {
+            mstore(0x00, 0x4b6e7f18) // `NotOwnerNorApproved()`.
+            revert(0x1c, 0x04)
+          }
         }
-    }
-
-    /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
-    /*            INTERNAL DATA HITCHHIKING FUNCTIONS             */
-    /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
-
-    // For performance, no events are emitted for the hitchhiking setters.
-    // Please emit your own events if required.
-
-    /// @dev Returns the auxiliary data for `owner`.
-    /// Minting, transferring, burning the tokens of `owner` will not change the auxiliary data.
-    /// Auxiliary data can be set for any address, even if it does not have any tokens.
-    function _getAux(address owner) internal view virtual returns (uint224 result) {
-        /// @solidity memory-safe-assembly
-        assembly {
-            mstore(0x1c, _ERC721_MASTER_SLOT_SEED)
-            mstore(0x00, owner)
-            result := shr(32, sload(keccak256(0x0c, 0x1c)))
+        // Delete the approved address if any.
+        if approvedAddress {
+          sstore(add(1, ownershipSlot), 0)
         }
-    }
-
-    /// @dev Set the auxiliary data for `owner` to `value`.
-    /// Minting, transferring, burning the tokens of `owner` will not change the auxiliary data.
-    /// Auxiliary data can be set for any address, even if it does not have any tokens.
-    function _setAux(address owner, uint224 value) internal virtual {
-        /// @solidity memory-safe-assembly
-        assembly {
-            mstore(0x1c, _ERC721_MASTER_SLOT_SEED)
-            mstore(0x00, owner)
-            let balanceSlot := keccak256(0x0c, 0x1c)
-            let packed := sload(balanceSlot)
-            sstore(balanceSlot, xor(packed, shl(32, xor(value, shr(32, packed)))))
+      }
+      // Clear the owner.
+      sstore(ownershipSlot, xor(ownershipPacked, owner))
+      // Decrement the balance of `owner`.
+      {
+        let balanceSlot := keccak256(0x0c, 0x1c)
+        sstore(balanceSlot, sub(sload(balanceSlot), 1))
+      }
+      // Emit the {Transfer} event.
+      log4(codesize(), 0x00, _TRANSFER_EVENT_SIGNATURE, owner, 0, id)
+    }
+    _afterTokenTransfer(owner, address(0), id);
+  }
+
+  /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
+  /*                INTERNAL APPROVAL FUNCTIONS                 */
+  /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
+
+  /// @dev Returns whether `account` is the owner of token `id`, or is approved to manage it.
+  ///
+  /// Requirements:
+  /// - Token `id` must exist.
+  function _isApprovedOrOwner(
+    address account,
+    uint256 id
+  ) internal view virtual returns (bool result) {
+    /// @solidity memory-safe-assembly
+    assembly {
+      result := 1
+      // Clear the upper 96 bits.
+      account := shr(96, shl(96, account))
+      // Load the ownership data.
+      mstore(0x00, id)
+      mstore(0x1c, or(_ERC721_MASTER_SLOT_SEED, account))
+      let ownershipSlot := add(id, add(id, keccak256(0x00, 0x20)))
+      let owner := shr(96, shl(96, sload(ownershipSlot)))
+      // Revert if the token does not exist.
+      if iszero(owner) {
+        mstore(0x00, 0xceea21b6) // `TokenDoesNotExist()`.
+        revert(0x1c, 0x04)
+      }
+      // Check if `account` is the `owner`.
+      if iszero(eq(account, owner)) {
+        mstore(0x00, owner)
+        // Check if `account` is approved to manage the token.
+        if iszero(sload(keccak256(0x0c, 0x30))) {
+          result := eq(account, sload(add(1, ownershipSlot)))
         }
-    }
-
-    /// @dev Returns the extra data for token `id`.
-    /// Minting, transferring, burning a token will not change the extra data.
-    /// The extra data can be set on a non-existent token.
-    function _getExtraData(uint256 id) internal view virtual returns (uint96 result) {
-        /// @solidity memory-safe-assembly
-        assembly {
-            mstore(0x00, id)
-            mstore(0x1c, _ERC721_MASTER_SLOT_SEED)
-            result := shr(160, sload(add(id, add(id, keccak256(0x00, 0x20)))))
+      }
+    }
+  }
+
+  /// @dev Returns the account approved to manage token `id`.
+  /// Returns the zero address instead of reverting if the token does not exist.
+  function _getApproved(uint256 id) internal view virtual returns (address result) {
+    /// @solidity memory-safe-assembly
+    assembly {
+      mstore(0x00, id)
+      mstore(0x1c, _ERC721_MASTER_SLOT_SEED)
+      result := sload(add(1, add(id, add(id, keccak256(0x00, 0x20)))))
+    }
+  }
+
+  /// @dev Equivalent to `_approve(address(0), account, id)`.
+  function _approve(address account, uint256 id) internal virtual {
+    _approve(address(0), account, id);
+  }
+
+  /// @dev Sets `account` as the approved account to manage token `id`, using `by`.
+  ///
+  /// Requirements:
+  /// - Token `id` must exist.
+  /// - If `by` is not the zero address, `by` must be the owner
+  ///   or an approved operator for the token owner.
+  ///
+  /// Emits a {Approval} event.
+  function _approve(address by, address account, uint256 id) internal virtual {
+    assembly {
+      // Clear the upper 96 bits.
+      let bitmaskAddress := shr(96, not(0))
+      account := and(bitmaskAddress, account)
+      by := and(bitmaskAddress, by)
+      // Load the owner of the token.
+      mstore(0x00, id)
+      mstore(0x1c, or(_ERC721_MASTER_SLOT_SEED, by))
+      let ownershipSlot := add(id, add(id, keccak256(0x00, 0x20)))
+      let owner := and(bitmaskAddress, sload(ownershipSlot))
+      // Revert if the token does not exist.
+      if iszero(owner) {
+        mstore(0x00, 0xceea21b6) // `TokenDoesNotExist()`.
+        revert(0x1c, 0x04)
+      }
+      // If `by` is not the zero address, do the authorization check.
+      // Revert if `by` is not the owner, nor approved.
+      if iszero(or(iszero(by), eq(by, owner))) {
+        mstore(0x00, owner)
+        if iszero(sload(keccak256(0x0c, 0x30))) {
+          mstore(0x00, 0x4b6e7f18) // `NotOwnerNorApproved()`.
+          revert(0x1c, 0x04)
         }
-    }
-
-    /// @dev Sets the extra data for token `id` to `value`.
-    /// Minting, transferring, burning a token will not change the extra data.
-    /// The extra data can be set on a non-existent token.
-    function _setExtraData(uint256 id, uint96 value) internal virtual {
-        /// @solidity memory-safe-assembly
-        assembly {
-            mstore(0x00, id)
-            mstore(0x1c, _ERC721_MASTER_SLOT_SEED)
-            let ownershipSlot := add(id, add(id, keccak256(0x00, 0x20)))
-            let packed := sload(ownershipSlot)
-            sstore(ownershipSlot, xor(packed, shl(160, xor(value, shr(160, packed)))))
+      }
+      // Sets `account` as the approved account to manage `id`.
+      sstore(add(1, ownershipSlot), account)
+      // Emit the {Approval} event.
+      log4(codesize(), 0x00, _APPROVAL_EVENT_SIGNATURE, owner, account, id)
+    }
+  }
+
+  /// @dev Approve or remove the `operator` as an operator for `by`,
+  /// without authorization checks.
+  ///
+  /// Emits an {ApprovalForAll} event.
+  function _setApprovalForAll(address by, address operator, bool isApproved) internal virtual {
+    /// @solidity memory-safe-assembly
+    assembly {
+      // Clear the upper 96 bits.
+      by := shr(96, shl(96, by))
+      operator := shr(96, shl(96, operator))
+      // Convert to 0 or 1.
+      isApproved := iszero(iszero(isApproved))
+      // Update the `isApproved` for (`by`, `operator`).
+      mstore(0x1c, or(_ERC721_MASTER_SLOT_SEED, operator))
+      mstore(0x00, by)
+      sstore(keccak256(0x0c, 0x30), isApproved)
+      // Emit the {ApprovalForAll} event.
+      mstore(0x00, isApproved)
+      log3(0x00, 0x20, _APPROVAL_FOR_ALL_EVENT_SIGNATURE, by, operator)
+    }
+  }
+
+  /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
+  /*                INTERNAL TRANSFER FUNCTIONS                 */
+  /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
+
+  /// @dev Equivalent to `_transfer(address(0), from, to, id)`.
+  function _transfer(address from, address to, uint256 id) internal virtual {
+    _transfer(address(0), from, to, id);
+  }
+
+  /// @dev Transfers token `id` from `from` to `to`.
+  ///
+  /// Requirements:
+  ///
+  /// - Token `id` must exist.
+  /// - `from` must be the owner of the token.
+  /// - `to` cannot be the zero address.
+  /// - If `by` is not the zero address,
+  ///   it must be the owner of the token, or be approved to manage the token.
+  ///
+  /// Emits a {Transfer} event.
+  function _transfer(address by, address from, address to, uint256 id) internal virtual {
+    _beforeTokenTransfer(from, to, id);
+    /// @solidity memory-safe-assembly
+    assembly {
+      // Clear the upper 96 bits.
+      let bitmaskAddress := shr(96, not(0))
+      from := and(bitmaskAddress, from)
+      to := and(bitmaskAddress, to)
+      by := and(bitmaskAddress, by)
+      // Load the ownership data.
+      mstore(0x00, id)
+      mstore(0x1c, or(_ERC721_MASTER_SLOT_SEED, by))
+      let ownershipSlot := add(id, add(id, keccak256(0x00, 0x20)))
+      let ownershipPacked := sload(ownershipSlot)
+      let owner := and(bitmaskAddress, ownershipPacked)
+      // Revert if the token does not exist, or if `from` is not the owner.
+      if iszero(mul(owner, eq(owner, from))) {
+        // `TokenDoesNotExist()`, `TransferFromIncorrectOwner()`.
+        mstore(shl(2, iszero(owner)), 0xceea21b6a1148100)
+        revert(0x1c, 0x04)
+      }
+      // Load, check, and update the token approval.
+      {
+        mstore(0x00, from)
+        let approvedAddress := sload(add(1, ownershipSlot))
+        // If `by` is not the zero address, do the authorization check.
+        // Revert if the `by` is not the owner, nor approved.
+        if iszero(or(iszero(by), or(eq(by, from), eq(by, approvedAddress)))) {
+          if iszero(sload(keccak256(0x0c, 0x30))) {
+            mstore(0x00, 0x4b6e7f18) // `NotOwnerNorApproved()`.
+            revert(0x1c, 0x04)
+          }
         }
-    }
-
-    /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
-    /*                  INTERNAL MINT FUNCTIONS                   */
-    /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
-
-    /// @dev Mints token `id` to `to`.
-    ///
-    /// Requirements:
-    ///
-    /// - Token `id` must not exist.
-    /// - `to` cannot be the zero address.
-    ///
-    /// Emits a {Transfer} event.
-    function _mint(address to, uint256 id) internal virtual {
-        _beforeTokenTransfer(address(0), to, id);
-        /// @solidity memory-safe-assembly
-        assembly {
-            // Clear the upper 96 bits.
-            to := shr(96, shl(96, to))
-            // Load the ownership data.
-            mstore(0x00, id)
-            mstore(0x1c, _ERC721_MASTER_SLOT_SEED)
-            let ownershipSlot := add(id, add(id, keccak256(0x00, 0x20)))
-            let ownershipPacked := sload(ownershipSlot)
-            // Revert if the token already exists.
-            if shl(96, ownershipPacked) {
-                mstore(0x00, 0xc991cbb1) // `TokenAlreadyExists()`.
-                revert(0x1c, 0x04)
-            }
-            // Update with the owner.
-            sstore(ownershipSlot, or(ownershipPacked, to))
-            // Increment the balance of the owner.
-            {
-                mstore(0x00, to)
-                let balanceSlot := keccak256(0x0c, 0x1c)
-                let balanceSlotPacked := add(sload(balanceSlot), 1)
-                // Revert if `to` is the zero address, or if the account balance overflows.
-                if iszero(mul(to, and(balanceSlotPacked, _MAX_ACCOUNT_BALANCE))) {
-                    // `TransferToZeroAddress()`, `AccountBalanceOverflow()`.
-                    mstore(shl(2, iszero(to)), 0xea553b3401336cea)
-                    revert(0x1c, 0x04)
-                }
-                sstore(balanceSlot, balanceSlotPacked)
-            }
-            // Emit the {Transfer} event.
-            log4(codesize(), 0x00, _TRANSFER_EVENT_SIGNATURE, 0, to, id)
+        // Delete the approved address if any.
+        if approvedAddress {
+          sstore(add(1, ownershipSlot), 0)
         }
-        _afterTokenTransfer(address(0), to, id);
-    }
-
-    /// @dev Mints token `id` to `to`, and updates the extra data for token `id` to `value`.
-    /// Does NOT check if token `id` already exists (assumes `id` is auto-incrementing).
-    ///
-    /// Requirements:
-    ///
-    /// - `to` cannot be the zero address.
-    ///
-    /// Emits a {Transfer} event.
-    function _mintAndSetExtraDataUnchecked(address to, uint256 id, uint96 value) internal virtual {
-        _beforeTokenTransfer(address(0), to, id);
-        /// @solidity memory-safe-assembly
-        assembly {
-            // Clear the upper 96 bits.
-            to := shr(96, shl(96, to))
-            // Update with the owner and extra data.
-            mstore(0x00, id)
-            mstore(0x1c, _ERC721_MASTER_SLOT_SEED)
-            sstore(add(id, add(id, keccak256(0x00, 0x20))), or(shl(160, value), to))
-            // Increment the balance of the owner.
-            {
-                mstore(0x00, to)
-                let balanceSlot := keccak256(0x0c, 0x1c)
-                let balanceSlotPacked := add(sload(balanceSlot), 1)
-                // Revert if `to` is the zero address, or if the account balance overflows.
-                if iszero(mul(to, and(balanceSlotPacked, _MAX_ACCOUNT_BALANCE))) {
-                    // `TransferToZeroAddress()`, `AccountBalanceOverflow()`.
-                    mstore(shl(2, iszero(to)), 0xea553b3401336cea)
-                    revert(0x1c, 0x04)
-                }
-                sstore(balanceSlot, balanceSlotPacked)
-            }
-            // Emit the {Transfer} event.
-            log4(codesize(), 0x00, _TRANSFER_EVENT_SIGNATURE, 0, to, id)
+      }
+      // Update with the new owner.
+      sstore(ownershipSlot, xor(ownershipPacked, xor(from, to)))
+      // Decrement the balance of `from`.
+      {
+        let fromBalanceSlot := keccak256(0x0c, 0x1c)
+        sstore(fromBalanceSlot, sub(sload(fromBalanceSlot), 1))
+      }
+      // Increment the balance of `to`.
+      {
+        mstore(0x00, to)
+        let toBalanceSlot := keccak256(0x0c, 0x1c)
+        let toBalanceSlotPacked := add(sload(toBalanceSlot), 1)
+        // Revert if `to` is the zero address, or if the account balance overflows.
+        if iszero(mul(to, and(toBalanceSlotPacked, _MAX_ACCOUNT_BALANCE))) {
+          // `TransferToZeroAddress()`, `AccountBalanceOverflow()`.
+          mstore(shl(2, iszero(to)), 0xea553b3401336cea)
+          revert(0x1c, 0x04)
         }
-        _afterTokenTransfer(address(0), to, id);
-    }
-
-    /// @dev Equivalent to `_safeMint(to, id, "")`.
-    function _safeMint(address to, uint256 id) internal virtual {
-        _safeMint(to, id, "");
-    }
-
-    /// @dev Mints token `id` to `to`.
-    ///
-    /// Requirements:
-    ///
-    /// - Token `id` must not exist.
-    /// - `to` cannot be the zero address.
-    /// - If `to` refers to a smart contract, it must implement
-    ///   {IERC721Receiver-onERC721Received}, which is called upon a safe transfer.
-    ///
-    /// Emits a {Transfer} event.
-    function _safeMint(address to, uint256 id, bytes memory data) internal virtual {
-        _mint(to, id);
-        if (_hasCode(to)) _checkOnERC721Received(address(0), to, id, data);
-    }
-
-    /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
-    /*                  INTERNAL BURN FUNCTIONS                   */
-    /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
-
-    /// @dev Equivalent to `_burn(address(0), id)`.
-    function _burn(uint256 id) internal virtual {
-        _burn(address(0), id);
-    }
-
-    /// @dev Destroys token `id`, using `by`.
-    ///
-    /// Requirements:
-    ///
-    /// - Token `id` must exist.
-    /// - If `by` is not the zero address,
-    ///   it must be the owner of the token, or be approved to manage the token.
-    ///
-    /// Emits a {Transfer} event.
-    function _burn(address by, uint256 id) internal virtual {
-        address owner = ownerOf(id);
-        _beforeTokenTransfer(owner, address(0), id);
-        /// @solidity memory-safe-assembly
-        assembly {
-            // Clear the upper 96 bits.
-            by := shr(96, shl(96, by))
-            // Load the ownership data.
-            mstore(0x00, id)
-            mstore(0x1c, or(_ERC721_MASTER_SLOT_SEED, by))
-            let ownershipSlot := add(id, add(id, keccak256(0x00, 0x20)))
-            let ownershipPacked := sload(ownershipSlot)
-            // Reload the owner in case it is changed in `_beforeTokenTransfer`.
-            owner := shr(96, shl(96, ownershipPacked))
-            // Revert if the token does not exist.
-            if iszero(owner) {
-                mstore(0x00, 0xceea21b6) // `TokenDoesNotExist()`.
-                revert(0x1c, 0x04)
-            }
-            // Load and check the token approval.
-            {
-                mstore(0x00, owner)
-                let approvedAddress := sload(add(1, ownershipSlot))
-                // If `by` is not the zero address, do the authorization check.
-                // Revert if the `by` is not the owner, nor approved.
-                if iszero(or(iszero(by), or(eq(by, owner), eq(by, approvedAddress)))) {
-                    if iszero(sload(keccak256(0x0c, 0x30))) {
-                        mstore(0x00, 0x4b6e7f18) // `NotOwnerNorApproved()`.
-                        revert(0x1c, 0x04)
-                    }
-                }
-                // Delete the approved address if any.
-                if approvedAddress { sstore(add(1, ownershipSlot), 0) }
-            }
-            // Clear the owner.
-            sstore(ownershipSlot, xor(ownershipPacked, owner))
-            // Decrement the balance of `owner`.
-            {
-                let balanceSlot := keccak256(0x0c, 0x1c)
-                sstore(balanceSlot, sub(sload(balanceSlot), 1))
-            }
-            // Emit the {Transfer} event.
-            log4(codesize(), 0x00, _TRANSFER_EVENT_SIGNATURE, owner, 0, id)
+        sstore(toBalanceSlot, toBalanceSlotPacked)
+      }
+      // Emit the {Transfer} event.
+      log4(codesize(), 0x00, _TRANSFER_EVENT_SIGNATURE, from, to, id)
+    }
+    _afterTokenTransfer(from, to, id);
+  }
+
+  /// @dev Equivalent to `_safeTransfer(from, to, id, "")`.
+  function _safeTransfer(address from, address to, uint256 id) internal virtual {
+    _safeTransfer(from, to, id, '');
+  }
+
+  /// @dev Transfers token `id` from `from` to `to`.
+  ///
+  /// Requirements:
+  ///
+  /// - Token `id` must exist.
+  /// - `from` must be the owner of the token.
+  /// - `to` cannot be the zero address.
+  /// - The caller must be the owner of the token, or be approved to manage the token.
+  /// - If `to` refers to a smart contract, it must implement
+  ///   {IERC721Receiver-onERC721Received}, which is called upon a safe transfer.
+  ///
+  /// Emits a {Transfer} event.
+  function _safeTransfer(address from, address to, uint256 id, bytes memory data) internal virtual {
+    _transfer(address(0), from, to, id);
+    if (_hasCode(to)) _checkOnERC721Received(from, to, id, data);
+  }
+
+  /// @dev Equivalent to `_safeTransfer(by, from, to, id, "")`.
+  function _safeTransfer(address by, address from, address to, uint256 id) internal virtual {
+    _safeTransfer(by, from, to, id, '');
+  }
+
+  /// @dev Transfers token `id` from `from` to `to`.
+  ///
+  /// Requirements:
+  ///
+  /// - Token `id` must exist.
+  /// - `from` must be the owner of the token.
+  /// - `to` cannot be the zero address.
+  /// - If `by` is not the zero address,
+  ///   it must be the owner of the token, or be approved to manage the token.
+  /// - If `to` refers to a smart contract, it must implement
+  ///   {IERC721Receiver-onERC721Received}, which is called upon a safe transfer.
+  ///
+  /// Emits a {Transfer} event.
+  function _safeTransfer(
+    address by,
+    address from,
+    address to,
+    uint256 id,
+    bytes memory data
+  ) internal virtual {
+    _transfer(by, from, to, id);
+    if (_hasCode(to)) _checkOnERC721Received(from, to, id, data);
+  }
+
+  /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
+  /*                    HOOKS FOR OVERRIDING                    */
+  /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
+
+  /// @dev Hook that is called before any token transfers, including minting and burning.
+  function _beforeTokenTransfer(address from, address to, uint256 id) internal virtual {}
+
+  /// @dev Hook that is called after any token transfers, including minting and burning.
+  function _afterTokenTransfer(address from, address to, uint256 id) internal virtual {}
+
+  /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
+  /*                      PRIVATE HELPERS                       */
+  /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
+
+  /// @dev Returns if `a` has bytecode of non-zero length.
+  function _hasCode(address a) private view returns (bool result) {
+    /// @solidity memory-safe-assembly
+    assembly {
+      result := extcodesize(a) // Can handle dirty upper bits.
+    }
+  }
+
+  /// @dev Perform a call to invoke {IERC721Receiver-onERC721Received} on `to`.
+  /// Reverts if the target does not support the function correctly.
+  function _checkOnERC721Received(address from, address to, uint256 id, bytes memory data) private {
+    /// @solidity memory-safe-assembly
+    assembly {
+      // Prepare the calldata.
+      let m := mload(0x40)
+      let onERC721ReceivedSelector := 0x150b7a02
+      mstore(m, onERC721ReceivedSelector)
+      mstore(add(m, 0x20), caller()) // The `operator`, which is always `msg.sender`.
+      mstore(add(m, 0x40), shr(96, shl(96, from)))
+      mstore(add(m, 0x60), id)
+      mstore(add(m, 0x80), 0x80)
+      let n := mload(data)
+      mstore(add(m, 0xa0), n)
+      if n {
+        pop(staticcall(gas(), 4, add(data, 0x20), n, add(m, 0xc0), n))
+      }
+      // Revert if the call reverts.
+      if iszero(call(gas(), to, 0, add(m, 0x1c), add(n, 0xa4), m, 0x20)) {
+        if returndatasize() {
+          // Bubble up the revert if the call reverts.
+          returndatacopy(m, 0x00, returndatasize())
+          revert(m, returndatasize())
         }
-        _afterTokenTransfer(owner, address(0), id);
-    }
-
-    /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
-    /*                INTERNAL APPROVAL FUNCTIONS                 */
-    /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
-
-    /// @dev Returns whether `account` is the owner of token `id`, or is approved to manage it.
-    ///
-    /// Requirements:
-    /// - Token `id` must exist.
-    function _isApprovedOrOwner(address account, uint256 id)
-        internal
-        view
-        virtual
-        returns (bool result)
-    {
-        /// @solidity memory-safe-assembly
-        assembly {
-            result := 1
-            // Clear the upper 96 bits.
-            account := shr(96, shl(96, account))
-            // Load the ownership data.
-            mstore(0x00, id)
-            mstore(0x1c, or(_ERC721_MASTER_SLOT_SEED, account))
-            let ownershipSlot := add(id, add(id, keccak256(0x00, 0x20)))
-            let owner := shr(96, shl(96, sload(ownershipSlot)))
-            // Revert if the token does not exist.
-            if iszero(owner) {
-                mstore(0x00, 0xceea21b6) // `TokenDoesNotExist()`.
-                revert(0x1c, 0x04)
-            }
-            // Check if `account` is the `owner`.
-            if iszero(eq(account, owner)) {
-                mstore(0x00, owner)
-                // Check if `account` is approved to manage the token.
-                if iszero(sload(keccak256(0x0c, 0x30))) {
-                    result := eq(account, sload(add(1, ownershipSlot)))
-                }
-            }
-        }
-    }
-
-    /// @dev Returns the account approved to manage token `id`.
-    /// Returns the zero address instead of reverting if the token does not exist.
-    function _getApproved(uint256 id) internal view virtual returns (address result) {
-        /// @solidity memory-safe-assembly
-        assembly {
-            mstore(0x00, id)
-            mstore(0x1c, _ERC721_MASTER_SLOT_SEED)
-            result := sload(add(1, add(id, add(id, keccak256(0x00, 0x20)))))
-        }
-    }
-
-    /// @dev Equivalent to `_approve(address(0), account, id)`.
-    function _approve(address account, uint256 id) internal virtual {
-        _approve(address(0), account, id);
-    }
-
-    /// @dev Sets `account` as the approved account to manage token `id`, using `by`.
-    ///
-    /// Requirements:
-    /// - Token `id` must exist.
-    /// - If `by` is not the zero address, `by` must be the owner
-    ///   or an approved operator for the token owner.
-    ///
-    /// Emits a {Approval} event.
-    function _approve(address by, address account, uint256 id) internal virtual {
-        assembly {
-            // Clear the upper 96 bits.
-            let bitmaskAddress := shr(96, not(0))
-            account := and(bitmaskAddress, account)
-            by := and(bitmaskAddress, by)
-            // Load the owner of the token.
-            mstore(0x00, id)
-            mstore(0x1c, or(_ERC721_MASTER_SLOT_SEED, by))
-            let ownershipSlot := add(id, add(id, keccak256(0x00, 0x20)))
-            let owner := and(bitmaskAddress, sload(ownershipSlot))
-            // Revert if the token does not exist.
-            if iszero(owner) {
-                mstore(0x00, 0xceea21b6) // `TokenDoesNotExist()`.
-                revert(0x1c, 0x04)
-            }
-            // If `by` is not the zero address, do the authorization check.
-            // Revert if `by` is not the owner, nor approved.
-            if iszero(or(iszero(by), eq(by, owner))) {
-                mstore(0x00, owner)
-                if iszero(sload(keccak256(0x0c, 0x30))) {
-                    mstore(0x00, 0x4b6e7f18) // `NotOwnerNorApproved()`.
-                    revert(0x1c, 0x04)
-                }
-            }
-            // Sets `account` as the approved account to manage `id`.
-            sstore(add(1, ownershipSlot), account)
-            // Emit the {Approval} event.
-            log4(codesize(), 0x00, _APPROVAL_EVENT_SIGNATURE, owner, account, id)
-        }
-    }
-
-    /// @dev Approve or remove the `operator` as an operator for `by`,
-    /// without authorization checks.
-    ///
-    /// Emits an {ApprovalForAll} event.
-    function _setApprovalForAll(address by, address operator, bool isApproved) internal virtual {
-        /// @solidity memory-safe-assembly
-        assembly {
-            // Clear the upper 96 bits.
-            by := shr(96, shl(96, by))
-            operator := shr(96, shl(96, operator))
-            // Convert to 0 or 1.
-            isApproved := iszero(iszero(isApproved))
-            // Update the `isApproved` for (`by`, `operator`).
-            mstore(0x1c, or(_ERC721_MASTER_SLOT_SEED, operator))
-            mstore(0x00, by)
-            sstore(keccak256(0x0c, 0x30), isApproved)
-            // Emit the {ApprovalForAll} event.
-            mstore(0x00, isApproved)
-            log3(0x00, 0x20, _APPROVAL_FOR_ALL_EVENT_SIGNATURE, by, operator)
-        }
-    }
-
-    /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
-    /*                INTERNAL TRANSFER FUNCTIONS                 */
-    /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
-
-    /// @dev Equivalent to `_transfer(address(0), from, to, id)`.
-    function _transfer(address from, address to, uint256 id) internal virtual {
-        _transfer(address(0), from, to, id);
-    }
-
-    /// @dev Transfers token `id` from `from` to `to`.
-    ///
-    /// Requirements:
-    ///
-    /// - Token `id` must exist.
-    /// - `from` must be the owner of the token.
-    /// - `to` cannot be the zero address.
-    /// - If `by` is not the zero address,
-    ///   it must be the owner of the token, or be approved to manage the token.
-    ///
-    /// Emits a {Transfer} event.
-    function _transfer(address by, address from, address to, uint256 id) internal virtual {
-        _beforeTokenTransfer(from, to, id);
-        /// @solidity memory-safe-assembly
-        assembly {
-            // Clear the upper 96 bits.
-            let bitmaskAddress := shr(96, not(0))
-            from := and(bitmaskAddress, from)
-            to := and(bitmaskAddress, to)
-            by := and(bitmaskAddress, by)
-            // Load the ownership data.
-            mstore(0x00, id)
-            mstore(0x1c, or(_ERC721_MASTER_SLOT_SEED, by))
-            let ownershipSlot := add(id, add(id, keccak256(0x00, 0x20)))
-            let ownershipPacked := sload(ownershipSlot)
-            let owner := and(bitmaskAddress, ownershipPacked)
-            // Revert if the token does not exist, or if `from` is not the owner.
-            if iszero(mul(owner, eq(owner, from))) {
-                // `TokenDoesNotExist()`, `TransferFromIncorrectOwner()`.
-                mstore(shl(2, iszero(owner)), 0xceea21b6a1148100)
-                revert(0x1c, 0x04)
-            }
-            // Load, check, and update the token approval.
-            {
-                mstore(0x00, from)
-                let approvedAddress := sload(add(1, ownershipSlot))
-                // If `by` is not the zero address, do the authorization check.
-                // Revert if the `by` is not the owner, nor approved.
-                if iszero(or(iszero(by), or(eq(by, from), eq(by, approvedAddress)))) {
-                    if iszero(sload(keccak256(0x0c, 0x30))) {
-                        mstore(0x00, 0x4b6e7f18) // `NotOwnerNorApproved()`.
-                        revert(0x1c, 0x04)
-                    }
-                }
-                // Delete the approved address if any.
-                if approvedAddress { sstore(add(1, ownershipSlot), 0) }
-            }
-            // Update with the new owner.
-            sstore(ownershipSlot, xor(ownershipPacked, xor(from, to)))
-            // Decrement the balance of `from`.
-            {
-                let fromBalanceSlot := keccak256(0x0c, 0x1c)
-                sstore(fromBalanceSlot, sub(sload(fromBalanceSlot), 1))
-            }
-            // Increment the balance of `to`.
-            {
-                mstore(0x00, to)
-                let toBalanceSlot := keccak256(0x0c, 0x1c)
-                let toBalanceSlotPacked := add(sload(toBalanceSlot), 1)
-                // Revert if `to` is the zero address, or if the account balance overflows.
-                if iszero(mul(to, and(toBalanceSlotPacked, _MAX_ACCOUNT_BALANCE))) {
-                    // `TransferToZeroAddress()`, `AccountBalanceOverflow()`.
-                    mstore(shl(2, iszero(to)), 0xea553b3401336cea)
-                    revert(0x1c, 0x04)
-                }
-                sstore(toBalanceSlot, toBalanceSlotPacked)
-            }
-            // Emit the {Transfer} event.
-            log4(codesize(), 0x00, _TRANSFER_EVENT_SIGNATURE, from, to, id)
-        }
-        _afterTokenTransfer(from, to, id);
-    }
-
-    /// @dev Equivalent to `_safeTransfer(from, to, id, "")`.
-    function _safeTransfer(address from, address to, uint256 id) internal virtual {
-        _safeTransfer(from, to, id, "");
-    }
-
-    /// @dev Transfers token `id` from `from` to `to`.
-    ///
-    /// Requirements:
-    ///
-    /// - Token `id` must exist.
-    /// - `from` must be the owner of the token.
-    /// - `to` cannot be the zero address.
-    /// - The caller must be the owner of the token, or be approved to manage the token.
-    /// - If `to` refers to a smart contract, it must implement
-    ///   {IERC721Receiver-onERC721Received}, which is called upon a safe transfer.
-    ///
-    /// Emits a {Transfer} event.
-    function _safeTransfer(address from, address to, uint256 id, bytes memory data)
-        internal
-        virtual
-    {
-        _transfer(address(0), from, to, id);
-        if (_hasCode(to)) _checkOnERC721Received(from, to, id, data);
-    }
-
-    /// @dev Equivalent to `_safeTransfer(by, from, to, id, "")`.
-    function _safeTransfer(address by, address from, address to, uint256 id) internal virtual {
-        _safeTransfer(by, from, to, id, "");
-    }
-
-    /// @dev Transfers token `id` from `from` to `to`.
-    ///
-    /// Requirements:
-    ///
-    /// - Token `id` must exist.
-    /// - `from` must be the owner of the token.
-    /// - `to` cannot be the zero address.
-    /// - If `by` is not the zero address,
-    ///   it must be the owner of the token, or be approved to manage the token.
-    /// - If `to` refers to a smart contract, it must implement
-    ///   {IERC721Receiver-onERC721Received}, which is called upon a safe transfer.
-    ///
-    /// Emits a {Transfer} event.
-    function _safeTransfer(address by, address from, address to, uint256 id, bytes memory data)
-        internal
-        virtual
-    {
-        _transfer(by, from, to, id);
-        if (_hasCode(to)) _checkOnERC721Received(from, to, id, data);
-    }
-
-    /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
-    /*                    HOOKS FOR OVERRIDING                    */
-    /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
-
-    /// @dev Hook that is called before any token transfers, including minting and burning.
-    function _beforeTokenTransfer(address from, address to, uint256 id) internal virtual {}
-
-    /// @dev Hook that is called after any token transfers, including minting and burning.
-    function _afterTokenTransfer(address from, address to, uint256 id) internal virtual {}
-
-    /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
-    /*                      PRIVATE HELPERS                       */
-    /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
-
-    /// @dev Returns if `a` has bytecode of non-zero length.
-    function _hasCode(address a) private view returns (bool result) {
-        /// @solidity memory-safe-assembly
-        assembly {
-            result := extcodesize(a) // Can handle dirty upper bits.
-        }
-    }
-
-    /// @dev Perform a call to invoke {IERC721Receiver-onERC721Received} on `to`.
-    /// Reverts if the target does not support the function correctly.
-    function _checkOnERC721Received(address from, address to, uint256 id, bytes memory data)
-        private
-    {
-        /// @solidity memory-safe-assembly
-        assembly {
-            // Prepare the calldata.
-            let m := mload(0x40)
-            let onERC721ReceivedSelector := 0x150b7a02
-            mstore(m, onERC721ReceivedSelector)
-            mstore(add(m, 0x20), caller()) // The `operator`, which is always `msg.sender`.
-            mstore(add(m, 0x40), shr(96, shl(96, from)))
-            mstore(add(m, 0x60), id)
-            mstore(add(m, 0x80), 0x80)
-            let n := mload(data)
-            mstore(add(m, 0xa0), n)
-            if n { pop(staticcall(gas(), 4, add(data, 0x20), n, add(m, 0xc0), n)) }
-            // Revert if the call reverts.
-            if iszero(call(gas(), to, 0, add(m, 0x1c), add(n, 0xa4), m, 0x20)) {
-                if returndatasize() {
-                    // Bubble up the revert if the call reverts.
-                    returndatacopy(m, 0x00, returndatasize())
-                    revert(m, returndatasize())
-                }
-            }
-            // Load the returndata and compare it.
-            if iszero(eq(mload(m), shl(224, onERC721ReceivedSelector))) {
-                mstore(0x00, 0xd1a57ed6) // `TransferToNonERC721ReceiverImplementer()`.
-                revert(0x1c, 0x04)
-            }
-        }
-    }
+      }
+      // Load the returndata and compare it.
+      if iszero(eq(mload(m), shl(224, onERC721ReceivedSelector))) {
+        mstore(0x00, 0xd1a57ed6) // `TransferToNonERC721ReceiverImplementer()`.
+        revert(0x1c, 0x04)
+      }
+    }
+  }
 }
 
-
 // a minimal implementation of soladys ERC721 with a mint function
 contract MockERC721 is ERC721 {
-    function mint(address to, uint256 tokenUri) external {
-        _mint(to, tokenUri);
-    }
-
-    function name() public view override returns (string memory) {
-        return "";
-    }
-
-    /// @dev Returns the token collection symbol.
-    function symbol() public view override returns (string memory) {
-        return "";
-    }
-
-    /// @dev Returns the Uniform Resource Identifier (URI) for token `id`.
-    function tokenURI(uint256 id) public view override returns (string memory) {
-        return "";
-    }
-
-}
\ No newline at end of file
+  function mint(address to, uint256 tokenUri) external {
+    _mint(to, tokenUri);
+  }
+
+  function name() public pure override returns (string memory) {
+    return '';
+  }
+
+  /// @dev Returns the token collection symbol.
+  function symbol() public pure override returns (string memory) {
+    return '';
+  }
+
+  /// @dev Returns the Uniform Resource Identifier (URI) for token `id`.
+  function tokenURI(uint256) public pure override returns (string memory) {
+    return '';
+  }
+}
diff --git a/test/TransparentProxyFactory.t.sol b/test/TransparentProxyFactory.t.sol
index c679b5d..0cb2ef6 100644
--- a/test/TransparentProxyFactory.t.sol
+++ b/test/TransparentProxyFactory.t.sol
@@ -17,6 +17,24 @@ contract TestTransparentProxyFactory is Test {
     mockImpl = new MockImpl();
   }
 
+  function test_createProxy() external {
+    address owner = makeAddr('admin');
+    uint256 FOO = 2;
+    bytes memory data = abi.encodeWithSelector(mockImpl.initialize.selector, FOO);
+    {
+      address proxy = factory.create(address(mockImpl), owner, data);
+
+      address proxyAdmin = factory.getProxyAdmin(proxy);
+      assertEq(ProxyAdmin(proxyAdmin).owner(), owner);
+    }
+    {
+      address proxy = factory.create(address(mockImpl), owner, data);
+
+      address proxyAdmin = factory.getProxyAdmin(proxy);
+      assertEq(ProxyAdmin(proxyAdmin).owner(), owner);
+    }
+  }
+
   function testCreateDeterministic(address admin, bytes32 salt) public {
     // we know that this is covered at the ERC1967Upgrade
     vm.assume(admin != address(0) && admin != address(this));
@@ -42,10 +60,7 @@ contract TestTransparentProxyFactory is Test {
     bytes32 proxySalt
   ) public {
     address owner = makeAddr('owner');
-    address deterministicProxyAdmin = factory.predictCreateDeterministicProxyAdmin(
-      proxyAdminSalt,
-      owner
-    );
+    factory.predictCreateDeterministicProxyAdmin(proxyAdminSalt, owner);
 
     uint256 FOO = 2;
 

From f8e93d5f13932130af813f5bf00908209b5779b3 Mon Sep 17 00:00:00 2001
From: Lukas <lukasstrassel@googlemail.com>
Date: Mon, 20 Jan 2025 15:34:49 +0100
Subject: [PATCH 4/8] fix: patch tests

---
 test/TransparentProxyFactory.t.sol | 13 ++++++-------
 1 file changed, 6 insertions(+), 7 deletions(-)

diff --git a/test/TransparentProxyFactory.t.sol b/test/TransparentProxyFactory.t.sol
index 0cb2ef6..ddf3d0d 100644
--- a/test/TransparentProxyFactory.t.sol
+++ b/test/TransparentProxyFactory.t.sol
@@ -35,24 +35,26 @@ contract TestTransparentProxyFactory is Test {
     }
   }
 
-  function testCreateDeterministic(address admin, bytes32 salt) public {
+  function test_createDeterministicProxy(address initialOwner, bytes32 salt) public {
     // we know that this is covered at the ERC1967Upgrade
-    vm.assume(admin != address(0) && admin != address(this));
+    vm.assume(initialOwner != address(0) && initialOwner != address(this));
 
     uint256 FOO = 2;
     bytes memory data = abi.encodeWithSelector(mockImpl.initialize.selector, FOO);
 
     address predictedAddress1 = factory.predictCreateDeterministic(
       address(mockImpl),
-      admin,
+      initialOwner,
       data,
       salt
     );
 
-    address proxy1 = factory.createDeterministic(address(mockImpl), admin, data, salt);
+    address proxy1 = factory.createDeterministic(address(mockImpl), initialOwner, data, salt);
 
     assertEq(predictedAddress1, proxy1);
     assertEq(MockImpl(proxy1).getFoo(), FOO);
+    address proxyAdmin = factory.getProxyAdmin(proxy1);
+    assertEq(ProxyAdmin(proxyAdmin).owner(), initialOwner);
   }
 
   function testCreateDeterministicWithDeterministicProxy(
@@ -60,10 +62,7 @@ contract TestTransparentProxyFactory is Test {
     bytes32 proxySalt
   ) public {
     address owner = makeAddr('owner');
-    factory.predictCreateDeterministicProxyAdmin(proxyAdminSalt, owner);
-
     uint256 FOO = 2;
-
     bytes memory data = abi.encodeWithSelector(mockImpl.initialize.selector, FOO);
 
     address predictedAddress1 = factory.predictCreateDeterministic(

From d92ccc9d6245f0333bdfa97d16ea78cc044e8ed0 Mon Sep 17 00:00:00 2001
From: Lukas <lukasstrassel@googlemail.com>
Date: Mon, 20 Jan 2025 15:36:06 +0100
Subject: [PATCH 5/8] fix: upgrade oz

---
 .gitmodules                            | 2 +-
 lib/forge-std                          | 2 +-
 lib/openzeppelin-contracts-upgradeable | 2 +-
 3 files changed, 3 insertions(+), 3 deletions(-)

diff --git a/.gitmodules b/.gitmodules
index 98436a2..281458f 100644
--- a/.gitmodules
+++ b/.gitmodules
@@ -4,4 +4,4 @@
 [submodule "lib/openzeppelin-contracts-upgradeable"]
 	path = lib/openzeppelin-contracts-upgradeable
 	url = https://github.com/OpenZeppelin/openzeppelin-contracts-upgradeable
-  branch = release-v5.0
+  branch = release-v5.1
diff --git a/lib/forge-std b/lib/forge-std
index 0e70977..726a6ee 160000
--- a/lib/forge-std
+++ b/lib/forge-std
@@ -1 +1 @@
-Subproject commit 0e7097750918380d84dd3cfdef595bee74dabb70
+Subproject commit 726a6ee5fc8427a0013d6f624e486c9130c0e336
diff --git a/lib/openzeppelin-contracts-upgradeable b/lib/openzeppelin-contracts-upgradeable
index 723f8ca..fa52531 160000
--- a/lib/openzeppelin-contracts-upgradeable
+++ b/lib/openzeppelin-contracts-upgradeable
@@ -1 +1 @@
-Subproject commit 723f8cab09cdae1aca9ec9cc1cfa040c2d4b06c1
+Subproject commit fa525310e45f91eb20a6d3baa2644be8e0adba31

From e702cdcefa078e603cde4bd695615d64740fbb94 Mon Sep 17 00:00:00 2001
From: Lukas <lukasstrassel@googlemail.com>
Date: Mon, 20 Jan 2025 15:41:42 +0100
Subject: [PATCH 6/8] fix: add comment

---
 .../transparent-proxy/TransparentProxyFactoryBase.sol         | 4 ++++
 1 file changed, 4 insertions(+)

diff --git a/src/contracts/transparent-proxy/TransparentProxyFactoryBase.sol b/src/contracts/transparent-proxy/TransparentProxyFactoryBase.sol
index 17778c7..a671851 100644
--- a/src/contracts/transparent-proxy/TransparentProxyFactoryBase.sol
+++ b/src/contracts/transparent-proxy/TransparentProxyFactoryBase.sol
@@ -108,6 +108,10 @@ abstract contract TransparentProxyFactoryBase is ITransparentProxyFactory {
     _proxyToAdmin[proxy] = _predictCreate1Address(proxy);
   }
 
+  /**
+   * @dev the prediction only depends on the address of the proxy.
+   * The admin is always the first and only contract deployed by the proxy.
+   */
   function _predictCreate1Address(address proxy) internal virtual returns (address) {
     return
       address(

From 8af7fd7cda9772f13595ff199fa4cc8d02df7f07 Mon Sep 17 00:00:00 2001
From: Lukas <lukasstrassel@googlemail.com>
Date: Wed, 22 Jan 2025 09:35:01 +0100
Subject: [PATCH 7/8] fix: patch also on zksync

---
 .../TransparentProxyFactoryBase.sol           |  4 ++--
 .../TransparentProxyFactoryZkSync.sol         | 10 ++++++++
 .../test/TransparentProxyFactoryZkSync.t.sol  | 23 ++++++++++++++++---
 3 files changed, 32 insertions(+), 5 deletions(-)

diff --git a/src/contracts/transparent-proxy/TransparentProxyFactoryBase.sol b/src/contracts/transparent-proxy/TransparentProxyFactoryBase.sol
index a671851..b9c15bc 100644
--- a/src/contracts/transparent-proxy/TransparentProxyFactoryBase.sol
+++ b/src/contracts/transparent-proxy/TransparentProxyFactoryBase.sol
@@ -105,14 +105,14 @@ abstract contract TransparentProxyFactoryBase is ITransparentProxyFactory {
   ) internal pure virtual returns (address);
 
   function _storeProxyInRegistry(address proxy) internal {
-    _proxyToAdmin[proxy] = _predictCreate1Address(proxy);
+    _proxyToAdmin[proxy] = _predictProxyAdminAddress(proxy);
   }
 
   /**
    * @dev the prediction only depends on the address of the proxy.
    * The admin is always the first and only contract deployed by the proxy.
    */
-  function _predictCreate1Address(address proxy) internal virtual returns (address) {
+  function _predictProxyAdminAddress(address proxy) internal pure virtual returns (address) {
     return
       address(
         uint160(
diff --git a/zksync/src/contracts/transparent-proxy/TransparentProxyFactoryZkSync.sol b/zksync/src/contracts/transparent-proxy/TransparentProxyFactoryZkSync.sol
index 4e31015..6ebfcfc 100644
--- a/zksync/src/contracts/transparent-proxy/TransparentProxyFactoryZkSync.sol
+++ b/zksync/src/contracts/transparent-proxy/TransparentProxyFactoryZkSync.sol
@@ -18,6 +18,7 @@ contract TransparentProxyFactoryZkSync is
 {
   /// @inheritdoc ITransparentProxyFactoryZkSync
   bytes32 public constant ZKSYNC_CREATE2_PREFIX = keccak256('zksyncCreate2');
+  bytes32 public constant ZKSYNC_CREATE_PREFIX = keccak256('zksyncCreate');
 
   function _predictCreate2Address(
     address sender,
@@ -57,4 +58,13 @@ contract TransparentProxyFactoryZkSync is
     }
     return result;
   }
+
+  // on zksync nonces start with 0 https://docs.zksync.io/zksync-protocol/differences/nonces
+  function _predictProxyAdminAddress(address proxy) internal pure override returns (address) {
+    bytes32 addressHash = keccak256(
+      bytes.concat(ZKSYNC_CREATE_PREFIX, bytes32(uint256(uint160(proxy))), bytes32(uint256(0)))
+    );
+
+    return address(uint160(uint256(addressHash)));
+  }
 }
diff --git a/zksync/test/TransparentProxyFactoryZkSync.t.sol b/zksync/test/TransparentProxyFactoryZkSync.t.sol
index ed08916..a31a282 100644
--- a/zksync/test/TransparentProxyFactoryZkSync.t.sol
+++ b/zksync/test/TransparentProxyFactoryZkSync.t.sol
@@ -2,21 +2,38 @@
 pragma solidity ^0.8.24;
 
 import {Test} from 'forge-std/Test.sol';
-import {TransparentProxyFactoryZkSync} from '../src/contracts/transparent-proxy/TransparentProxyFactoryZkSync.sol';
-import {TransparentUpgradeableProxy} from 'openzeppelin-contracts/contracts/proxy/transparent/TransparentUpgradeableProxy.sol';
+import {TransparentUpgradeableProxy, ProxyAdmin} from 'openzeppelin-contracts/contracts/proxy/transparent/TransparentUpgradeableProxy.sol';
 import {Ownable} from 'openzeppelin-contracts/contracts/access/Ownable.sol';
+import {TransparentProxyFactoryZkSync} from '../src/contracts/transparent-proxy/TransparentProxyFactoryZkSync.sol';
 import {MockImpl} from '../../src/mocks/MockImpl.sol';
 
 contract TestTransparentProxyFactoryZkSync is Test {
   TransparentProxyFactoryZkSync internal factory;
   MockImpl internal mockImpl;
-  address internal owner = makeAddr('owner');
+  address internal owner = address(1234);
 
   function setUp() public {
     factory = new TransparentProxyFactoryZkSync();
     mockImpl = new MockImpl();
   }
 
+  function test_createProxy() external {
+    uint256 FOO = 2;
+    bytes memory data = abi.encodeWithSelector(mockImpl.initialize.selector, FOO);
+    {
+      address proxy = factory.create(address(mockImpl), owner, data);
+
+      address proxyAdmin = factory.getProxyAdmin(proxy);
+      assertEq(ProxyAdmin(proxyAdmin).owner(), owner);
+    }
+    {
+      address proxy = factory.create(address(mockImpl), owner, data);
+
+      address proxyAdmin = factory.getProxyAdmin(proxy);
+      assertEq(ProxyAdmin(proxyAdmin).owner(), owner);
+    }
+  }
+
   function testCreate() public {
     uint256 FOO = 2;
     bytes memory data = abi.encodeWithSelector(mockImpl.initialize.selector, FOO);

From 9f7c9b4dd92c90c4dec912bb375b9912a8e1590d Mon Sep 17 00:00:00 2001
From: Lukas <ls@bgdlabs.com>
Date: Fri, 24 Jan 2025 23:08:06 +0100
Subject: [PATCH 8/8] Apply suggestions from code review

Co-authored-by: Pavel <56404416+pavelvm5@users.noreply.github.com>
---
 .../transparent-proxy/TransparentProxyFactoryBase.sol         | 4 ++--
 .../transparent-proxy/interfaces/ITransparentProxyFactory.sol | 1 -
 test/TransparentProxyFactory.t.sol                            | 1 -
 3 files changed, 2 insertions(+), 4 deletions(-)

diff --git a/src/contracts/transparent-proxy/TransparentProxyFactoryBase.sol b/src/contracts/transparent-proxy/TransparentProxyFactoryBase.sol
index b9c15bc..dfc9249 100644
--- a/src/contracts/transparent-proxy/TransparentProxyFactoryBase.sol
+++ b/src/contracts/transparent-proxy/TransparentProxyFactoryBase.sol
@@ -29,7 +29,7 @@ abstract contract TransparentProxyFactoryBase is ITransparentProxyFactory {
     address proxy = address(new TransparentUpgradeableProxy(logic, adminOwner, data));
     _storeProxyInRegistry(proxy);
 
-    emit ProxyCreated(proxy, logic, address(adminOwner));
+    emit ProxyCreated(proxy, logic, adminOwner);
 
     return proxy;
   }
@@ -52,7 +52,7 @@ abstract contract TransparentProxyFactoryBase is ITransparentProxyFactory {
     address proxy = address(new TransparentUpgradeableProxy{salt: salt}(logic, adminOwner, data));
     _storeProxyInRegistry(proxy);
 
-    emit ProxyDeterministicCreated(proxy, logic, address(adminOwner), salt);
+    emit ProxyDeterministicCreated(proxy, logic, adminOwner, salt);
     return proxy;
   }
 
diff --git a/src/contracts/transparent-proxy/interfaces/ITransparentProxyFactory.sol b/src/contracts/transparent-proxy/interfaces/ITransparentProxyFactory.sol
index d3151e3..b58e1bf 100644
--- a/src/contracts/transparent-proxy/interfaces/ITransparentProxyFactory.sol
+++ b/src/contracts/transparent-proxy/interfaces/ITransparentProxyFactory.sol
@@ -1,7 +1,6 @@
 // SPDX-License-Identifier: MIT
 pragma solidity >=0.8.0;
 
-import {ProxyAdmin} from 'openzeppelin-contracts/contracts/proxy/transparent/ProxyAdmin.sol';
 
 interface ITransparentProxyFactory {
   event ProxyCreated(address proxy, address indexed logic, address indexed proxyAdmin);
diff --git a/test/TransparentProxyFactory.t.sol b/test/TransparentProxyFactory.t.sol
index ddf3d0d..addfcc8 100644
--- a/test/TransparentProxyFactory.t.sol
+++ b/test/TransparentProxyFactory.t.sol
@@ -3,7 +3,6 @@ pragma solidity ^0.8.0;
 
 import 'forge-std/Test.sol';
 import {Ownable} from 'openzeppelin-contracts/contracts/access/Ownable.sol';
-import {TransparentUpgradeableProxy} from 'openzeppelin-contracts/contracts/proxy/transparent/TransparentUpgradeableProxy.sol';
 import {ProxyAdmin} from 'openzeppelin-contracts/contracts/proxy/transparent/ProxyAdmin.sol';
 import {TransparentProxyFactory} from '../src/contracts/transparent-proxy/TransparentProxyFactory.sol';
 import {MockImpl} from '../src/mocks/MockImpl.sol';