diff --git a/contracts/contracts/EIP1559Signer.sol b/contracts/contracts/EIP1559Signer.sol index 0bc45fde..77c0505d 100644 --- a/contracts/contracts/EIP1559Signer.sol +++ b/contracts/contracts/EIP1559Signer.sol @@ -40,10 +40,10 @@ library EIP1559Signer { b[6] = RLPWriter.writeUint(rawTx.value); b[7] = RLPWriter.writeBytes(rawTx.data); b[8] = EIPTypes.encodeAccessList(rawTx.accessList); - + // RLP encode the transaction data bytes memory rlpEncodedTx = RLPWriter.writeList(b); - + // Return the unsigned transaction with EIP-1559 type prefix return abi.encodePacked(hex"02", rlpEncodedTx); } @@ -71,10 +71,10 @@ library EIP1559Signer { b[9] = RLPWriter.writeUint(uint256(rsv.v)); b[10] = RLPWriter.writeUint(uint256(rsv.r)); b[11] = RLPWriter.writeUint(uint256(rsv.s)); - + // RLP encode the transaction data bytes memory rlpEncodedTx = RLPWriter.writeList(b); - + // Return the signed transaction with EIP-1559 type prefix return abi.encodePacked(hex"02", rlpEncodedTx); } @@ -92,10 +92,10 @@ library EIP1559Signer { ) internal view returns (SignatureRSV memory ret) { // First encode the transaction without signature fields bytes memory encoded = encodeUnsignedTx(rawTx); - + // Hash the encoded unsigned transaction bytes32 digest = keccak256(encoded); - + // Sign the hash ret = EthereumUtils.sign(pubkeyAddr, secretKey, digest); } @@ -116,10 +116,10 @@ library EIP1559Signer { publicAddress, secretKey ); - + // For EIP-1559, we only need to normalize v to 0/1 rsv.v = rsv.v - 27; - + return encodeSignedTx(transaction, rsv); } -} \ No newline at end of file +} diff --git a/contracts/contracts/EIP2930Signer.sol b/contracts/contracts/EIP2930Signer.sol index 741fe18b..5c22d17d 100644 --- a/contracts/contracts/EIP2930Signer.sol +++ b/contracts/contracts/EIP2930Signer.sol @@ -38,10 +38,10 @@ library EIP2930Signer { b[5] = RLPWriter.writeUint(rawTx.value); b[6] = RLPWriter.writeBytes(rawTx.data); b[7] = EIPTypes.encodeAccessList(rawTx.accessList); - + // RLP encode the transaction data bytes memory rlpEncodedTx = RLPWriter.writeList(b); - + // Return the unsigned transaction with EIP-2930 type prefix return abi.encodePacked(hex"01", rlpEncodedTx); } @@ -68,10 +68,10 @@ library EIP2930Signer { b[8] = RLPWriter.writeUint(uint256(rsv.v)); b[9] = RLPWriter.writeUint(uint256(rsv.r)); b[10] = RLPWriter.writeUint(uint256(rsv.s)); - + // RLP encode the transaction data bytes memory rlpEncodedTx = RLPWriter.writeList(b); - + // Return the signed transaction with EIP-2930 type prefix return abi.encodePacked(hex"01", rlpEncodedTx); } @@ -89,10 +89,10 @@ library EIP2930Signer { ) internal view returns (SignatureRSV memory ret) { // First encode the transaction without signature fields bytes memory encoded = encodeUnsignedTx(rawTx); - + // Hash the encoded unsigned transaction bytes32 digest = keccak256(encoded); - + // Sign the hash ret = EthereumUtils.sign(pubkeyAddr, secretKey, digest); } @@ -113,10 +113,10 @@ library EIP2930Signer { publicAddress, secretKey ); - + // For EIP-2930, we only need to normalize v to 0/1 rsv.v = rsv.v - 27; - + return encodeSignedTx(transaction, rsv); } -} \ No newline at end of file +} diff --git a/contracts/contracts/EIPTypes.sol b/contracts/contracts/EIPTypes.sol index 04b80fbc..7c895415 100644 --- a/contracts/contracts/EIPTypes.sol +++ b/contracts/contracts/EIPTypes.sol @@ -6,12 +6,12 @@ library EIPTypes { struct AccessList { AccessListItem[] items; } - + struct AccessListItem { address addr; bytes32[] storageKeys; } - + /** * @notice Encode an access list for EIP-1559 and EIP-2930 transactions */ @@ -21,23 +21,27 @@ library EIPTypes { returns (bytes memory) { bytes[] memory items = new bytes[](list.items.length); - - for (uint i = 0; i < list.items.length; i++) { + + for (uint256 i = 0; i < list.items.length; i++) { bytes[] memory item = new bytes[](2); // Encode the address item[0] = RLPWriter.writeAddress(list.items[i].addr); - + // Encode storage keys - bytes[] memory storageKeys = new bytes[](list.items[i].storageKeys.length); - for (uint j = 0; j < list.items[i].storageKeys.length; j++) { + bytes[] memory storageKeys = new bytes[]( + list.items[i].storageKeys.length + ); + for (uint256 j = 0; j < list.items[i].storageKeys.length; j++) { // Use writeBytes for the full storage key - storageKeys[j] = RLPWriter.writeBytes(abi.encodePacked(list.items[i].storageKeys[j])); + storageKeys[j] = RLPWriter.writeBytes( + abi.encodePacked(list.items[i].storageKeys[j]) + ); } item[1] = RLPWriter.writeList(storageKeys); - + items[i] = RLPWriter.writeList(item); } - + return RLPWriter.writeList(items); } -} \ No newline at end of file +} diff --git a/contracts/test/eip1559_2930.ts b/contracts/test/eip1559_2930.ts index 0205667a..2c6fd626 100644 --- a/contracts/test/eip1559_2930.ts +++ b/contracts/test/eip1559_2930.ts @@ -6,7 +6,8 @@ import { EIPTests } from '../typechain-types/contracts/tests/EIPTests'; import { HardhatNetworkHDAccountsConfig } from 'hardhat/types'; import { Transaction } from 'ethers'; -const EXPECTED_EVENT = '0xfedcba9876543210fedcba9876543210fedcba9876543210fedcba9876543210'; +const EXPECTED_EVENT = + '0xfedcba9876543210fedcba9876543210fedcba9876543210fedcba9876543210'; const EXPECTED_ENTROPY_ENCRYPTED = 3.8; // Shannon entropy calculation @@ -22,7 +23,8 @@ function entropy(str: string) { } function getWallet(index: number) { - const accounts = hre.network.config.accounts as HardhatNetworkHDAccountsConfig; + const accounts = hre.network.config + .accounts as HardhatNetworkHDAccountsConfig; if (!accounts.mnemonic) { return new ethers.Wallet((accounts as unknown as string[])[0]); } @@ -35,7 +37,7 @@ function getWallet(index: number) { async function verifyTxReceipt(response: any, expectedData?: string) { const receipt = await response.wait(); if (!receipt) throw new Error('No transaction receipt received'); - + if (expectedData) { expect(receipt.logs[0].data).to.equal(expectedData); } @@ -70,7 +72,7 @@ describe('EIP-1559 and EIP-2930 Tests', function () { expect(onchainChainId).eq(expectedChainId); }); - describe('EIP-1559', function() { + describe('EIP-1559', function () { it('Other-Signed EIP-1559 transaction submission via un-wrapped provider', async function () { const provider = ethers.provider; const feeData = await provider.getFeeData(); @@ -79,18 +81,20 @@ describe('EIP-1559 and EIP-2930 Tests', function () { const secretKey = await testContract.SECRET_KEY(); console.log(`Public Address: ${publicAddr}`); console.log(`Secret Key: ${secretKey}`); - + // Ensure fee data is valid if (!feeData.gasPrice) { - throw new Error('Failed to fetch fee data'); - } - + throw new Error('Failed to fetch fee data'); + } + // Set custom values for maxPriorityFeePerGas and maxFeePerGas const maxPriorityFeePerGas = ethers.parseUnits('20', 'gwei'); // Custom value for maxPriorityFeePerGas const maxFeePerGas = ethers.parseUnits('120', 'gwei'); // Custom value for maxFeePerGas const signedTx = await testContract.signEIP1559({ - nonce: await provider.getTransactionCount(await testContract.SENDER_ADDRESS()), + nonce: await provider.getTransactionCount( + await testContract.SENDER_ADDRESS(), + ), maxPriorityFeePerGas: maxPriorityFeePerGas, maxFeePerGas: maxFeePerGas, gasLimit: 250000, @@ -113,72 +117,74 @@ describe('EIP-1559 and EIP-2930 Tests', function () { const privateKey = await testContract.SECRET_KEY(); const wp = new ethers.Wallet(privateKey, provider); const wallet = wrapEthersSigner(wp); - + const maxPriorityFeePerGas = ethers.parseUnits('20', 'gwei'); const maxFeePerGas = ethers.parseUnits('200', 'gwei'); const contractAddress = await testContract.getAddress(); - + // Get storage slots const [slot0, slot1, slot2, slot3] = await testContract.getStorageSlots(); - + // Test cases const testCases = [ - { - name: "Without access list", - accessList: [] - }, - { - name: "With access list", - accessList: [ - { - address: contractAddress, - storageKeys: [slot0, slot1, slot2, slot3] - } - ] - } + { + name: 'Without access list', + accessList: [], + }, + { + name: 'With access list', + accessList: [ + { + address: contractAddress, + storageKeys: [slot0, slot1, slot2, slot3], + }, + ], + }, ]; - + for (const testCase of testCases) { - console.log(`\nTesting: ${testCase.name}`); - - const tx = Transaction.from({ - gasLimit: 250000, - to: contractAddress, - value: 0, - data: calldata, - chainId: (await provider.getNetwork()).chainId, - maxPriorityFeePerGas, - maxFeePerGas, - nonce: await provider.getTransactionCount(wallet.address), - type: 2, // EIP-1559 - accessList: testCase.accessList - }); - - const signedTx = await wallet.signTransaction(tx); - let response = await provider.broadcastTransaction(signedTx); - const receipt = await verifyTxReceipt(response); - - console.log(`Gas used: ${receipt.gasUsed} gas`); - - // Verify transaction succeeded and produced expected results - expect(entropy(response.data)).gte(EXPECTED_ENTROPY_ENCRYPTED); - expect(response.data).not.eq(calldata); - expect(receipt.logs[0].data).equal(EXPECTED_EVENT); - - // Optional: Print the decoded transaction to verify access list - const decodedTx = Transaction.from(signedTx); - console.log('Access List:', decodedTx.accessList); + console.log(`\nTesting: ${testCase.name}`); + + const tx = Transaction.from({ + gasLimit: 250000, + to: contractAddress, + value: 0, + data: calldata, + chainId: (await provider.getNetwork()).chainId, + maxPriorityFeePerGas, + maxFeePerGas, + nonce: await provider.getTransactionCount(wallet.address), + type: 2, // EIP-1559 + accessList: testCase.accessList, + }); + + const signedTx = await wallet.signTransaction(tx); + let response = await provider.broadcastTransaction(signedTx); + const receipt = await verifyTxReceipt(response); + + console.log(`Gas used: ${receipt.gasUsed} gas`); + + // Verify transaction succeeded and produced expected results + expect(entropy(response.data)).gte(EXPECTED_ENTROPY_ENCRYPTED); + expect(response.data).not.eq(calldata); + expect(receipt.logs[0].data).equal(EXPECTED_EVENT); + + // Optional: Print the decoded transaction to verify access list + const decodedTx = Transaction.from(signedTx); + console.log('Access List:', decodedTx.accessList); } - }); + }); }); - describe('EIP-2930', function() { + describe('EIP-2930', function () { it('Other-Signed EIP-2930 transaction submission via un-wrapped provider', async function () { const provider = ethers.provider; const feeData = await provider.getFeeData(); - + const signedTx = await testContract.signEIP2930({ - nonce: await provider.getTransactionCount(await testContract.SENDER_ADDRESS()), + nonce: await provider.getTransactionCount( + await testContract.SENDER_ADDRESS(), + ), gasPrice: feeData.gasPrice as bigint, gasLimit: 250000, to: await testContract.getAddress(), @@ -210,12 +216,12 @@ describe('EIP-1559 and EIP-2930 Tests', function () { gasPrice: feeData.gasPrice, nonce: await provider.getTransactionCount(wallet.address), type: 1, // EIP-2930 - accessList: [ + accessList: [ { address: await testContract.getAddress(), storageKeys: [ - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000001", + '0x0000000000000000000000000000000000000000000000000000000000000000', + '0x0000000000000000000000000000000000000000000000000000000000000001', ], }, ], @@ -234,17 +240,17 @@ describe('EIP-1559 and EIP-2930 Tests', function () { it('should fail with invalid storage key length', async function () { const provider = ethers.provider; const publicAddr = await testContract.SENDER_ADDRESS(); - + // Create an access list with invalid storage key length const accessList = { items: [ { addr: await testContract.getAddress(), storageKeys: [ - ethers.zeroPadValue('0x01', 16) // Invalid: only 16 bytes instead of 32 - ] - } - ] + ethers.zeroPadValue('0x01', 16), // Invalid: only 16 bytes instead of 32 + ], + }, + ], }; await expect( @@ -257,50 +263,54 @@ describe('EIP-1559 and EIP-2930 Tests', function () { data: '0x', accessList, chainId: (await provider.getNetwork()).chainId, - }) + }), ).to.be.revertedWithCustomError; }); }); - describe('Access List Gas Tests', function() { - - describe('Gas Usage Comparison', function() { - it('should compare gas usage with and without access lists for EIP-1559', async function() { + describe('Access List Gas Tests', function () { + describe('Gas Usage Comparison', function () { + it('should compare gas usage with and without access lists for EIP-1559', async function () { const provider = ethers.provider; const publicAddr = await testContract.SENDER_ADDRESS(); const contractAddress = await testContract.getAddress(); - - const [slot0, slot1, slot2, slot3] = await testContract.getStorageSlots(); - + + const [slot0, slot1, slot2, slot3] = + await testContract.getStorageSlots(); + // Test cases with different access list configurations const testCases = [ { - name: "No access list", - accessList: { items: [] } + name: 'No access list', + accessList: { items: [] }, }, { - name: "With storedNumber1 and storedNumber2", + name: 'With storedNumber1 and storedNumber2', accessList: { - items: [{ - addr: contractAddress, - storageKeys: [slot0, slot1] - }] - } + items: [ + { + addr: contractAddress, + storageKeys: [slot0, slot1], + }, + ], + }, }, { - name: "With all storage slots", + name: 'With all storage slots', accessList: { - items: [{ - addr: contractAddress, - storageKeys: [slot0, slot1, slot2, slot3] - }] - } - } + items: [ + { + addr: contractAddress, + storageKeys: [slot0, slot1, slot2, slot3], + }, + ], + }, + }, ]; - + for (const testCase of testCases) { console.log(`\nTesting: ${testCase.name}`); - + const signedTx = await testContract.signEIP1559({ nonce: await provider.getTransactionCount(publicAddr), maxPriorityFeePerGas: ethers.parseUnits('20', 'gwei'), @@ -312,58 +322,65 @@ describe('EIP-1559 and EIP-2930 Tests', function () { accessList: testCase.accessList, chainId: (await provider.getNetwork()).chainId, }); - + const response = await provider.broadcastTransaction(signedTx); const receipt = await verifyTxReceipt(response); - + console.log(`Gas used: ${receipt.gasUsed}`); } }); - - it('should compare gas usage with and without access lists for EIP-2930', async function() { + + it('should compare gas usage with and without access lists for EIP-2930', async function () { const provider = ethers.provider; const publicAddr = await testContract.SENDER_ADDRESS(); const contractAddress = await testContract.getAddress(); - - const [slot0, slot1, slot2, slot3] = await testContract.getStorageSlots(); - + + const [slot0, slot1, slot2, slot3] = + await testContract.getStorageSlots(); + const testCases = [ { - name: "No access list", - accessList: { items: [] } + name: 'No access list', + accessList: { items: [] }, }, { - name: "With number slots", + name: 'With number slots', accessList: { - items: [{ - addr: contractAddress, - storageKeys: [slot0, slot1] - }] - } + items: [ + { + addr: contractAddress, + storageKeys: [slot0, slot1], + }, + ], + }, }, { - name: "With bytes slots", + name: 'With bytes slots', accessList: { - items: [{ - addr: contractAddress, - storageKeys: [slot2, slot3] - }] - } + items: [ + { + addr: contractAddress, + storageKeys: [slot2, slot3], + }, + ], + }, }, { - name: "With all slots", + name: 'With all slots', accessList: { - items: [{ - addr: contractAddress, - storageKeys: [slot0, slot1, slot2, slot3] - }] - } - } + items: [ + { + addr: contractAddress, + storageKeys: [slot0, slot1, slot2, slot3], + }, + ], + }, + }, ]; - + for (const testCase of testCases) { console.log(`\nTesting: ${testCase.name}`); - + const signedTx = await testContract.signEIP2930({ nonce: await provider.getTransactionCount(publicAddr), gasPrice: ethers.parseUnits('100', 'gwei'), @@ -374,14 +391,13 @@ describe('EIP-1559 and EIP-2930 Tests', function () { accessList: testCase.accessList, chainId: (await provider.getNetwork()).chainId, }); - + const response = await provider.broadcastTransaction(signedTx); const receipt = await verifyTxReceipt(response); - + console.log(`Gas used: ${receipt.gasUsed}`); } }); }); }); - -}); \ No newline at end of file +});