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';