Hadrien Croubois преди 4 месеца
родител
ревизия
88962fb5ab
променени са 4 файла, в които са добавени 82 реда и са изтрити 3 реда
  1. 5 0
      .changeset/full-ways-help.md
  2. 20 0
      contracts/account/utils/EIP7702Utils.sol
  3. 4 3
      contracts/mocks/Stateless.sol
  4. 53 0
      test/account/utils/EIP7702Utils.test.js

+ 5 - 0
.changeset/full-ways-help.md

@@ -0,0 +1,5 @@
+---
+'openzeppelin-solidity': minor
+---
+
+`EIP7702Utils`: Add a library for checking if an address has an EIP-7702 delegation in place.

+ 20 - 0
contracts/account/utils/EIP7702Utils.sol

@@ -0,0 +1,20 @@
+// SPDX-License-Identifier: MIT
+
+pragma solidity ^0.8.20;
+
+/**
+ * @dev Library with common EIP-7702 utility functions.
+ *
+ * See https://eips.ethereum.org/EIPS/eip-7702[ERC-7702].
+ */
+library EIP7702Utils {
+    bytes3 internal constant EIP7702_PREFIX = 0xef0100;
+
+    /**
+     * @dev Returns the address of the delegate if `account` as an EIP-7702 delegation setup, or address(0) otherwise.
+     */
+    function fetchDelegate(address account) internal view returns (address) {
+        bytes23 delegation = bytes23(account.code);
+        return bytes3(delegation) == EIP7702_PREFIX ? address(bytes20(delegation << 24)) : address(0);
+    }
+}

+ 4 - 3
contracts/mocks/Stateless.sol

@@ -19,13 +19,14 @@ import {Clones} from "../proxy/Clones.sol";
 import {Create2} from "../utils/Create2.sol";
 import {DoubleEndedQueue} from "../utils/structs/DoubleEndedQueue.sol";
 import {ECDSA} from "../utils/cryptography/ECDSA.sol";
+import {EIP7702Utils} from "../account/utils/EIP7702Utils.sol";
 import {EnumerableMap} from "../utils/structs/EnumerableMap.sol";
 import {EnumerableSet} from "../utils/structs/EnumerableSet.sol";
-import {ERC1155Holder} from "../token/ERC1155/utils/ERC1155Holder.sol";
 import {ERC165} from "../utils/introspection/ERC165.sol";
 import {ERC165Checker} from "../utils/introspection/ERC165Checker.sol";
-import {ERC1967Utils} from "../proxy/ERC1967/ERC1967Utils.sol";
 import {ERC721Holder} from "../token/ERC721/utils/ERC721Holder.sol";
+import {ERC1155Holder} from "../token/ERC1155/utils/ERC1155Holder.sol";
+import {ERC1967Utils} from "../proxy/ERC1967/ERC1967Utils.sol";
 import {ERC4337Utils} from "../account/utils/draft-ERC4337Utils.sol";
 import {ERC7579Utils} from "../account/utils/draft-ERC7579Utils.sol";
 import {Heap} from "../utils/structs/Heap.sol";
@@ -35,8 +36,8 @@ import {MessageHashUtils} from "../utils/cryptography/MessageHashUtils.sol";
 import {Nonces} from "../utils/Nonces.sol";
 import {NoncesKeyed} from "../utils/NoncesKeyed.sol";
 import {P256} from "../utils/cryptography/P256.sol";
-import {Panic} from "../utils/Panic.sol";
 import {Packing} from "../utils/Packing.sol";
+import {Panic} from "../utils/Panic.sol";
 import {RSA} from "../utils/cryptography/RSA.sol";
 import {SafeCast} from "../utils/math/SafeCast.sol";
 import {SafeERC20} from "../token/ERC20/utils/SafeERC20.sol";

+ 53 - 0
test/account/utils/EIP7702Utils.test.js

@@ -0,0 +1,53 @@
+const { ethers, config } = require('hardhat');
+const { expect } = require('chai');
+const { loadFixture } = require('@nomicfoundation/hardhat-network-helpers');
+
+// [NOTE]
+//
+// ethers.getSigners() returns object than cannot currently send type-4 transaction, or sign authorization. Therefore,
+// we have to instantiate the eoa AND the relayer manually using ethers 6.14.0 wallets. This can be improved when
+// @nomicfoundation/hardhat-ethers starts instantiating signers with 7702 support.
+const relayAuthorization = authorization =>
+  ethers.Wallet.fromPhrase(config.networks.hardhat.accounts.mnemonic, ethers.provider).sendTransaction({
+    to: ethers.ZeroAddress,
+    authorizationList: [authorization],
+    gasLimit: 46_000n,
+  });
+
+const fixture = async () => {
+  const eoa = ethers.Wallet.createRandom(ethers.provider);
+  const mock = await ethers.deployContract('$EIP7702Utils');
+  return { eoa, mock };
+};
+
+describe('EIP7702Utils', function () {
+  beforeEach(async function () {
+    Object.assign(this, await loadFixture(fixture));
+  });
+
+  describe('fetchDelegate', function () {
+    it('EOA without delegation', async function () {
+      await expect(this.mock.$fetchDelegate(this.eoa)).to.eventually.equal(ethers.ZeroAddress);
+    });
+
+    it('EOA with delegation', async function () {
+      // set delegation
+      await this.eoa.authorize({ address: this.mock }).then(relayAuthorization);
+
+      await expect(this.mock.$fetchDelegate(this.eoa)).to.eventually.equal(this.mock);
+    });
+
+    it('EOA with revoked delegation', async function () {
+      // set delegation
+      await this.eoa.authorize({ address: this.mock }).then(relayAuthorization);
+      // reset delegation
+      await this.eoa.authorize({ address: ethers.ZeroAddress }).then(relayAuthorization);
+
+      await expect(this.mock.$fetchDelegate(this.eoa)).to.eventually.equal(ethers.ZeroAddress);
+    });
+
+    it('other smart contract', async function () {
+      await expect(this.mock.$fetchDelegate(this.mock)).to.eventually.equal(ethers.ZeroAddress);
+    });
+  });
+});