From 352ab136878f652658f8f4e3d70b4087563653c4 Mon Sep 17 00:00:00 2001 From: Hadrien Croubois Date: Fri, 10 Jan 2025 02:48:32 +0100 Subject: [PATCH] Add a Calldata library with `emptyBytes` and `emptyString` functions (#5422) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Ernesto GarcĂ­a --- .changeset/good-cameras-serve.md | 5 ++++ .../account/utils/draft-ERC4337Utils.sol | 13 +++------- contracts/utils/Calldata.sol | 24 +++++++++++++++++++ test/utils/Calldata.test.js | 22 +++++++++++++++++ 4 files changed, 54 insertions(+), 10 deletions(-) create mode 100644 .changeset/good-cameras-serve.md create mode 100644 contracts/utils/Calldata.sol create mode 100644 test/utils/Calldata.test.js diff --git a/.changeset/good-cameras-serve.md b/.changeset/good-cameras-serve.md new file mode 100644 index 00000000000..1f1895504dd --- /dev/null +++ b/.changeset/good-cameras-serve.md @@ -0,0 +1,5 @@ +--- +"openzeppelin-solidity": minor +--- + +`Calldata`: Library with `emptyBytes` and `emptyString` functions to generate empty `bytes` and `string` calldata types. diff --git a/contracts/account/utils/draft-ERC4337Utils.sol b/contracts/account/utils/draft-ERC4337Utils.sol index d13d5193970..5cd927c63ca 100644 --- a/contracts/account/utils/draft-ERC4337Utils.sol +++ b/contracts/account/utils/draft-ERC4337Utils.sol @@ -5,6 +5,7 @@ pragma solidity ^0.8.20; import {PackedUserOperation} from "../../interfaces/draft-IERC4337.sol"; import {Math} from "../../utils/math/Math.sol"; +import {Calldata} from "../../utils/Calldata.sol"; import {Packing} from "../../utils/Packing.sol"; /** @@ -107,7 +108,7 @@ library ERC4337Utils { /// @dev Returns `factoryData` from the {PackedUserOperation}, or empty bytes if the initCode is empty or not properly formatted. function factoryData(PackedUserOperation calldata self) internal pure returns (bytes calldata) { - return self.initCode.length < 20 ? _emptyCalldataBytes() : self.initCode[20:]; + return self.initCode.length < 20 ? Calldata.emptyBytes() : self.initCode[20:]; } /// @dev Returns `verificationGasLimit` from the {PackedUserOperation}. @@ -157,14 +158,6 @@ library ERC4337Utils { /// @dev Returns the fourth section of `paymasterAndData` from the {PackedUserOperation}. function paymasterData(PackedUserOperation calldata self) internal pure returns (bytes calldata) { - return self.paymasterAndData.length < 52 ? _emptyCalldataBytes() : self.paymasterAndData[52:]; - } - - // slither-disable-next-line write-after-write - function _emptyCalldataBytes() private pure returns (bytes calldata result) { - assembly ("memory-safe") { - result.offset := 0 - result.length := 0 - } + return self.paymasterAndData.length < 52 ? Calldata.emptyBytes() : self.paymasterAndData[52:]; } } diff --git a/contracts/utils/Calldata.sol b/contracts/utils/Calldata.sol new file mode 100644 index 00000000000..60e0b08b015 --- /dev/null +++ b/contracts/utils/Calldata.sol @@ -0,0 +1,24 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.20; + +/** + * @dev Helper library for manipulating objects in calldata. + */ +library Calldata { + // slither-disable-next-line write-after-write + function emptyBytes() internal pure returns (bytes calldata result) { + assembly ("memory-safe") { + result.offset := 0 + result.length := 0 + } + } + + // slither-disable-next-line write-after-write + function emptyString() internal pure returns (string calldata result) { + assembly ("memory-safe") { + result.offset := 0 + result.length := 0 + } + } +} diff --git a/test/utils/Calldata.test.js b/test/utils/Calldata.test.js new file mode 100644 index 00000000000..7e9d3d47813 --- /dev/null +++ b/test/utils/Calldata.test.js @@ -0,0 +1,22 @@ +const { ethers } = require('hardhat'); +const { expect } = require('chai'); +const { loadFixture } = require('@nomicfoundation/hardhat-network-helpers'); + +async function fixture() { + const mock = await ethers.deployContract('$Calldata'); + return { mock }; +} + +describe('Calldata utilities', function () { + beforeEach(async function () { + Object.assign(this, await loadFixture(fixture)); + }); + + it('emptyBytes', async function () { + await expect(this.mock.$emptyBytes()).to.eventually.equal('0x'); + }); + + it('emptyString', async function () { + await expect(this.mock.$emptyString()).to.eventually.equal(''); + }); +});