Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add wax compression #1

Merged
merged 11 commits into from
Jun 19, 2024
4 changes: 4 additions & 0 deletions packages/bundler/contracts/Import.sol
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,7 @@ pragma solidity ^0.8.0;
// import contracts to get their type info.
import "@account-abstraction/utils/contracts/test/SampleRecipient.sol";
import "@account-abstraction/contracts/samples/SimpleAccountFactory.sol";
import "@account-abstraction/contracts/samples/bls/BLSSignatureAggregator.sol";

import {HandleOpsCaller} from "./compression/HandleOpsCaller.sol";
import {HandleAggregatedOpsCaller} from "./compression/HandleAggregatedOpsCaller.sol";
30 changes: 30 additions & 0 deletions packages/bundler/contracts/compression/AddressRegistry.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
//SPDX-License-Identifier: Unlicense
pragma solidity >=0.7.0 <0.9.0;
pragma abicoder v2;

contract AddressRegistry {
mapping(uint256 => address) public addresses;
uint256 public nextId = 0;

event AddressRegistered(uint256 id, address indexed addr);

struct Entry {
uint256 id;
address addr;
}

function register(address addr) external {
uint256 id = nextId;
nextId += 1;
addresses[id] = addr;

emit AddressRegistered(id, addr);
}

function lookup(uint256 id) external view returns (address) {
require(id < nextId, "AddressRegistry: Address not found");
address addr = addresses[id];

return addr;
}
}
33 changes: 33 additions & 0 deletions packages/bundler/contracts/compression/BitStack.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
//SPDX-License-Identifier: Unlicense
pragma solidity >=0.7.0 <0.9.0;
pragma abicoder v2;

library BitStack {
// The most significant bit of a bit stack signals the end of the stack.
// Otherwise there would be an infinite number of ambiguous zeros. This
// often doesn't matter, but it's useful for reversing bit stacks.
uint256 constant empty = 1;

function push(uint256 bitStack, bool bit) internal pure returns (uint256) {
bitStack <<= 1;
bitStack += bit ? 1 : 0;

return bitStack;
}

function pop(uint256 bitStack) internal pure returns (bool, uint256) {
return ((bitStack & 1) == 1, bitStack >> 1);
}

function reverse(uint256 bitStack) internal pure returns (uint256) {
uint256 reverseBitStack = 1;

while (bitStack != 1) {
reverseBitStack <<= 1;
reverseBitStack += (bitStack & 1);
bitStack >>= 1;
}

return reverseBitStack;
}
}
98 changes: 98 additions & 0 deletions packages/bundler/contracts/compression/DemoAccount.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
//SPDX-License-Identifier: Unlicense
pragma solidity >=0.7.0 <0.9.0;
pragma abicoder v2;

import {WaxLib as W} from "./WaxLib.sol";
import {IDecompressor} from "./decompressors/IDecompressor.sol";

contract DemoAccount {
address public owner;
IDecompressor public decompressor = IDecompressor(address(0));

constructor(address ownerParam) {
owner = ownerParam;
}

function perform(
W.Action[] memory actions
) public isTrusted returns (bytes[] memory) {
bytes[] memory results = new bytes[](actions.length);

for (uint256 i = 0; i < actions.length; i++) {
W.Action memory a = actions[i];

if (a.to != W.contractCreationAddress) {
(bool success, bytes memory result) = payable(a.to)
.call{value: a.value}(a.data);

if (!success) {
revert W.ActionError(i, result);
}

results[i] = result;
} else {
address addr;
uint256 value = a.value;
bytes memory data = a.data;

assembly {
addr := create(
value,
add(data, 0x20),
mload(data)
)

if iszero(addr) {
revert(0, 0)
}
}

results[i] = abi.encode(addr);
}
}

return results;
}

function setDecompressor(
IDecompressor decompressorParam
) public isTrusted {
decompressor = decompressorParam;
}

function decompressAndPerform(
bytes calldata stream
) public isTrusted returns (bytes[] memory) {
(W.Action[] memory actions,) = decompressor.decompress(stream);
return perform(actions);
}

/**
* This is the normal way to pass calldata.
*
* Using `decompressAndPerform` directly costs about 51.5 bytes more
* because the solidity ABI encodes a call with bytes as:
* - 4 byte function signature
* - 32 bytes for a uint256 field indicating the byte length
* - The actual bytes
* - Padding bytes (zeros) to ensure a multiple of 32 (+15.5 on average)
*
* Having the fallback function allows us to just pass the actual bytes
* most of the time. However, wallets need to ensure they don't
* accidentally encode one of the other methods. When they hit this case,
* they need to encode a call to `decompressAndPerform` instead.
*/
fallback(bytes calldata stream) external isTrusted returns (bytes memory) {
return abi.encode(decompressAndPerform(stream));
}

receive() external payable {}

modifier isTrusted() {
require(
msg.sender == owner ||
msg.sender == address(this)
);
_;
}
}
132 changes: 132 additions & 0 deletions packages/bundler/contracts/compression/HandleAggregatedOpsCaller.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
//SPDX-License-Identifier: Unlicense
pragma solidity >=0.7.0 <0.9.0;
pragma abicoder v2;

import {
IEntryPoint,
IAggregator,
UserOpsPerAggregator,
UserOperation
} from "./I4337.sol";
import {VLQ} from "./VLQ.sol";
import {BitStack} from "./BitStack.sol";
import {AddressRegistry} from "./AddressRegistry.sol";
import {RegIndex} from "./RegIndex.sol";
import {PseudoFloat} from "./PseudoFloat.sol";

contract HandleAggregatedOpsCaller {
IEntryPoint entryPoint;
address payable beneficiary;
IAggregator aggregator;

AddressRegistry registry;

constructor(
IEntryPoint entryPointParam,
address payable beneficiaryParam,
IAggregator aggregatorParam,
AddressRegistry registryParam
) {
entryPoint = entryPointParam;
beneficiary = beneficiaryParam;
aggregator = aggregatorParam;
registry = registryParam;
}

fallback(bytes calldata stream) external returns (bytes memory) {
uint256 len;
(len, stream) = VLQ.decode(stream);

uint256 bitStack;
(bitStack, stream) = VLQ.decode(stream);

UserOperation[] memory ops = new UserOperation[](len);

for (uint256 i = 0; i < len; i++) {
UserOperation memory op = ops[i];

(
op.sender,
stream,
bitStack
) = decodeAddress(stream, bitStack);

(op.nonce, stream) = VLQ.decode(stream);

bool hasInitCode;
(hasInitCode, bitStack) = BitStack.pop(bitStack);

if (hasInitCode) {
(op.initCode, stream) = decodeBytes(stream);
}

(op.callData, stream) = decodeBytes(stream);

bool usesDecompressAndPerform;
(usesDecompressAndPerform, bitStack) = BitStack.pop(bitStack);

if (usesDecompressAndPerform) {
op.callData = abi.encodeWithSignature("decompressAndPerform(bytes)", op.callData);
}

(op.callGasLimit, stream) = PseudoFloat.decode(stream);
(op.verificationGasLimit, stream) = PseudoFloat.decode(stream);
(op.preVerificationGas, stream) = PseudoFloat.decode(stream);
(op.maxFeePerGas, stream) = PseudoFloat.decode(stream);
(op.maxPriorityFeePerGas, stream) = PseudoFloat.decode(stream);

bool hasPaymaster;
(hasPaymaster, bitStack) = BitStack.pop(bitStack);

if (hasPaymaster) {
(op.paymasterAndData, stream) = decodeBytes(stream);
}

// op.signature is left empty because we're using aggregation
}

UserOpsPerAggregator[] memory bundle = new UserOpsPerAggregator[](1);

bundle[0] = UserOpsPerAggregator({
userOps: ops,
aggregator: aggregator,
signature: stream // signature is just the remaining bytes
});

entryPoint.handleAggregatedOps(bundle, beneficiary);

return hex"";
}

function decodeAddress(
bytes calldata stream,
uint256 bitStack
) internal view returns (
address,
bytes calldata,
uint256
) {
bool useRegistry;
(useRegistry, bitStack) = BitStack.pop(bitStack);

if (useRegistry) {
uint256 id;
(id, stream) = RegIndex.decode(stream);

return (registry.lookup(id), stream, bitStack);
}

return (address(bytes20(stream[:20])), stream[20:], bitStack);
}

function decodeBytes(
bytes calldata stream
) internal pure returns (bytes memory, bytes calldata) {
uint256 len;
(len, stream) = VLQ.decode(stream);
bytes memory bytes_ = stream[:len];
stream = stream[len:];

return (bytes_, stream);
}
}
Loading