Skip to content
Open
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
3 changes: 3 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,6 @@
[submodule "lib/p256-verifier"]
path = lib/p256-verifier
url = https://github.com/daimo-eth/p256-verifier
[submodule "lib/inflate-sol"]
path = lib/inflate-sol
url = https://github.com/adlerjohn/inflate-sol
1 change: 1 addition & 0 deletions lib/inflate-sol
Submodule inflate-sol added at 2a8814
12 changes: 9 additions & 3 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
"main": "dist/index.js",
"types": "dist/index.d.ts",
"scripts": {
"test": "forge test -vv",
"test": "forge test -vv --ffi",
"build": "tsc",
"codegen": "wagmi generate"
},
Expand All @@ -16,6 +16,7 @@
"dependencies": {
"@tsconfig/node20": "^20.1.2",
"@wagmi/cli": "^1.5.2",
"pako": "^2.1.0",
"typescript": "^5.0.0"
},
"publishConfig": {
Expand Down
3 changes: 2 additions & 1 deletion remappings.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,5 @@ forge-std/=lib/forge-std/src/
account-abstraction/=lib/account-abstraction/contracts/
openzeppelin-contracts/=lib/openzeppelin-contracts/
@openzeppelin/contracts/=lib/openzeppelin-contracts/contracts/
p256-verifier/=lib/p256-verifier/src/
p256-verifier/=lib/p256-verifier/src/
inflate-sol/=lib/inflate-sol/contracts/
32 changes: 32 additions & 0 deletions src/DeflateInflator.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
// SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity >=0.8;

import "./IInflator.sol";
import "inflate-sol/InflateLib.sol";
import "account-abstraction/interfaces/IEntryPoint.sol";
import {Test, console2} from "forge-std/Test.sol";

/// Inflates a generic bundle compressed with DEFLATE
/// This reduces calldata size by ~72% and calldata cost by ~37%
/// (due to calldata 0-bytes being cheaper than non-0-bytes)
contract DeflateInflator is IInflator {
error InflateLibError(InflateLib.ErrorCode errorCode);

function inflate(
bytes calldata compressed
) external view override returns (UserOperation[] memory, address payable) {
(InflateLib.ErrorCode errorCode, bytes memory decompressed) = InflateLib
.puff(compressed[3:], uint24(bytes3(compressed[0:3])));

if (errorCode != InflateLib.ErrorCode.ERR_NONE) {
revert InflateLibError(errorCode);
}

UserOperation[] memory ops = abi.decode(
abi.encodePacked(uint256(0x20), decompressed),
(UserOperation[])
);

return (ops, payable(tx.origin));
}
}
58 changes: 57 additions & 1 deletion test/BundleBulker.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,12 @@
pragma solidity ^0.8.13;

import {Test, console2} from "forge-std/Test.sol";
import {UserOperation} from "account-abstraction/interfaces/IEntryPoint.sol";
import {UserOperation,IEntryPoint} from "account-abstraction/interfaces/IEntryPoint.sol";

import {BundleBulker} from "../src/BundleBulker.sol";
import {IInflator} from "../src/IInflator.sol";
import {DaimoTransferInflator} from "../src/DaimoTransferInflator.sol";
import {DeflateInflator} from "../src/DeflateInflator.sol";

contract BundleBulkerTest is Test {
BundleBulker public b;
Expand Down Expand Up @@ -152,6 +153,61 @@ contract BundleBulkerTest is Test {
)
);
}

function test_DeflateInflator() public {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

recommend pulling this test out into DeflateInflator.t.sol

(i'll deleteDaimoTransferInflator in a future PR, already superseded by DaimoOpInflator)

DeflateInflator d = new DeflateInflator();
b.registerInflator(0x42, d);

// Taken from a real bundle that was submitted to the network:
// https://basescan.org/tx/0xacb32cca8ddefcf73cb16fda17ff34cf15382e4e0cfb7a96e8149011a5fe3d29
bytes memory raw = hex'000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000200000000000000000000000008bffa71a959af0b15c6eaa10d244d80bf23cb6a20000000000000000501c58693b65f1374631a2fca7bb7dc600000000000000000000000000000000000000000000000000000000000000000000000000000160000000000000000000000000000000000000000000000000000000000000018000000000000000000000000000000000000000000000000000000000000493e000000000000000000000000000000000000000000000000000000000000aae6000000000000000000000000000000000000000000000000000000000007b44a300000000000000000000000000000000000000000000000000000000000f427200000000000000000000000000000000000000000000000000000000000f4240000000000000000000000000000000000000000000000000000000000000030000000000000000000000000000000000000000000000000000000000000003400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000014434fcd5be000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000020000000000000000000000000833589fcd6edb6e08f4c7c32d4f71b54bda02913000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044a9059cbb000000000000000000000000a1b349c566c44769888948adc061abcdb54497f700000000000000000000000000000000000000000000000000000000000f42400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001499d720cd5a04c16dc5377638e3f6d609c895714f00000000000000000000000000000000000000000000000000000000000000000000000000000000000001e80100006553c75f00000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000c0000000000000000000000000000000000000000000000000000000000000012000000000000000000000000000000000000000000000000000000000000000170000000000000000000000000000000000000000000000000000000000000001ce1a2a89ec9d3cecd1e9fd65808d85702d7f8681d42ce8f0982363a362b87bd5498c72f497f9d27ae895c6d2c10a73e85b73d258371d2322c80ca5bfad242f5f000000000000000000000000000000000000000000000000000000000000002500000000000000000000000000000000000000000000000000000000000000000500000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000006f7b2274797065223a22776562617574686e2e676574222c226368616c6c656e6765223a22415141415a5650485830567a705463726d35665a6846505f566369545433584d57484832624e7a6a6435346531774e354d32696f222c226f726967696e223a226461696d6f2e636f6d227d0000000000000000000000000000000000000000000000000000000000000000000000000000000000';
UserOperation[] memory originalOps = abi.decode(abi.encodePacked(uint256(0x20), raw), (UserOperation[]));

string[] memory inputs = new string[](3);
inputs[0] = "node";
inputs[1] = "test/deflate.js";
inputs[2] = '000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000200000000000000000000000008bffa71a959af0b15c6eaa10d244d80bf23cb6a20000000000000000501c58693b65f1374631a2fca7bb7dc600000000000000000000000000000000000000000000000000000000000000000000000000000160000000000000000000000000000000000000000000000000000000000000018000000000000000000000000000000000000000000000000000000000000493e000000000000000000000000000000000000000000000000000000000000aae6000000000000000000000000000000000000000000000000000000000007b44a300000000000000000000000000000000000000000000000000000000000f427200000000000000000000000000000000000000000000000000000000000f4240000000000000000000000000000000000000000000000000000000000000030000000000000000000000000000000000000000000000000000000000000003400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000014434fcd5be000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000020000000000000000000000000833589fcd6edb6e08f4c7c32d4f71b54bda02913000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044a9059cbb000000000000000000000000a1b349c566c44769888948adc061abcdb54497f700000000000000000000000000000000000000000000000000000000000f42400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001499d720cd5a04c16dc5377638e3f6d609c895714f00000000000000000000000000000000000000000000000000000000000000000000000000000000000001e80100006553c75f00000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000c0000000000000000000000000000000000000000000000000000000000000012000000000000000000000000000000000000000000000000000000000000000170000000000000000000000000000000000000000000000000000000000000001ce1a2a89ec9d3cecd1e9fd65808d85702d7f8681d42ce8f0982363a362b87bd5498c72f497f9d27ae895c6d2c10a73e85b73d258371d2322c80ca5bfad242f5f000000000000000000000000000000000000000000000000000000000000002500000000000000000000000000000000000000000000000000000000000000000500000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000006f7b2274797065223a22776562617574686e2e676574222c226368616c6c656e6765223a22415141415a5650485830567a705463726d35665a6846505f566369545433584d57484832624e7a6a6435346531774e354d32696f222c226f726967696e223a226461696d6f2e636f6d227d0000000000000000000000000000000000000000000000000000000000000000000000000000000000';
Copy link
Member

@dcposch dcposch Jan 10, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why not just inputs[2] = raw;
?

overall, i don't know about adding FFI

i'd prefer to just say

bytes memory compressed = '<constant hex>';

...computed by calling test/deflate separately

bytes memory compressed = vm.ffi(inputs);

(UserOperation[] memory inflatedOps, address payable beneficiary) = b.inflate(
abi.encodePacked(uint32(0x42), uint24(raw.length), compressed)
);

assertEq(beneficiary, tx.origin);
assertEq(abi.encode(inflatedOps), abi.encode(originalOps));

// Calculate and print compression ratios

bytes memory uncompressedCallData = abi.encodeWithSelector(IEntryPoint.handleOps.selector, originalOps, tx.origin);
uint256 uncompressedCallDataLength = uncompressedCallData.length;
uint256 uncompressedCallDataCost = calculateCalldataCost(uncompressedCallData);

bytes memory compressedCallData = abi.encodePacked(uint32(0x42), uint24(raw.length), compressed);
uint256 compressedCallDataLength = compressedCallData.length;
uint256 compressedCallDataCost = calculateCalldataCost(compressedCallData);

console2.log("Uncompressed calldata length: ", uncompressedCallDataLength);
console2.log("Deflate compressed calldata length: ", compressedCallDataLength);
console2.log("Calldata length compression ratio: ", 100 - (compressedCallDataLength * 1e2) / uncompressedCallDataLength, "%");
console2.log("");
console2.log("Uncompressed calldata cost: ", uncompressedCallDataCost);
console2.log("Deflate compressed calldata cost: ", compressedCallDataCost);
console2.log("Calldata cost compression ratio: ", 100 - (compressedCallDataCost * 1e2) / uncompressedCallDataCost, "%");
}

function calculateCalldataCost(bytes memory callData) public pure returns (uint256 cost) {
// 0 bytes cost 4 gas, non-0 bytes cost 16 gas
for (uint256 i = 0; i < callData.length; i++) {
if (callData[i] == 0) {
cost += 4;
} else {
cost += 16;
}
}
}



}

contract DummyInflator is IInflator {
Expand Down
5 changes: 5 additions & 0 deletions test/deflate.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
const pako = require("pako");

const compressed = pako.deflateRaw(new Uint8Array(Buffer.from(process.argv[2], 'hex')), { level: 9 });

console.log(Buffer.from(compressed).toString('hex'));
Copy link
Member

@dcposch dcposch Jan 10, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🚀

cc @nalinbhardwaj this is a good example of why we might not want to require IInflator or IOpInflator to come with a compress() function in solidity/evm

in this case, there's an existing library that decompresses a DEFLATE stream, but none that compresses > easier to compress in js