ソースを参照

Add `factory()`, `factoryData()` and `paymasterData()` helpers to ERC4337Utils (#5313)

Co-authored-by: Ernesto García <ernestognw@gmail.com>
Hadrien Croubois 10 ヶ月 前
コミット
e1d44e0342

+ 26 - 3
contracts/account/utils/draft-ERC4337Utils.sol

@@ -98,6 +98,16 @@ library ERC4337Utils {
         return result;
     }
 
+    /// @dev Returns `factory` from the {PackedUserOperation}, or address(0) if the initCode is empty or not properly formatted.
+    function factory(PackedUserOperation calldata self) internal pure returns (address) {
+        return self.initCode.length < 20 ? address(0) : address(bytes20(self.initCode[0:20]));
+    }
+
+    /// @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:];
+    }
+
     /// @dev Returns `verificationGasLimit` from the {PackedUserOperation}.
     function verificationGasLimit(PackedUserOperation calldata self) internal pure returns (uint256) {
         return uint128(self.accountGasLimits.extract_32_16(0x00));
@@ -130,16 +140,29 @@ library ERC4337Utils {
 
     /// @dev Returns the first section of `paymasterAndData` from the {PackedUserOperation}.
     function paymaster(PackedUserOperation calldata self) internal pure returns (address) {
-        return address(bytes20(self.paymasterAndData[0:20]));
+        return self.paymasterAndData.length < 52 ? address(0) : address(bytes20(self.paymasterAndData[0:20]));
     }
 
     /// @dev Returns the second section of `paymasterAndData` from the {PackedUserOperation}.
     function paymasterVerificationGasLimit(PackedUserOperation calldata self) internal pure returns (uint256) {
-        return uint128(bytes16(self.paymasterAndData[20:36]));
+        return self.paymasterAndData.length < 52 ? 0 : uint128(bytes16(self.paymasterAndData[20:36]));
     }
 
     /// @dev Returns the third section of `paymasterAndData` from the {PackedUserOperation}.
     function paymasterPostOpGasLimit(PackedUserOperation calldata self) internal pure returns (uint256) {
-        return uint128(bytes16(self.paymasterAndData[36:52]));
+        return self.paymasterAndData.length < 52 ? 0 : uint128(bytes16(self.paymasterAndData[36:52]));
+    }
+
+    /// @dev Returns the forth 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
+        }
     }
 }

+ 52 - 10
test/account/utils/draft-ERC4337Utils.test.js

@@ -2,13 +2,13 @@ const { ethers } = require('hardhat');
 const { expect } = require('chai');
 const { loadFixture } = require('@nomicfoundation/hardhat-network-helpers');
 
-const { packValidationData, packPaymasterData, UserOperation } = require('../../helpers/erc4337');
+const { packValidationData, UserOperation } = require('../../helpers/erc4337');
 const { MAX_UINT48 } = require('../../helpers/constants');
 
 const fixture = async () => {
-  const [authorizer, sender, entrypoint, paymaster] = await ethers.getSigners();
+  const [authorizer, sender, entrypoint, factory, paymaster] = await ethers.getSigners();
   const utils = await ethers.deployContract('$ERC4337Utils');
-  return { utils, authorizer, sender, entrypoint, paymaster };
+  return { utils, authorizer, sender, entrypoint, factory, paymaster };
 };
 
 describe('ERC4337Utils', function () {
@@ -144,6 +144,33 @@ describe('ERC4337Utils', function () {
   });
 
   describe('userOp values', function () {
+    describe('intiCode', function () {
+      beforeEach(async function () {
+        this.userOp = new UserOperation({
+          sender: this.sender,
+          nonce: 1,
+          verificationGas: 0x12345678n,
+          factory: this.factory,
+          factoryData: '0x123456',
+        });
+
+        this.emptyUserOp = new UserOperation({
+          sender: this.sender,
+          nonce: 1,
+        });
+      });
+
+      it('returns factory', async function () {
+        expect(this.utils.$factory(this.userOp.packed)).to.eventually.equal(this.factory);
+        expect(this.utils.$factory(this.emptyUserOp.packed)).to.eventually.equal(ethers.ZeroAddress);
+      });
+
+      it('returns factoryData', async function () {
+        expect(this.utils.$factoryData(this.userOp.packed)).to.eventually.equal('0x123456');
+        expect(this.utils.$factoryData(this.emptyUserOp.packed)).to.eventually.equal('0x');
+      });
+    });
+
     it('returns verificationGasLimit', async function () {
       const userOp = new UserOperation({ sender: this.sender, nonce: 1, verificationGas: 0x12345678n });
       expect(this.utils.$verificationGasLimit(userOp.packed)).to.eventually.equal(userOp.verificationGas);
@@ -176,28 +203,43 @@ describe('ERC4337Utils', function () {
 
     describe('paymasterAndData', function () {
       beforeEach(async function () {
-        this.verificationGasLimit = 0x12345678n;
-        this.postOpGasLimit = 0x87654321n;
-        this.paymasterAndData = packPaymasterData(this.paymaster, this.verificationGasLimit, this.postOpGasLimit);
         this.userOp = new UserOperation({
           sender: this.sender,
           nonce: 1,
-          paymasterAndData: this.paymasterAndData,
+          paymaster: this.paymaster,
+          paymasterVerificationGasLimit: 0x12345678n,
+          paymasterPostOpGasLimit: 0x87654321n,
+          paymasterData: '0xbeefcafe',
+        });
+
+        this.emptyUserOp = new UserOperation({
+          sender: this.sender,
+          nonce: 1,
         });
       });
 
       it('returns paymaster', async function () {
-        expect(this.utils.$paymaster(this.userOp.packed)).to.eventually.equal(this.paymaster);
+        expect(this.utils.$paymaster(this.userOp.packed)).to.eventually.equal(this.userOp.paymaster);
+        expect(this.utils.$paymaster(this.emptyUserOp.packed)).to.eventually.equal(ethers.ZeroAddress);
       });
 
       it('returns verificationGasLimit', async function () {
         expect(this.utils.$paymasterVerificationGasLimit(this.userOp.packed)).to.eventually.equal(
-          this.verificationGasLimit,
+          this.userOp.paymasterVerificationGasLimit,
         );
+        expect(this.utils.$paymasterVerificationGasLimit(this.emptyUserOp.packed)).to.eventually.equal(0n);
       });
 
       it('returns postOpGasLimit', async function () {
-        expect(this.utils.$paymasterPostOpGasLimit(this.userOp.packed)).to.eventually.equal(this.postOpGasLimit);
+        expect(this.utils.$paymasterPostOpGasLimit(this.userOp.packed)).to.eventually.equal(
+          this.userOp.paymasterPostOpGasLimit,
+        );
+        expect(this.utils.$paymasterPostOpGasLimit(this.emptyUserOp.packed)).to.eventually.equal(0n);
+      });
+
+      it('returns data', async function () {
+        expect(this.utils.$paymasterData(this.userOp.packed)).to.eventually.equal(this.userOp.paymasterData);
+        expect(this.utils.$paymasterData(this.emptyUserOp.packed)).to.eventually.equal('0x');
       });
     });
   });

+ 2 - 3
test/account/utils/draft-ERC7579Utils.test.js

@@ -1,6 +1,6 @@
 const { ethers } = require('hardhat');
 const { expect } = require('chai');
-const { loadFixture, setBalance } = require('@nomicfoundation/hardhat-network-helpers');
+const { loadFixture } = require('@nomicfoundation/hardhat-network-helpers');
 const {
   EXEC_TYPE_DEFAULT,
   EXEC_TYPE_TRY,
@@ -17,11 +17,10 @@ const coder = ethers.AbiCoder.defaultAbiCoder();
 
 const fixture = async () => {
   const [sender] = await ethers.getSigners();
-  const utils = await ethers.deployContract('$ERC7579Utils');
+  const utils = await ethers.deployContract('$ERC7579Utils', { value: ethers.parseEther('1') });
   const utilsGlobal = await ethers.deployContract('$ERC7579UtilsGlobalMock');
   const target = await ethers.deployContract('CallReceiverMock');
   const anotherTarget = await ethers.deployContract('CallReceiverMock');
-  await setBalance(utils.target, ethers.parseEther('1'));
   return { utils, utilsGlobal, target, anotherTarget, sender };
 };
 

+ 24 - 8
test/helpers/erc4337.js

@@ -26,10 +26,14 @@ function packValidationData(validAfter, validUntil, authorizer) {
   );
 }
 
-function packPaymasterData(paymaster, verificationGasLimit, postOpGasLimit) {
+function packInitCode(factory, factoryData) {
+  return ethers.solidityPacked(['address', 'bytes'], [getAddress(factory), factoryData]);
+}
+
+function packPaymasterAndData(paymaster, paymasterVerificationGasLimit, paymasterPostOpGasLimit, paymasterData) {
   return ethers.solidityPacked(
-    ['address', 'uint128', 'uint128'],
-    [getAddress(paymaster), verificationGasLimit, postOpGasLimit],
+    ['address', 'uint128', 'uint128', 'bytes'],
+    [getAddress(paymaster), paymasterVerificationGasLimit, paymasterPostOpGasLimit, paymasterData],
   );
 }
 
@@ -38,14 +42,18 @@ class UserOperation {
   constructor(params) {
     this.sender = getAddress(params.sender);
     this.nonce = params.nonce;
-    this.initCode = params.initCode ?? '0x';
+    this.factory = params.factory ?? undefined;
+    this.factoryData = params.factoryData ?? '0x';
     this.callData = params.callData ?? '0x';
     this.verificationGas = params.verificationGas ?? 10_000_000n;
     this.callGas = params.callGas ?? 100_000n;
     this.preVerificationGas = params.preVerificationGas ?? 100_000n;
     this.maxPriorityFee = params.maxPriorityFee ?? 100_000n;
     this.maxFeePerGas = params.maxFeePerGas ?? 100_000n;
-    this.paymasterAndData = params.paymasterAndData ?? '0x';
+    this.paymaster = params.paymaster ?? undefined;
+    this.paymasterVerificationGasLimit = params.paymasterVerificationGasLimit ?? 0n;
+    this.paymasterPostOpGasLimit = params.paymasterPostOpGasLimit ?? 0n;
+    this.paymasterData = params.paymasterData ?? '0x';
     this.signature = params.signature ?? '0x';
   }
 
@@ -53,12 +61,19 @@ class UserOperation {
     return {
       sender: this.sender,
       nonce: this.nonce,
-      initCode: this.initCode,
+      initCode: this.factory ? packInitCode(this.factory, this.factoryData) : '0x',
       callData: this.callData,
       accountGasLimits: pack(this.verificationGas, this.callGas),
       preVerificationGas: this.preVerificationGas,
       gasFees: pack(this.maxPriorityFee, this.maxFeePerGas),
-      paymasterAndData: this.paymasterAndData,
+      paymasterAndData: this.paymaster
+        ? packPaymasterAndData(
+            this.paymaster,
+            this.paymasterVerificationGasLimit,
+            this.paymasterPostOpGasLimit,
+            this.paymasterData,
+          )
+        : '0x',
       signature: this.signature,
     };
   }
@@ -90,6 +105,7 @@ module.exports = {
   SIG_VALIDATION_SUCCESS,
   SIG_VALIDATION_FAILURE,
   packValidationData,
-  packPaymasterData,
+  packInitCode,
+  packPaymasterAndData,
   UserOperation,
 };