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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/full-ways-help.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'openzeppelin-solidity': minor
---

`EIP7702Utils`: Add a library for checking if an address has an EIP-7702 delegation in place.
20 changes: 20 additions & 0 deletions contracts/account/utils/EIP7702Utils.sol
Original file line number Diff line number Diff line change
@@ -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);
}
}
7 changes: 4 additions & 3 deletions contracts/mocks/Stateless.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand All @@ -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";
Expand Down
53 changes: 53 additions & 0 deletions test/account/utils/EIP7702Utils.test.js
Original file line number Diff line number Diff line change
@@ -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);
});
});
});