mirror of
https://github.com/getwax/wax.git
synced 2026-01-08 22:57:58 -05:00
Update plugin contracts to use 4337 v0.7.0
This commit is contained in:
@@ -1,7 +1,8 @@
|
||||
{
|
||||
"gasFactor": "1",
|
||||
"port": "3000",
|
||||
"entryPoint": "0x5FF137D4b0FDCD49DcA30c7CF57E578a026d2789",
|
||||
"network": "http://127.0.0.1:8545",
|
||||
"entryPoint": "0x0000000071727De22E5E9d8BAf0edAc6f37da032",
|
||||
"beneficiary": "0xd21934eD8eAf27a67f0A70042Af50A1D6d195E81",
|
||||
"minBalance": "1",
|
||||
"mnemonic": "./workdir/mnemonic.txt",
|
||||
|
||||
@@ -4,6 +4,7 @@ out = "out"
|
||||
libs = [
|
||||
"lib",
|
||||
]
|
||||
solc_version = "0.8.23"
|
||||
|
||||
allow_paths = [
|
||||
"../../primitives",
|
||||
|
||||
@@ -16,7 +16,7 @@ function getRemappings() {
|
||||
|
||||
const config: HardhatUserConfig = {
|
||||
solidity: {
|
||||
version: "0.8.19",
|
||||
version: "0.8.23",
|
||||
settings: {
|
||||
optimizer: {
|
||||
enabled: true,
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
"@getwax/circuits": "../zkp"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@account-abstraction/contracts": "^0.6.0",
|
||||
"@account-abstraction/contracts": "0.7.0",
|
||||
"@account-abstraction/utils": "^0.6.0",
|
||||
"@nomicfoundation/hardhat-chai-matchers": "^2.0.0",
|
||||
"@nomicfoundation/hardhat-ethers": "^3.0.0",
|
||||
|
||||
@@ -3,7 +3,6 @@ import DeterministicDeployer from "../lib-ts/deterministic-deployer/Deterministi
|
||||
import {
|
||||
SimulateTxAccessor__factory,
|
||||
SafeProxyFactory__factory,
|
||||
TokenCallbackHandler__factory,
|
||||
CompatibilityFallbackHandler__factory,
|
||||
CreateCall__factory,
|
||||
MultiSend__factory,
|
||||
@@ -16,6 +15,8 @@ import {
|
||||
BLSOpen__factory,
|
||||
} from "../typechain-types";
|
||||
import makeDevFaster from "../test/e2e/utils/makeDevFaster";
|
||||
import { TokenCallbackHandler__factory } from "../typechain-types/factories/lib/safe-contracts/contracts/handler/TokenCallbackHandler__factory";
|
||||
import bundlerConfig from "./../config/bundler.config.json";
|
||||
|
||||
async function deploy() {
|
||||
const { NODE_URL, MNEMONIC } = process.env;
|
||||
@@ -36,12 +37,6 @@ async function deploy() {
|
||||
MultiSendCallOnly__factory,
|
||||
SignMessageLib__factory,
|
||||
BLSOpen__factory,
|
||||
DeterministicDeployer.link(BLSSignatureAggregator__factory, [
|
||||
{
|
||||
"lib/account-abstraction/contracts/samples/bls/lib/BLSOpen.sol:BLSOpen":
|
||||
deployer.calculateAddress(BLSOpen__factory, []),
|
||||
},
|
||||
]),
|
||||
];
|
||||
|
||||
for (const contractFactory of contractFactories) {
|
||||
@@ -51,6 +46,25 @@ async function deploy() {
|
||||
console.log(`deployed ${contractName} to ${await contract.getAddress()}`);
|
||||
}
|
||||
|
||||
const blsSignatureAggregatorFactory = DeterministicDeployer.link(
|
||||
BLSSignatureAggregator__factory,
|
||||
[
|
||||
{
|
||||
"lib/account-abstraction/contracts/samples/bls/lib/BLSOpen.sol:BLSOpen":
|
||||
deployer.calculateAddress(BLSOpen__factory, []),
|
||||
},
|
||||
],
|
||||
);
|
||||
const blsSignatureAggregator = await deployer.connectOrDeploy(
|
||||
blsSignatureAggregatorFactory,
|
||||
[bundlerConfig.entryPoint],
|
||||
);
|
||||
console.log(
|
||||
`deployed ${
|
||||
BLSSignatureAggregator__factory.name.split("_")[0]
|
||||
} to ${await blsSignatureAggregator.getAddress()}`,
|
||||
);
|
||||
|
||||
const safeDeployer = await DeterministicDeployer.initSafeVersion(wallet);
|
||||
|
||||
const safeContractFactories = [
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
DOCKER_NETWORK=packages-plugins-docker-network
|
||||
|
||||
GETH_IMAGE=ethereum/client-go:v1.13.5
|
||||
BUNDLER_IMAGE=patched-bundler-727838b
|
||||
BUNDLER_IMAGE=accountabstraction/bundler:0.7.0
|
||||
|
||||
GETH_CONTAINER=geth${RANDOM}
|
||||
BUNDLER_CONTAINER=bundler${RANDOM}
|
||||
@@ -78,13 +78,9 @@ docker exec ${GETH_CONTAINER} geth \
|
||||
# Deploy common contracts
|
||||
yarn hardhat run "${SCRIPT_DIR}/deploy_all.ts" --network localhost
|
||||
|
||||
if ! docker images | grep -q "${BUNDLER_IMAGE}"; then
|
||||
echo "Building '${BUNDLER_IMAGE}'..."
|
||||
# Build the Docker image from the Dockerfile
|
||||
docker build -f "${SCRIPT_DIR}/${BUNDLER_IMAGE}.dockerfile" -t "${BUNDLER_IMAGE}" .
|
||||
fi
|
||||
|
||||
# Start ERC-4337 bundler
|
||||
docker pull ${BUNDLER_IMAGE}
|
||||
|
||||
docker run --rm -i --name ${BUNDLER_CONTAINER} -p 3000:3000 -v "$PWD"/config:/app/workdir:ro --network=${DOCKER_NETWORK} ${BUNDLER_IMAGE} \
|
||||
--network http://${GETH_CONTAINER}:8545 \
|
||||
&
|
||||
|
||||
@@ -4,7 +4,7 @@ pragma abicoder v2;
|
||||
|
||||
import {HandlerContext} from "safe-contracts/contracts/handler/HandlerContext.sol";
|
||||
|
||||
import {IEntryPoint, UserOperation} from "account-abstraction/contracts/interfaces/IEntryPoint.sol";
|
||||
import {IEntryPoint, PackedUserOperation} from "account-abstraction/contracts/interfaces/IEntryPoint.sol";
|
||||
import {BLS} from "account-abstraction/contracts/samples/bls/lib/hubble-contracts/contracts/libs/BLS.sol";
|
||||
import {IBLSAccount} from "account-abstraction/contracts/samples/bls/IBLSAccount.sol";
|
||||
|
||||
@@ -59,7 +59,7 @@ contract SafeBlsPlugin is Safe4337Base, IBLSAccount {
|
||||
}
|
||||
|
||||
function _validateSignature(
|
||||
UserOperation calldata userOp,
|
||||
PackedUserOperation calldata userOp,
|
||||
bytes32 /* userOpHash */
|
||||
) internal view override returns (uint256) {
|
||||
uint256 initCodeLen = userOp.initCode.length;
|
||||
|
||||
@@ -4,11 +4,12 @@ pragma abicoder v2;
|
||||
|
||||
import {HandlerContext} from "safe-contracts/contracts/handler/HandlerContext.sol";
|
||||
|
||||
import {IEntryPoint, UserOperation} from "account-abstraction/contracts/interfaces/IEntryPoint.sol";
|
||||
import {IEntryPoint, PackedUserOperation} from "account-abstraction/contracts/interfaces/IEntryPoint.sol";
|
||||
import {BLS} from "account-abstraction/contracts/samples/bls/lib/hubble-contracts/contracts/libs/BLS.sol";
|
||||
import {IBLSAccount} from "account-abstraction/contracts/samples/bls/IBLSAccount.sol";
|
||||
import {MessageHashUtils} from "@openzeppelin/contracts/utils/cryptography/MessageHashUtils.sol";
|
||||
|
||||
import {Safe4337Base, ISafe} from "./utils/Safe4337Base.sol";
|
||||
import {Safe4337Base, ISafe, SIG_VALIDATION_FAILED} from "./utils/Safe4337Base.sol";
|
||||
import {WaxLib as W} from "../compression/WaxLib.sol";
|
||||
import {IDecompressor} from "../compression/decompressors/IDecompressor.sol";
|
||||
|
||||
@@ -38,12 +39,10 @@ contract SafeCompressionPlugin is Safe4337Base, IBLSAccount {
|
||||
_decompressor = decompressorParam;
|
||||
}
|
||||
|
||||
function decompressAndPerform(
|
||||
bytes calldata stream
|
||||
) public {
|
||||
function decompressAndPerform(bytes calldata stream) public {
|
||||
_requireFromEntryPoint();
|
||||
|
||||
(W.Action[] memory actions,) = _decompressor.decompress(stream);
|
||||
(W.Action[] memory actions, ) = _decompressor.decompress(stream);
|
||||
|
||||
ISafe safe = _currentSafe();
|
||||
|
||||
@@ -57,9 +56,7 @@ contract SafeCompressionPlugin is Safe4337Base, IBLSAccount {
|
||||
}
|
||||
}
|
||||
|
||||
function setDecompressor(
|
||||
IDecompressor decompressorParam
|
||||
) public {
|
||||
function setDecompressor(IDecompressor decompressorParam) public {
|
||||
_requireFromCurrentSafeOrEntryPoint();
|
||||
_decompressor = decompressorParam;
|
||||
}
|
||||
@@ -77,14 +74,15 @@ contract SafeCompressionPlugin is Safe4337Base, IBLSAccount {
|
||||
}
|
||||
|
||||
function _validateSignature(
|
||||
UserOperation calldata userOp,
|
||||
PackedUserOperation calldata userOp,
|
||||
bytes32 /* userOpHash */
|
||||
) internal view override returns (uint256) {
|
||||
uint256 initCodeLen = userOp.initCode.length;
|
||||
|
||||
if (initCodeLen > 0) {
|
||||
bytes32 claimedKeyHash =
|
||||
keccak256(userOp.initCode[initCodeLen - 128:]);
|
||||
bytes32 claimedKeyHash = keccak256(
|
||||
userOp.initCode[initCodeLen - 128:]
|
||||
);
|
||||
|
||||
// See appendKeyToInitCode.ts for a detailed explanation.
|
||||
require(
|
||||
|
||||
@@ -2,11 +2,12 @@
|
||||
pragma solidity >=0.7.0 <0.9.0;
|
||||
pragma abicoder v2;
|
||||
|
||||
import {Safe4337Base} from "./utils/Safe4337Base.sol";
|
||||
import {IEntryPoint, UserOperation} from "account-abstraction/contracts/interfaces/IEntryPoint.sol";
|
||||
import {UserOperation} from "account-abstraction/contracts/interfaces/IEntryPoint.sol";
|
||||
import {Safe4337Base, SIG_VALIDATION_FAILED} from "./utils/Safe4337Base.sol";
|
||||
import {IEntryPoint, PackedUserOperation} from "account-abstraction/contracts/interfaces/IEntryPoint.sol";
|
||||
import {PackedUserOperation} from "account-abstraction/contracts/interfaces/IEntryPoint.sol";
|
||||
|
||||
import {ECDSA} from "openzeppelin-contracts/contracts/utils/cryptography/ECDSA.sol";
|
||||
import {MessageHashUtils} from "@openzeppelin/contracts/utils/cryptography/MessageHashUtils.sol";
|
||||
|
||||
interface ISafe {
|
||||
function enableModule(address module) external;
|
||||
@@ -88,11 +89,11 @@ contract SafeECDSAPlugin is Safe4337Base {
|
||||
}
|
||||
|
||||
function _validateSignature(
|
||||
UserOperation calldata userOp,
|
||||
PackedUserOperation calldata userOp,
|
||||
bytes32 userOpHash
|
||||
) internal view override returns (uint256 validationData) {
|
||||
address keyOwner = ecdsaOwnerStorage[msg.sender].owner;
|
||||
bytes32 hash = userOpHash.toEthSignedMessageHash();
|
||||
bytes32 hash = MessageHashUtils.toEthSignedMessageHash(userOpHash);
|
||||
|
||||
if (keyOwner != hash.recover(userOp.signature)) {
|
||||
return SIG_VALIDATION_FAILED;
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
pragma solidity ^0.8.0;
|
||||
|
||||
import {ECDSA} from "openzeppelin-contracts/contracts/utils/cryptography/ECDSA.sol";
|
||||
import {MessageHashUtils} from "@openzeppelin/contracts/utils/cryptography/MessageHashUtils.sol";
|
||||
|
||||
contract Enum {
|
||||
enum Operation {
|
||||
@@ -114,7 +115,9 @@ contract SafeECDSARecoveryPlugin {
|
||||
}
|
||||
|
||||
bytes32 currentOwnerHash = keccak256(abi.encodePacked(currentOwner));
|
||||
bytes32 ethSignedHash = currentOwnerHash.toEthSignedMessageHash();
|
||||
bytes32 ethSignedHash = MessageHashUtils.toEthSignedMessageHash(
|
||||
currentOwnerHash
|
||||
);
|
||||
|
||||
if (newOwner != ethSignedHash.recover(newOwnerSignature))
|
||||
revert INVALID_NEW_OWNER_SIGNATURE();
|
||||
|
||||
@@ -3,10 +3,10 @@ pragma solidity >=0.8.0 <0.9.0;
|
||||
|
||||
import {HandlerContext} from "safe-contracts/contracts/handler/HandlerContext.sol";
|
||||
import {BaseAccount} from "account-abstraction/contracts/core/BaseAccount.sol";
|
||||
import {IEntryPoint, UserOperation} from "account-abstraction/contracts/interfaces/IEntryPoint.sol";
|
||||
import {IEntryPoint, PackedUserOperation} from "account-abstraction/contracts/interfaces/IEntryPoint.sol";
|
||||
import {WebAuthn} from "../primitives/WebAuthn.sol";
|
||||
|
||||
import {Safe4337Base} from "./utils/Safe4337Base.sol";
|
||||
import {Safe4337Base, SIG_VALIDATION_FAILED} from "./utils/Safe4337Base.sol";
|
||||
|
||||
interface ISafe {
|
||||
function enableModule(address module) external;
|
||||
@@ -66,7 +66,7 @@ contract SafeWebAuthnPlugin is Safe4337Base, WebAuthn {
|
||||
}
|
||||
|
||||
function _validateSignature(
|
||||
UserOperation calldata userOp,
|
||||
PackedUserOperation calldata userOp,
|
||||
bytes32 /*userOpHash*/
|
||||
) internal override returns (uint256 validationData) {
|
||||
bytes calldata authenticatorData;
|
||||
|
||||
@@ -4,8 +4,8 @@ pragma abicoder v2;
|
||||
|
||||
import {IGroth16Verifier} from "./interface/IGroth16Verifier.sol";
|
||||
import {ISafe} from "./interface/ISafe.sol";
|
||||
import {Safe4337Base} from "./utils/Safe4337Base.sol";
|
||||
import {IEntryPoint, UserOperation} from "account-abstraction/contracts/interfaces/IEntryPoint.sol";
|
||||
import {Safe4337Base, SIG_VALIDATION_FAILED} from "./utils/Safe4337Base.sol";
|
||||
import {IEntryPoint, PackedUserOperation} from "account-abstraction/contracts/interfaces/IEntryPoint.sol";
|
||||
|
||||
struct ZKPPasswordOwnerStorage {
|
||||
address owner;
|
||||
@@ -76,7 +76,7 @@ contract SafeZKPPasswordPlugin is Safe4337Base {
|
||||
}
|
||||
|
||||
function _validateSignature(
|
||||
UserOperation calldata userOp,
|
||||
PackedUserOperation calldata userOp,
|
||||
bytes32 userOpHash
|
||||
) internal view override returns (uint256 validationData) {
|
||||
// TODO (merge-ok) There is likely a more efficient way to encode this
|
||||
|
||||
@@ -3,7 +3,7 @@ pragma solidity >=0.7.0 <0.9.0;
|
||||
pragma abicoder v2;
|
||||
|
||||
import {HandlerContext} from "safe-contracts/contracts/handler/HandlerContext.sol";
|
||||
|
||||
import {SIG_VALIDATION_FAILED} from "account-abstraction/contracts/core/Helpers.sol";
|
||||
import {BaseAccount} from "account-abstraction/contracts/core/BaseAccount.sol";
|
||||
|
||||
interface ISafe {
|
||||
|
||||
@@ -7,13 +7,14 @@ import {
|
||||
import { setupTests } from "./utils/setupTests";
|
||||
import receiptOf from "./utils/receiptOf";
|
||||
import {
|
||||
generateInitCodeAndAddress,
|
||||
generateFactoryParamsAndAddress,
|
||||
createUserOperation,
|
||||
} from "./utils/createUserOp";
|
||||
import { getSigners } from "./utils/getSigners";
|
||||
import getBlsUserOpHash from "./utils/getBlsUserOpHash";
|
||||
import appendKeyToInitCode from "./utils/appendKeyToInitCode";
|
||||
import setupBls from "./utils/setupBls";
|
||||
import { packUserOp } from "./utils/userOpUtils";
|
||||
|
||||
const BLS_PRIVATE_KEY =
|
||||
"0xdbe3d601b1b25c42c50015a87855fdce00ea9b3a7e33c92d31c69aeb70708e08";
|
||||
@@ -58,41 +59,46 @@ describe("SafeBlsPlugin", () => {
|
||||
[recipientAddress, transferAmount, "0x"],
|
||||
);
|
||||
|
||||
let { initCode, deployedAddress } = await generateInitCodeAndAddress(
|
||||
admin,
|
||||
owner,
|
||||
safeBlsPlugin,
|
||||
safeSingleton,
|
||||
safeProxyFactory,
|
||||
);
|
||||
let { factoryParams, deployedAddress } =
|
||||
await generateFactoryParamsAndAddress(
|
||||
admin,
|
||||
owner,
|
||||
safeBlsPlugin,
|
||||
safeSingleton,
|
||||
safeProxyFactory,
|
||||
);
|
||||
|
||||
initCode = appendKeyToInitCode(initCode, blsSigner.pubkey);
|
||||
factoryParams.factoryData = appendKeyToInitCode(
|
||||
factoryParams.factoryData,
|
||||
blsSigner.pubkey,
|
||||
);
|
||||
|
||||
const unsignedUserOperation = await createUserOperation(
|
||||
provider,
|
||||
bundlerProvider,
|
||||
deployedAddress,
|
||||
initCode,
|
||||
factoryParams,
|
||||
userOpCallData,
|
||||
entryPointAddress,
|
||||
"0x",
|
||||
);
|
||||
const packedUserOperation = packUserOp(unsignedUserOperation);
|
||||
|
||||
const blsUserOpHash = getBlsUserOpHash(
|
||||
(await provider.getNetwork()).chainId,
|
||||
await blsSignatureAggregator.getAddress(),
|
||||
blsSigner.pubkey,
|
||||
unsignedUserOperation,
|
||||
packedUserOperation,
|
||||
entryPointAddress,
|
||||
);
|
||||
|
||||
const aggReportedUserOpHash = await blsSignatureAggregator.getUserOpHash(
|
||||
unsignedUserOperation,
|
||||
);
|
||||
const aggReportedUserOpHash =
|
||||
await blsSignatureAggregator.getUserOpHash(packedUserOperation);
|
||||
|
||||
expect(blsUserOpHash).to.equal(aggReportedUserOpHash);
|
||||
|
||||
const userOperation = {
|
||||
...unsignedUserOperation,
|
||||
...packedUserOperation,
|
||||
signature: solidityPacked(
|
||||
["uint256[2]"],
|
||||
[blsSigner.sign(blsUserOpHash)],
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { expect } from "chai";
|
||||
import { AbiCoder, ethers, solidityPacked } from "ethers";
|
||||
import { ethers, solidityPacked } from "ethers";
|
||||
import {
|
||||
AddressRegistry__factory,
|
||||
EntryPoint__factory,
|
||||
@@ -12,6 +12,7 @@ import { setupTests } from "./utils/setupTests";
|
||||
import { createUserOperation } from "./utils/createUserOp";
|
||||
import setupBls from "./utils/setupBls";
|
||||
import getBlsUserOpHash from "./utils/getBlsUserOpHash";
|
||||
import { packUserOp } from "./utils/userOpUtils";
|
||||
|
||||
const BLS_PRIVATE_KEY =
|
||||
"0xdbe3d601b1b25c42c50015a87855fdce00ea9b3a7e33c92d31c69aeb70708e08";
|
||||
@@ -41,7 +42,6 @@ describe("SafeCompressionPlugin", () => {
|
||||
SafeCompressionFactory__factory,
|
||||
[],
|
||||
);
|
||||
await safeCompressionFactory.waitForDeployment();
|
||||
|
||||
const addressRegistry = await deployer.connectOrDeploy(
|
||||
AddressRegistry__factory,
|
||||
@@ -92,11 +92,14 @@ describe("SafeCompressionPlugin", () => {
|
||||
[compressedActions],
|
||||
);
|
||||
|
||||
// Note: initCode is not used because we need to create both the safe
|
||||
// Note: factoryParams is not used because we need to create both the safe
|
||||
// proxy and the plugin, and 4337 currently only allows one contract
|
||||
// creation in this step. Since we need an extra step anyway, it's simpler
|
||||
// to do the whole create outside of 4337.
|
||||
const initCode = "0x";
|
||||
const factoryParams = {
|
||||
factory: "0x",
|
||||
factoryData: "0x",
|
||||
};
|
||||
|
||||
// Native tokens for the pre-fund
|
||||
await receiptOf(
|
||||
@@ -112,27 +115,28 @@ describe("SafeCompressionPlugin", () => {
|
||||
provider,
|
||||
bundlerProvider,
|
||||
accountAddress,
|
||||
initCode,
|
||||
factoryParams,
|
||||
userOpCallData,
|
||||
entryPointAddress,
|
||||
"0x",
|
||||
);
|
||||
const packedUserOperation = packUserOp(unsignedUserOperation);
|
||||
|
||||
const blsUserOpHash = getBlsUserOpHash(
|
||||
(await provider.getNetwork()).chainId,
|
||||
await blsSignatureAggregator.getAddress(),
|
||||
blsSigner.pubkey,
|
||||
unsignedUserOperation,
|
||||
packedUserOperation,
|
||||
entryPointAddress,
|
||||
);
|
||||
|
||||
const aggReportedUserOpHash = await blsSignatureAggregator.getUserOpHash(
|
||||
unsignedUserOperation,
|
||||
);
|
||||
const aggReportedUserOpHash =
|
||||
await blsSignatureAggregator.getUserOpHash(packedUserOperation);
|
||||
|
||||
expect(blsUserOpHash).to.equal(aggReportedUserOpHash);
|
||||
|
||||
const userOperation = {
|
||||
...unsignedUserOperation,
|
||||
...packedUserOperation,
|
||||
signature: solidityPacked(
|
||||
["uint256[2]"],
|
||||
[blsSigner.sign(blsUserOpHash)],
|
||||
|
||||
@@ -64,11 +64,14 @@ describe("SafeECDSAPlugin", () => {
|
||||
[recipient.address, transferAmount, "0x00"],
|
||||
);
|
||||
|
||||
// Note: initCode is not used because we need to create both the safe
|
||||
// Note: factoryParams is not used because we need to create both the safe
|
||||
// proxy and the plugin, and 4337 currently only allows one contract
|
||||
// creation in this step. Since we need an extra step anyway, it's simpler
|
||||
// to do the whole create outside of 4337.
|
||||
const initCode = "0x";
|
||||
const factoryParams = {
|
||||
factory: "0x",
|
||||
factoryData: "0x",
|
||||
};
|
||||
|
||||
// Send userOp
|
||||
await createAndSendUserOpWithEcdsaSig(
|
||||
@@ -76,7 +79,7 @@ describe("SafeECDSAPlugin", () => {
|
||||
bundlerProvider,
|
||||
owner,
|
||||
accountAddress,
|
||||
initCode,
|
||||
factoryParams,
|
||||
userOpCallData,
|
||||
entryPointAddress,
|
||||
dummySignature,
|
||||
|
||||
@@ -170,7 +170,10 @@ describe("SafeECDSARecoveryPlugin", () => {
|
||||
[await recoveryPlugin.getAddress(), "0x00", addRecoveryAccountCalldata],
|
||||
);
|
||||
|
||||
const initCode = "0x";
|
||||
const factoryParams = {
|
||||
factory: "0x",
|
||||
factoryData: "0x",
|
||||
};
|
||||
const dummySignature = await owner.signMessage("dummy sig");
|
||||
|
||||
// Send userOp to add recovery account
|
||||
@@ -179,7 +182,7 @@ describe("SafeECDSARecoveryPlugin", () => {
|
||||
bundlerProvider,
|
||||
owner,
|
||||
safeProxyAddress,
|
||||
initCode,
|
||||
factoryParams,
|
||||
userOpCallData,
|
||||
entryPointAddress,
|
||||
dummySignature,
|
||||
@@ -236,7 +239,7 @@ describe("SafeECDSARecoveryPlugin", () => {
|
||||
bundlerProvider,
|
||||
newEcdsaPluginSigner,
|
||||
safeProxyAddress,
|
||||
initCode,
|
||||
factoryParams,
|
||||
userOpCallData,
|
||||
entryPointAddress,
|
||||
dummySignature,
|
||||
@@ -317,7 +320,10 @@ describe("SafeECDSARecoveryPlugin", () => {
|
||||
[await recoveryPlugin.getAddress(), "0x00", addRecoveryAccountCalldata],
|
||||
);
|
||||
|
||||
const initCode = "0x";
|
||||
const factoryParams = {
|
||||
factory: "0x",
|
||||
factoryData: "0x",
|
||||
};
|
||||
const dummySignature = await owner.signMessage("dummy sig");
|
||||
|
||||
// Send userOp to add recovery account
|
||||
@@ -326,7 +332,7 @@ describe("SafeECDSARecoveryPlugin", () => {
|
||||
bundlerProvider,
|
||||
owner,
|
||||
safeProxyAddress,
|
||||
initCode,
|
||||
factoryParams,
|
||||
userOpCallData,
|
||||
entryPointAddress,
|
||||
dummySignature,
|
||||
@@ -380,7 +386,7 @@ describe("SafeECDSARecoveryPlugin", () => {
|
||||
bundlerProvider,
|
||||
guardianSigner,
|
||||
guardianSimpleAccountAddress,
|
||||
initCode,
|
||||
factoryParams,
|
||||
userOpCallData,
|
||||
entryPointAddress,
|
||||
dummySignature,
|
||||
@@ -403,7 +409,7 @@ describe("SafeECDSARecoveryPlugin", () => {
|
||||
bundlerProvider,
|
||||
newEcdsaPluginSigner,
|
||||
safeProxyAddress,
|
||||
initCode,
|
||||
factoryParams,
|
||||
userOpCallData,
|
||||
entryPointAddress,
|
||||
dummySignature,
|
||||
|
||||
@@ -4,7 +4,7 @@ import sendUserOpAndWait from "./utils/sendUserOpAndWait";
|
||||
import { setupTests } from "./utils/setupTests";
|
||||
import { SafeWebAuthnPlugin__factory } from "../../typechain-types";
|
||||
import {
|
||||
generateInitCodeAndAddress,
|
||||
generateFactoryParamsAndAddress,
|
||||
createUserOperation,
|
||||
} from "./utils/createUserOp";
|
||||
import { getSigners } from "./utils/getSigners";
|
||||
@@ -91,14 +91,14 @@ describe.skip("SafeWebAuthnPlugin", () => {
|
||||
"execTransaction",
|
||||
[recipientAddress, transferAmount, "0x00"],
|
||||
);
|
||||
|
||||
const { initCode, deployedAddress } = await generateInitCodeAndAddress(
|
||||
admin,
|
||||
owner,
|
||||
safeWebAuthnPlugin,
|
||||
safeSingleton,
|
||||
safeProxyFactory,
|
||||
);
|
||||
const { factoryParams, deployedAddress } =
|
||||
await generateFactoryParamsAndAddress(
|
||||
admin,
|
||||
owner,
|
||||
safeWebAuthnPlugin,
|
||||
safeSingleton,
|
||||
safeProxyFactory,
|
||||
);
|
||||
|
||||
const recipientBalanceBefore = await provider.getBalance(recipientAddress);
|
||||
|
||||
@@ -106,7 +106,7 @@ describe.skip("SafeWebAuthnPlugin", () => {
|
||||
provider,
|
||||
bundlerProvider,
|
||||
deployedAddress,
|
||||
initCode,
|
||||
factoryParams,
|
||||
userOpCallData,
|
||||
entryPointAddress,
|
||||
userOpSignature,
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import { getUserOpHash } from "@account-abstraction/utils";
|
||||
import { ERC4337ZKPPasswordClient } from "@getwax/circuits";
|
||||
import { expect } from "chai";
|
||||
import { resolveProperties, ethers } from "ethers";
|
||||
@@ -11,6 +10,7 @@ import {
|
||||
} from "../../typechain-types";
|
||||
import { setupTests } from "./utils/setupTests";
|
||||
import { createUserOperation } from "./utils/createUserOp";
|
||||
import { getUserOpHash } from "./utils/userOpUtils";
|
||||
|
||||
describe("SafeZKPPasswordPlugin", () => {
|
||||
it("should pass the ERC4337 validation", async () => {
|
||||
@@ -85,17 +85,20 @@ describe("SafeZKPPasswordPlugin", () => {
|
||||
],
|
||||
);
|
||||
|
||||
// Note: initCode is not used because we need to create both the safe
|
||||
// Note: factoryParams is not used because we need to create both the safe
|
||||
// proxy and the plugin, and 4337 currently only allows one contract
|
||||
// creation in this step. Since we need an extra step anyway, it's simpler
|
||||
// to do the whole create outside of 4337.
|
||||
const initCode = "0x";
|
||||
const factoryParams = {
|
||||
factory: "0x",
|
||||
factoryData: "0x",
|
||||
};
|
||||
|
||||
const unsignedUserOperation = await createUserOperation(
|
||||
provider,
|
||||
bundlerProvider,
|
||||
accountAddress,
|
||||
initCode,
|
||||
factoryParams,
|
||||
userOpCallData,
|
||||
entryPointAddress,
|
||||
dummySignature,
|
||||
|
||||
@@ -51,7 +51,6 @@ describe("SafeZkEmailRecoveryPlugin", () => {
|
||||
SafeECDSAFactory__factory,
|
||||
[],
|
||||
);
|
||||
await safeECDSAFactory.waitForDeployment();
|
||||
|
||||
const createArgs = [
|
||||
safeSingleton,
|
||||
@@ -94,7 +93,6 @@ describe("SafeZkEmailRecoveryPlugin", () => {
|
||||
await defaultDkimRegistry.getAddress(),
|
||||
],
|
||||
);
|
||||
await recoveryPlugin.waitForDeployment();
|
||||
});
|
||||
|
||||
it("Should use recovery plugin via EOA and then send tx with new key.", async () => {
|
||||
@@ -173,7 +171,10 @@ describe("SafeZkEmailRecoveryPlugin", () => {
|
||||
[await recoveryPlugin.getAddress(), "0x00", configureRecoveryCalldata],
|
||||
);
|
||||
|
||||
const initCode = "0x";
|
||||
const factoryParams = {
|
||||
factory: "0x",
|
||||
factoryData: "0x",
|
||||
};
|
||||
const dummySignature = await owner.signMessage("dummy sig");
|
||||
|
||||
// Send userOp to add recovery account
|
||||
@@ -182,7 +183,7 @@ describe("SafeZkEmailRecoveryPlugin", () => {
|
||||
bundlerProvider,
|
||||
owner,
|
||||
safeProxyAddress,
|
||||
initCode,
|
||||
factoryParams,
|
||||
userOpCallData,
|
||||
entryPointAddress,
|
||||
dummySignature,
|
||||
@@ -291,7 +292,7 @@ describe("SafeZkEmailRecoveryPlugin", () => {
|
||||
bundlerProvider,
|
||||
newEcdsaPluginSigner,
|
||||
safeProxyAddress,
|
||||
initCode,
|
||||
factoryParams,
|
||||
userOpCallData,
|
||||
entryPointAddress,
|
||||
dummySignature,
|
||||
@@ -393,7 +394,10 @@ describe("SafeZkEmailRecoveryPlugin", () => {
|
||||
[await recoveryPlugin.getAddress(), "0x00", addRecoveryHashCalldata],
|
||||
);
|
||||
|
||||
const initCode = "0x";
|
||||
const factoryParams = {
|
||||
factory: "0x",
|
||||
factoryData: "0x",
|
||||
};
|
||||
const dummySignature = await owner.signMessage("dummy sig");
|
||||
|
||||
// Send userOp to add recovery account
|
||||
@@ -402,7 +406,7 @@ describe("SafeZkEmailRecoveryPlugin", () => {
|
||||
bundlerProvider,
|
||||
owner,
|
||||
safeProxyAddress,
|
||||
initCode,
|
||||
factoryParams,
|
||||
userOpCallData,
|
||||
entryPointAddress,
|
||||
dummySignature,
|
||||
@@ -471,7 +475,7 @@ describe("SafeZkEmailRecoveryPlugin", () => {
|
||||
bundlerProvider,
|
||||
otherAccount,
|
||||
otherSimpleAccountAddress,
|
||||
initCode,
|
||||
factoryParams,
|
||||
userOpCallData,
|
||||
entryPointAddress,
|
||||
dummySignature,
|
||||
@@ -508,7 +512,7 @@ describe("SafeZkEmailRecoveryPlugin", () => {
|
||||
bundlerProvider,
|
||||
otherAccount,
|
||||
otherSimpleAccountAddress,
|
||||
initCode,
|
||||
factoryParams,
|
||||
userOpCallData,
|
||||
entryPointAddress,
|
||||
dummySignature,
|
||||
@@ -531,7 +535,7 @@ describe("SafeZkEmailRecoveryPlugin", () => {
|
||||
bundlerProvider,
|
||||
newEcdsaPluginSigner,
|
||||
safeProxyAddress,
|
||||
initCode,
|
||||
factoryParams,
|
||||
userOpCallData,
|
||||
entryPointAddress,
|
||||
dummySignature,
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
import { ethers, getBytes, NonceManager, Signer } from "ethers";
|
||||
import { AddressZero } from "@ethersproject/constants";
|
||||
import { UserOperationStruct } from "@account-abstraction/contracts";
|
||||
import { getUserOpHash } from "@account-abstraction/utils";
|
||||
|
||||
import { SafeProxyFactory } from "../../../typechain-types/lib/safe-contracts/contracts/proxies/SafeProxyFactory";
|
||||
import { Safe } from "../../../typechain-types/lib/safe-contracts/contracts/Safe";
|
||||
@@ -14,10 +12,17 @@ import receiptOf from "./receiptOf";
|
||||
import { calculateProxyAddress } from "./calculateProxyAddress";
|
||||
import { getGasEstimates } from "./getGasEstimates";
|
||||
import sendUserOpAndWait from "./sendUserOpAndWait";
|
||||
import {
|
||||
FactoryParams,
|
||||
getUserOpHash,
|
||||
PackedUserOperation,
|
||||
packUserOp,
|
||||
UserOperation,
|
||||
} from "./userOpUtils";
|
||||
|
||||
type Plugin = SafeBlsPlugin | SafeWebAuthnPlugin;
|
||||
|
||||
export const generateInitCodeAndAddress = async (
|
||||
export const generateFactoryParamsAndAddress = async (
|
||||
admin: NonceManager,
|
||||
owner: NonceManager,
|
||||
plugin: Plugin,
|
||||
@@ -58,25 +63,24 @@ export const generateInitCodeAndAddress = async (
|
||||
}),
|
||||
);
|
||||
|
||||
// The initCode contains 20 bytes of the factory address and the rest is the
|
||||
// calldata to be forwarded
|
||||
const initCode = ethers.concat([
|
||||
factoryAddress,
|
||||
safeProxyFactory.interface.encodeFunctionData("createProxyWithNonce", [
|
||||
singletonAddress,
|
||||
encodedInitializer,
|
||||
73,
|
||||
]),
|
||||
]);
|
||||
const factoryData = safeProxyFactory.interface.encodeFunctionData(
|
||||
"createProxyWithNonce",
|
||||
[singletonAddress, encodedInitializer, 73],
|
||||
);
|
||||
|
||||
return { initCode, deployedAddress };
|
||||
const factoryParams = {
|
||||
factory: factoryAddress,
|
||||
factoryData,
|
||||
};
|
||||
|
||||
return { factoryParams, deployedAddress };
|
||||
};
|
||||
|
||||
export const createUserOperation = async (
|
||||
provider: ethers.JsonRpcProvider,
|
||||
bundlerProvider: ethers.JsonRpcProvider,
|
||||
accountAddress: string,
|
||||
initCode: string,
|
||||
factoryParams: FactoryParams,
|
||||
userOpCallData: string,
|
||||
entryPointAddress: string,
|
||||
dummySignature: string,
|
||||
@@ -88,16 +92,19 @@ export const createUserOperation = async (
|
||||
const nonce = await entryPoint.getNonce(accountAddress, "0x00");
|
||||
const nonceHex = "0x0" + nonce.toString();
|
||||
|
||||
const userOperationWithoutGasFields = {
|
||||
let userOp: Partial<UserOperation> = {
|
||||
sender: accountAddress,
|
||||
nonce: nonceHex,
|
||||
initCode,
|
||||
callData: userOpCallData,
|
||||
callGasLimit: "0x00",
|
||||
paymasterAndData: "0x",
|
||||
signature: dummySignature,
|
||||
};
|
||||
|
||||
if (factoryParams.factory !== "0x") {
|
||||
userOp.factory = factoryParams.factory;
|
||||
userOp.factoryData = factoryParams.factoryData;
|
||||
}
|
||||
|
||||
const {
|
||||
callGasLimit,
|
||||
verificationGasLimit,
|
||||
@@ -107,23 +114,23 @@ export const createUserOperation = async (
|
||||
} = await getGasEstimates(
|
||||
provider,
|
||||
bundlerProvider,
|
||||
userOperationWithoutGasFields,
|
||||
userOp,
|
||||
entryPointAddress,
|
||||
);
|
||||
|
||||
const unsignedUserOperation = {
|
||||
sender: accountAddress,
|
||||
nonce: nonceHex,
|
||||
initCode,
|
||||
factory: userOp.factory,
|
||||
factoryData: userOp.factoryData,
|
||||
callData: userOpCallData,
|
||||
callGasLimit,
|
||||
verificationGasLimit,
|
||||
preVerificationGas,
|
||||
maxFeePerGas,
|
||||
maxPriorityFeePerGas,
|
||||
paymasterAndData: "0x",
|
||||
signature: dummySignature,
|
||||
} satisfies UserOperationStruct;
|
||||
} satisfies UserOperation;
|
||||
|
||||
return await ethers.resolveProperties(unsignedUserOperation);
|
||||
};
|
||||
@@ -133,7 +140,7 @@ export const createAndSendUserOpWithEcdsaSig = async (
|
||||
bundlerProvider: ethers.JsonRpcProvider,
|
||||
owner: Signer,
|
||||
accountAddress: string,
|
||||
initCode: string,
|
||||
factoryParams: FactoryParams,
|
||||
userOpCallData: string,
|
||||
entryPointAddress: string,
|
||||
dummySignature: string,
|
||||
@@ -142,7 +149,7 @@ export const createAndSendUserOpWithEcdsaSig = async (
|
||||
provider,
|
||||
bundlerProvider,
|
||||
accountAddress,
|
||||
initCode,
|
||||
factoryParams,
|
||||
userOpCallData,
|
||||
entryPointAddress,
|
||||
dummySignature,
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
import { ethers, keccak256 } from "ethers";
|
||||
import { ResolvedUserOp } from "./resolveUserOp";
|
||||
import { solG2 } from "@thehubbleproject/bls/dist/mcl";
|
||||
import { PackedUserOperation } from "./userOpUtils";
|
||||
|
||||
export default function getBlsUserOpHash(
|
||||
chainId: bigint,
|
||||
aggregatorAddress: string,
|
||||
publicKey: solG2,
|
||||
userOp: ResolvedUserOp,
|
||||
userOp: PackedUserOperation,
|
||||
entryPointAddress: string,
|
||||
): string {
|
||||
const abi = ethers.AbiCoder.defaultAbiCoder();
|
||||
|
||||
@@ -14,8 +15,14 @@ export default function getBlsUserOpHash(
|
||||
|
||||
return keccak256(
|
||||
abi.encode(
|
||||
["bytes32", "bytes32", "address", "uint256"],
|
||||
[internalUserOpHash(userOp), publicKeyHash, aggregatorAddress, chainId],
|
||||
["bytes32", "bytes32", "address", "uint256", "address"],
|
||||
[
|
||||
internalUserOpHash(userOp),
|
||||
publicKeyHash,
|
||||
aggregatorAddress,
|
||||
chainId,
|
||||
entryPointAddress,
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
@@ -26,7 +33,7 @@ export default function getBlsUserOpHash(
|
||||
* (Also the same as UserOperationLib.hash, but *different* from EntryPoint's
|
||||
* getUserOpHash and BLSSignatureAggregator's getUserOpHash.)
|
||||
*/
|
||||
function internalUserOpHash(userOp: ResolvedUserOp): string {
|
||||
function internalUserOpHash(userOp: PackedUserOperation): string {
|
||||
const abi = ethers.AbiCoder.defaultAbiCoder();
|
||||
|
||||
return keccak256(
|
||||
@@ -36,11 +43,9 @@ function internalUserOpHash(userOp: ResolvedUserOp): string {
|
||||
"uint256", // userOp.nonce,
|
||||
"bytes32", // keccak256(userOp.initCode),
|
||||
"bytes32", // keccak256(userOp.callData),
|
||||
"uint256", // userOp.callGasLimit,
|
||||
"uint256", // userOp.verificationGasLimit,
|
||||
"bytes32", // userOp.accountGasLimits,
|
||||
"uint256", // userOp.preVerificationGas,
|
||||
"uint256", // userOp.maxFeePerGas,
|
||||
"uint256", // userOp.maxPriorityFeePerGas,
|
||||
"bytes32", // userOp.gasFees,
|
||||
"bytes32", // keccak256(userOp.paymasterAndData),
|
||||
],
|
||||
[
|
||||
@@ -48,11 +53,9 @@ function internalUserOpHash(userOp: ResolvedUserOp): string {
|
||||
userOp.nonce,
|
||||
keccak256(userOp.initCode),
|
||||
keccak256(userOp.callData),
|
||||
userOp.callGasLimit,
|
||||
userOp.verificationGasLimit,
|
||||
userOp.accountGasLimits,
|
||||
userOp.preVerificationGas,
|
||||
userOp.maxFeePerGas,
|
||||
userOp.maxPriorityFeePerGas,
|
||||
userOp.gasFees,
|
||||
keccak256(userOp.paymasterAndData),
|
||||
],
|
||||
),
|
||||
|
||||
@@ -1,14 +1,15 @@
|
||||
import { ethers } from "ethers";
|
||||
import { UserOperation } from "./userOpUtils";
|
||||
|
||||
export const getGasEstimates = async (
|
||||
provider: ethers.JsonRpcProvider,
|
||||
bundlerProvider: ethers.JsonRpcProvider,
|
||||
userOperationWithoutGasFields: any,
|
||||
partialUserOperation: Partial<UserOperation>,
|
||||
entryPointAddress: string,
|
||||
) => {
|
||||
const gasEstimate = (await bundlerProvider.send(
|
||||
"eth_estimateUserOperationGas",
|
||||
[userOperationWithoutGasFields, entryPointAddress],
|
||||
[partialUserOperation, entryPointAddress],
|
||||
)) as {
|
||||
verificationGasLimit: string;
|
||||
preVerificationGas: string;
|
||||
|
||||
@@ -1,23 +0,0 @@
|
||||
import { UserOperationStruct } from "@account-abstraction/contracts";
|
||||
|
||||
export default async function resolveUserOp(
|
||||
userOp: UserOperationStruct,
|
||||
): Promise<ResolvedUserOp> {
|
||||
return {
|
||||
sender: await userOp.sender,
|
||||
nonce: await userOp.nonce,
|
||||
initCode: await userOp.initCode,
|
||||
callData: await userOp.callData,
|
||||
callGasLimit: await userOp.callGasLimit,
|
||||
verificationGasLimit: await userOp.verificationGasLimit,
|
||||
preVerificationGas: await userOp.preVerificationGas,
|
||||
maxFeePerGas: await userOp.maxFeePerGas,
|
||||
maxPriorityFeePerGas: await userOp.maxPriorityFeePerGas,
|
||||
paymasterAndData: await userOp.paymasterAndData,
|
||||
signature: await userOp.signature,
|
||||
};
|
||||
}
|
||||
|
||||
export type ResolvedUserOp = {
|
||||
[K in keyof UserOperationStruct]: Awaited<UserOperationStruct[K]>;
|
||||
};
|
||||
@@ -1,9 +1,9 @@
|
||||
import { UserOperationStruct } from "@account-abstraction/contracts";
|
||||
import { UserOperation } from "./userOpUtils";
|
||||
import { ethers } from "ethers";
|
||||
import sleep from "./sleep";
|
||||
|
||||
export default async function sendUserOpAndWait(
|
||||
userOp: UserOperationStruct,
|
||||
userOp: UserOperation,
|
||||
entryPoint: string,
|
||||
bundlerProvider: ethers.JsonRpcProvider,
|
||||
pollingDelay = 100,
|
||||
|
||||
@@ -22,11 +22,11 @@ export default async function setupBls(
|
||||
await blsOpen.getAddress(),
|
||||
},
|
||||
]),
|
||||
[],
|
||||
[entryPointAddress],
|
||||
);
|
||||
|
||||
await receiptOf(
|
||||
blsSignatureAggregator.addStake(entryPointAddress, 100n * 86_400n, {
|
||||
blsSignatureAggregator.addStake(100n * 86_400n, {
|
||||
value: parseEther("1"),
|
||||
}),
|
||||
);
|
||||
|
||||
214
packages/plugins/test/e2e/utils/userOpUtils.ts
Normal file
214
packages/plugins/test/e2e/utils/userOpUtils.ts
Normal file
@@ -0,0 +1,214 @@
|
||||
import {
|
||||
AbiCoder,
|
||||
BigNumberish,
|
||||
BytesLike,
|
||||
concat,
|
||||
hexlify,
|
||||
isHexString,
|
||||
keccak256,
|
||||
} from "ethers";
|
||||
import { PackedUserOperationStruct } from "../../../typechain-types/lib/account-abstraction/contracts/core/EntryPoint";
|
||||
|
||||
/**
|
||||
* @notice these utils have been largely copied from ERC4337Utils.ts in eth-infinitism's
|
||||
* bundler repo, which form part of @account-abstraction/utils. This is because
|
||||
* @account-abstraction/utils v0.7.0 has not been published on npm yet. These utility
|
||||
* function can be swapped out once v0.7.0 has been published.
|
||||
*
|
||||
* The only changes were to update ethers functionality from v5 to v6
|
||||
*/
|
||||
|
||||
export type PackedUserOperation = PackedUserOperationStruct;
|
||||
|
||||
export type UserOperation = {
|
||||
sender: string;
|
||||
nonce: BigNumberish;
|
||||
factory?: string;
|
||||
factoryData?: BytesLike;
|
||||
callData: BytesLike;
|
||||
callGasLimit: BigNumberish;
|
||||
verificationGasLimit: BigNumberish;
|
||||
preVerificationGas: BigNumberish;
|
||||
maxFeePerGas: BigNumberish;
|
||||
maxPriorityFeePerGas: BigNumberish;
|
||||
paymaster?: string;
|
||||
paymasterVerificationGasLimit?: BigNumberish;
|
||||
paymasterPostOpGasLimit?: BigNumberish;
|
||||
paymasterData?: BytesLike;
|
||||
signature: BytesLike;
|
||||
};
|
||||
|
||||
export type FactoryParams = {
|
||||
factory: string;
|
||||
factoryData?: BytesLike;
|
||||
};
|
||||
|
||||
/**
|
||||
* calculate the userOpHash of a given userOperation.
|
||||
* The userOpHash is a hash of all UserOperation fields, except the "signature" field.
|
||||
* The entryPoint uses this value in the emitted UserOperationEvent.
|
||||
* A wallet may use this value as the hash to sign (the SampleWallet uses this method)
|
||||
* @param op
|
||||
* @param entryPoint
|
||||
* @param chainId
|
||||
*/
|
||||
export function getUserOpHash(
|
||||
op: UserOperation,
|
||||
entryPoint: string,
|
||||
chainId: number,
|
||||
): string {
|
||||
const userOpHash = keccak256(encodeUserOp(op, true));
|
||||
const defaultAbiCoder = AbiCoder.defaultAbiCoder();
|
||||
const enc = defaultAbiCoder.encode(
|
||||
["bytes32", "address", "uint256"],
|
||||
[userOpHash, entryPoint, chainId],
|
||||
);
|
||||
return keccak256(enc);
|
||||
}
|
||||
|
||||
/**
|
||||
* abi-encode the userOperation
|
||||
* @param op a PackedUserOp
|
||||
* @param forSignature "true" if the hash is needed to calculate the getUserOpHash()
|
||||
* "false" to pack entire UserOp, for calculating the calldata cost of putting it on-chain.
|
||||
*/
|
||||
export function encodeUserOp(
|
||||
op1: PackedUserOperation | UserOperation,
|
||||
forSignature = true,
|
||||
): string {
|
||||
// if "op" is unpacked UserOperation, then pack it first, before we ABI-encode it.
|
||||
|
||||
let op: PackedUserOperation;
|
||||
if ("callGasLimit" in op1) {
|
||||
op = packUserOp(op1);
|
||||
} else {
|
||||
op = op1;
|
||||
}
|
||||
|
||||
const defaultAbiCoder = AbiCoder.defaultAbiCoder();
|
||||
if (forSignature) {
|
||||
return defaultAbiCoder.encode(
|
||||
[
|
||||
"address",
|
||||
"uint256",
|
||||
"bytes32",
|
||||
"bytes32",
|
||||
"bytes32",
|
||||
"uint256",
|
||||
"bytes32",
|
||||
"bytes32",
|
||||
],
|
||||
[
|
||||
op.sender,
|
||||
op.nonce,
|
||||
keccak256(op.initCode),
|
||||
keccak256(op.callData),
|
||||
op.accountGasLimits,
|
||||
op.preVerificationGas,
|
||||
op.gasFees,
|
||||
keccak256(op.paymasterAndData),
|
||||
],
|
||||
);
|
||||
} else {
|
||||
// for the purpose of calculating gas cost encode also signature (and no keccak of bytes)
|
||||
return defaultAbiCoder.encode(
|
||||
[
|
||||
"address",
|
||||
"uint256",
|
||||
"bytes",
|
||||
"bytes",
|
||||
"bytes32",
|
||||
"uint256",
|
||||
"bytes32",
|
||||
"bytes",
|
||||
"bytes",
|
||||
],
|
||||
[
|
||||
op.sender,
|
||||
op.nonce,
|
||||
op.initCode,
|
||||
op.callData,
|
||||
op.accountGasLimits,
|
||||
op.preVerificationGas,
|
||||
op.gasFees,
|
||||
op.paymasterAndData,
|
||||
op.signature,
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export function packUserOp(op: UserOperation): PackedUserOperation {
|
||||
let paymasterAndData: BytesLike;
|
||||
if (op.paymaster == null) {
|
||||
paymasterAndData = "0x";
|
||||
} else {
|
||||
if (
|
||||
op.paymasterVerificationGasLimit == null ||
|
||||
op.paymasterPostOpGasLimit == null
|
||||
) {
|
||||
throw new Error("paymaster with no gas limits");
|
||||
}
|
||||
paymasterAndData = packPaymasterData(
|
||||
op.paymaster,
|
||||
op.paymasterVerificationGasLimit,
|
||||
op.paymasterPostOpGasLimit,
|
||||
op.paymasterData,
|
||||
);
|
||||
}
|
||||
|
||||
return {
|
||||
sender: op.sender,
|
||||
nonce: "0x" + BigInt(op.nonce).toString(16),
|
||||
initCode:
|
||||
op.factory == null ? "0x" : concat([op.factory, op.factoryData ?? ""]),
|
||||
callData: op.callData,
|
||||
accountGasLimits: packUint(op.verificationGasLimit, op.callGasLimit),
|
||||
preVerificationGas: "0x" + BigInt(op.preVerificationGas).toString(16),
|
||||
gasFees: packUint(op.maxPriorityFeePerGas, op.maxFeePerGas),
|
||||
paymasterAndData,
|
||||
signature: op.signature,
|
||||
};
|
||||
}
|
||||
|
||||
export function packPaymasterData(
|
||||
paymaster: string,
|
||||
paymasterVerificationGasLimit: BigNumberish,
|
||||
postOpGasLimit: BigNumberish,
|
||||
paymasterData?: BytesLike,
|
||||
): BytesLike {
|
||||
return concat([
|
||||
paymaster,
|
||||
packUint(paymasterVerificationGasLimit, postOpGasLimit),
|
||||
paymasterData ?? "0x",
|
||||
]);
|
||||
}
|
||||
|
||||
export function packUint(high128: BigNumberish, low128: BigNumberish): string {
|
||||
high128 = BigInt(high128);
|
||||
low128 = BigInt(low128);
|
||||
const packed = "0x" + ((high128 << 128n) + low128).toString(16);
|
||||
return hexZeroPad(packed, 32);
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice Copied from ethers v5.7 as the ethers v6 equivalent behaves
|
||||
* in a way the bundler doesn't expect and an error is thrown
|
||||
*/
|
||||
export function hexZeroPad(value: BytesLike, length: number): string {
|
||||
if (typeof value !== "string") {
|
||||
value = hexlify(value);
|
||||
} else if (!isHexString(value)) {
|
||||
console.log("invalid hex string", "value", value);
|
||||
}
|
||||
|
||||
if (value.length > 2 * length + 2) {
|
||||
console.log("value out of range", "value", arguments[1]);
|
||||
}
|
||||
|
||||
while (value.length < 2 * length + 2) {
|
||||
value = "0x0" + value.substring(2);
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
pragma solidity 0.8.19;
|
||||
pragma solidity ^0.8.19;
|
||||
|
||||
import {KernelFactory} from "kernel/src/factory/KernelFactory.sol";
|
||||
import {Kernel, UserOperation, ECDSA} from "kernel/src/Kernel.sol";
|
||||
|
||||
@@ -7,7 +7,6 @@ import {TestHelper} from "../utils/TestHelper.sol";
|
||||
import {SafeBlsPluginHarness} from "../utils/SafeBlsPluginHarness.sol";
|
||||
import {SafeBlsPlugin} from "../../../src/safe/SafeBlsPlugin.sol";
|
||||
import {Safe4337Base} from "../../../src/safe/utils/Safe4337Base.sol";
|
||||
import {UserOperation, UserOperationLib} from "account-abstraction/contracts/interfaces/IEntryPoint.sol";
|
||||
import {BLSSignatureAggregator} from "account-abstraction/contracts/samples/bls/BLSSignatureAggregator.sol";
|
||||
|
||||
/* solhint-disable func-name-mixedcase */
|
||||
@@ -21,7 +20,7 @@ contract SafeBlsPluginTest is TestHelper {
|
||||
function setUp() public {
|
||||
uint256[4] memory blsPublicKey = getBlsPublicKey();
|
||||
|
||||
blsSignatureAggregator = new BLSSignatureAggregator();
|
||||
blsSignatureAggregator = new BLSSignatureAggregator(entryPointAddress);
|
||||
|
||||
safeBlsPlugin = new SafeBlsPluginHarness(
|
||||
entryPointAddress,
|
||||
|
||||
@@ -7,7 +7,6 @@ import {TestHelper} from "../utils/TestHelper.sol";
|
||||
import {SafeECDSAPluginHarness} from "../utils/SafeECDSAPluginHarness.sol";
|
||||
import {SafeECDSAPlugin} from "../../../src/safe/SafeECDSAPlugin.sol";
|
||||
import {Safe4337Base} from "../../../src/safe/utils/Safe4337Base.sol";
|
||||
import {UserOperation, UserOperationLib} from "account-abstraction/contracts/interfaces/IEntryPoint.sol";
|
||||
|
||||
/* solhint-disable func-name-mixedcase */
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@ import {SafeECDSAPlugin} from "../../../src/safe/SafeECDSAPlugin.sol";
|
||||
import {Safe} from "safe-contracts/contracts/Safe.sol";
|
||||
import {SafeProxy} from "safe-contracts/contracts/proxies/SafeProxy.sol";
|
||||
import {ECDSA} from "openzeppelin-contracts/contracts/utils/cryptography/ECDSA.sol";
|
||||
import {MessageHashUtils} from "@openzeppelin/contracts/utils/cryptography/MessageHashUtils.sol";
|
||||
|
||||
/* solhint-disable func-name-mixedcase */
|
||||
/* solhint-disable private-vars-leading-underscore */
|
||||
@@ -292,7 +293,10 @@ contract SafeECDSARecoveryPluginTest is TestHelper {
|
||||
bytes32 invalidOwnerHash = keccak256(
|
||||
abi.encodePacked("invalid address hash")
|
||||
);
|
||||
bytes32 ethSignedHash = invalidOwnerHash.toEthSignedMessageHash();
|
||||
bytes32 ethSignedHash = MessageHashUtils.toEthSignedMessageHash(
|
||||
invalidOwnerHash
|
||||
);
|
||||
|
||||
(uint8 v, bytes32 r, bytes32 s) = vm.sign(newOwner, ethSignedHash);
|
||||
bytes memory newOwnerSignature = abi.encodePacked(r, s, v); // note the order here is different from the tuple above.
|
||||
|
||||
@@ -331,7 +335,9 @@ contract SafeECDSARecoveryPluginTest is TestHelper {
|
||||
Vm.Wallet memory newOwner = Carol;
|
||||
|
||||
bytes32 currentOwnerHash = keccak256(abi.encodePacked(owner));
|
||||
bytes32 ethSignedHash = currentOwnerHash.toEthSignedMessageHash();
|
||||
bytes32 ethSignedHash = MessageHashUtils.toEthSignedMessageHash(
|
||||
currentOwnerHash
|
||||
);
|
||||
(uint8 v, bytes32 r, bytes32 s) = vm.sign(newOwner, ethSignedHash);
|
||||
bytes memory newOwnerSignature = abi.encodePacked(r, s, v); // note the order here is different from the tuple above.
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ import {TestHelper} from "../utils/TestHelper.sol";
|
||||
import {SafeWebAuthnPluginHarness} from "../utils/SafeWebAuthnPluginHarness.sol";
|
||||
import {SafeWebAuthnPlugin} from "../../../src/safe/SafeWebAuthnPlugin.sol";
|
||||
import {Safe4337Base} from "../../../src/safe/utils/Safe4337Base.sol";
|
||||
import {UserOperation, UserOperationLib} from "account-abstraction/contracts/interfaces/IEntryPoint.sol";
|
||||
import {PackedUserOperation} from "account-abstraction/contracts/interfaces/IEntryPoint.sol";
|
||||
|
||||
/* solhint-disable func-name-mixedcase */
|
||||
|
||||
@@ -26,7 +26,7 @@ contract SafeWebAuthnPluginTest is TestHelper {
|
||||
|
||||
function test_validateSignature_ValidSignature() public {
|
||||
// Arrange
|
||||
UserOperation memory userOp = buildUserOp();
|
||||
PackedUserOperation memory userOp = buildUserOp();
|
||||
bytes32 userOpHash = entryPoint.getUserOpHash(userOp);
|
||||
uint256 expectedValidationData = 0;
|
||||
|
||||
@@ -45,7 +45,7 @@ contract SafeWebAuthnPluginTest is TestHelper {
|
||||
|
||||
function test_validateSignature_InvalidSignature() public {
|
||||
// Arrange
|
||||
UserOperation memory userOp = buildUserOp();
|
||||
PackedUserOperation memory userOp = buildUserOp();
|
||||
bytes32 userOpHash = entryPoint.getUserOpHash(userOp);
|
||||
uint256 expectedValidationData = 1;
|
||||
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
pragma solidity ^0.8.12;
|
||||
|
||||
import {UserOperation} from "account-abstraction/contracts/interfaces/IEntryPoint.sol";
|
||||
import {SafeBlsPlugin} from "../../../src/safe/SafeBlsPlugin.sol";
|
||||
|
||||
/** Helper contract to expose internal functions for testing */
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
pragma solidity ^0.8.12;
|
||||
|
||||
import {UserOperation} from "account-abstraction/contracts/interfaces/IEntryPoint.sol";
|
||||
import {SafeECDSAPlugin} from "../../../src/safe/SafeECDSAPlugin.sol";
|
||||
|
||||
/** Helper contract to expose internal functions for testing */
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
pragma solidity ^0.8.12;
|
||||
|
||||
import {UserOperation} from "account-abstraction/contracts/interfaces/IEntryPoint.sol";
|
||||
import {PackedUserOperation} from "account-abstraction/contracts/interfaces/IEntryPoint.sol";
|
||||
import {SafeWebAuthnPlugin} from "../../../src/safe/SafeWebAuthnPlugin.sol";
|
||||
|
||||
/** Helper contract to expose internal functions for testing */
|
||||
@@ -12,7 +12,7 @@ contract SafeWebAuthnPluginHarness is SafeWebAuthnPlugin {
|
||||
) SafeWebAuthnPlugin(entryPointAddress, pubKey) {}
|
||||
|
||||
function exposed_validateSignature(
|
||||
UserOperation calldata userOp,
|
||||
PackedUserOperation calldata userOp,
|
||||
bytes32 userOpHash
|
||||
) external returns (uint256) {
|
||||
return _validateSignature(userOp, userOpHash);
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
pragma solidity ^0.8.12;
|
||||
|
||||
import "forge-std/Test.sol";
|
||||
import {EntryPoint, UserOperation} from "account-abstraction/contracts/core/EntryPoint.sol";
|
||||
import {EntryPoint, PackedUserOperation} from "account-abstraction/contracts/core/EntryPoint.sol";
|
||||
|
||||
/* solhint-disable private-vars-leading-underscore */
|
||||
/* solhint-disable var-name-mixedcase */
|
||||
@@ -26,29 +26,29 @@ abstract contract TestHelper is Test {
|
||||
Dave = vm.createWallet("Dave");
|
||||
}
|
||||
|
||||
function buildUserOp() public view returns (UserOperation memory userOp) {
|
||||
function buildUserOp()
|
||||
public
|
||||
view
|
||||
returns (PackedUserOperation memory userOp)
|
||||
{
|
||||
address sender = Alice.addr;
|
||||
uint256 nonce = 0;
|
||||
bytes memory initCode = hex"00";
|
||||
bytes memory callData = hex"00";
|
||||
uint256 callGasLimit = 0;
|
||||
uint256 verificationGasLimit = 0;
|
||||
bytes32 accountGasLimits = hex"00";
|
||||
uint256 preVerificationGas = 0;
|
||||
uint256 maxFeePerGas = 0;
|
||||
uint256 maxPriorityFeePerGas = 0;
|
||||
bytes32 gasFees = hex"00";
|
||||
bytes memory paymasterAndData = hex"00";
|
||||
bytes memory signature = hex"00";
|
||||
|
||||
userOp = UserOperation(
|
||||
userOp = PackedUserOperation(
|
||||
sender,
|
||||
nonce,
|
||||
initCode,
|
||||
callData,
|
||||
callGasLimit,
|
||||
verificationGasLimit,
|
||||
accountGasLimits,
|
||||
preVerificationGas,
|
||||
maxFeePerGas,
|
||||
maxPriorityFeePerGas,
|
||||
gasFees,
|
||||
paymasterAndData,
|
||||
signature
|
||||
);
|
||||
|
||||
@@ -7,6 +7,14 @@
|
||||
resolved "https://registry.yarnpkg.com/@aashutoshrathi/word-wrap/-/word-wrap-1.2.6.tgz#bd9154aec9983f77b3a034ecaa015c2e4201f6cf"
|
||||
integrity sha512-1Yjs2SvM8TflER/OD3cOjhWWOZb58A2t7wpE2S9XfBYTiIl+XFhQG2bjy4Pu1I+EAlCNUzRDYDdFwFYUKvXcIA==
|
||||
|
||||
"@account-abstraction/contracts@0.7.0":
|
||||
version "0.7.0"
|
||||
resolved "https://registry.yarnpkg.com/@account-abstraction/contracts/-/contracts-0.7.0.tgz#f0f60d73e8d6ef57b794b07f8ab7b4779a3814c6"
|
||||
integrity sha512-Bt/66ilu3u8I9+vFZ9fTd+cWs55fdb9J5YKfrhsrFafH1drkzwuCSL/xEot1GGyXXNJLQuXbMRztQPyelNbY1A==
|
||||
dependencies:
|
||||
"@openzeppelin/contracts" "^5.0.0"
|
||||
"@uniswap/v3-periphery" "^1.4.3"
|
||||
|
||||
"@account-abstraction/contracts@^0.6.0":
|
||||
version "0.6.0"
|
||||
resolved "https://registry.yarnpkg.com/@account-abstraction/contracts/-/contracts-0.6.0.tgz#7188a01839999226e6b2796328af338329543b76"
|
||||
@@ -865,11 +873,21 @@
|
||||
"@nomicfoundation/solidity-analyzer-win32-ia32-msvc" "0.1.1"
|
||||
"@nomicfoundation/solidity-analyzer-win32-x64-msvc" "0.1.1"
|
||||
|
||||
"@openzeppelin/contracts@3.4.2-solc-0.7":
|
||||
version "3.4.2-solc-0.7"
|
||||
resolved "https://registry.yarnpkg.com/@openzeppelin/contracts/-/contracts-3.4.2-solc-0.7.tgz#38f4dbab672631034076ccdf2f3201fab1726635"
|
||||
integrity sha512-W6QmqgkADuFcTLzHL8vVoNBtkwjvQRpYIAom7KiUNoLKghyx3FgH0GBjt8NRvigV1ZmMOBllvE1By1C+bi8WpA==
|
||||
|
||||
"@openzeppelin/contracts@^4.7.3":
|
||||
version "4.9.3"
|
||||
resolved "https://registry.yarnpkg.com/@openzeppelin/contracts/-/contracts-4.9.3.tgz#00d7a8cf35a475b160b3f0293a6403c511099364"
|
||||
integrity sha512-He3LieZ1pP2TNt5JbkPA4PNT9WC3gOTOlDcFGJW4Le4QKqwmiNJCRt44APfxMxvq7OugU/cqYuPcSBzOw38DAg==
|
||||
|
||||
"@openzeppelin/contracts@^5.0.0":
|
||||
version "5.0.2"
|
||||
resolved "https://registry.yarnpkg.com/@openzeppelin/contracts/-/contracts-5.0.2.tgz#b1d03075e49290d06570b2fd42154d76c2a5d210"
|
||||
integrity sha512-ytPc6eLGcHHnapAZ9S+5qsdomhjo6QBHTDRRBFfTxXIpsicMhVPouPgmUPebZZZGX7vt9USA+Z+0M0dSVtSUEA==
|
||||
|
||||
"@pkgr/utils@^2.3.1":
|
||||
version "2.4.2"
|
||||
resolved "https://registry.yarnpkg.com/@pkgr/utils/-/utils-2.4.2.tgz#9e638bbe9a6a6f165580dc943f138fd3309a2cbc"
|
||||
@@ -1249,6 +1267,32 @@
|
||||
"@typescript-eslint/types" "6.7.0"
|
||||
eslint-visitor-keys "^3.4.1"
|
||||
|
||||
"@uniswap/lib@^4.0.1-alpha":
|
||||
version "4.0.1-alpha"
|
||||
resolved "https://registry.yarnpkg.com/@uniswap/lib/-/lib-4.0.1-alpha.tgz#2881008e55f075344675b3bca93f020b028fbd02"
|
||||
integrity sha512-f6UIliwBbRsgVLxIaBANF6w09tYqc6Y/qXdsrbEmXHyFA7ILiKrIwRFXe1yOg8M3cksgVsO9N7yuL2DdCGQKBA==
|
||||
|
||||
"@uniswap/v2-core@^1.0.1":
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/@uniswap/v2-core/-/v2-core-1.0.1.tgz#af8f508bf183204779938969e2e54043e147d425"
|
||||
integrity sha512-MtybtkUPSyysqLY2U210NBDeCHX+ltHt3oADGdjqoThZaFRDKwM6k1Nb3F0A3hk5hwuQvytFWhrWHOEq6nVJ8Q==
|
||||
|
||||
"@uniswap/v3-core@^1.0.0":
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/@uniswap/v3-core/-/v3-core-1.0.1.tgz#b6d2bdc6ba3c3fbd610bdc502395d86cd35264a0"
|
||||
integrity sha512-7pVk4hEm00j9tc71Y9+ssYpO6ytkeI0y7WE9P6UcmNzhxPePwyAxImuhVsTqWK9YFvzgtvzJHi64pBl4jUzKMQ==
|
||||
|
||||
"@uniswap/v3-periphery@^1.4.3":
|
||||
version "1.4.4"
|
||||
resolved "https://registry.yarnpkg.com/@uniswap/v3-periphery/-/v3-periphery-1.4.4.tgz#d2756c23b69718173c5874f37fd4ad57d2f021b7"
|
||||
integrity sha512-S4+m+wh8HbWSO3DKk4LwUCPZJTpCugIsHrWR86m/OrUyvSqGDTXKFfc2sMuGXCZrD1ZqO3rhQsKgdWg3Hbb2Kw==
|
||||
dependencies:
|
||||
"@openzeppelin/contracts" "3.4.2-solc-0.7"
|
||||
"@uniswap/lib" "^4.0.1-alpha"
|
||||
"@uniswap/v2-core" "^1.0.1"
|
||||
"@uniswap/v3-core" "^1.0.0"
|
||||
base64-sol "1.0.1"
|
||||
|
||||
abbrev@1:
|
||||
version "1.1.1"
|
||||
resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.1.1.tgz#f8f2c887ad10bf67f634f005b6987fed3179aac8"
|
||||
@@ -1623,6 +1667,11 @@ base64-js@^1.3.1:
|
||||
resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a"
|
||||
integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==
|
||||
|
||||
base64-sol@1.0.1:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/base64-sol/-/base64-sol-1.0.1.tgz#91317aa341f0bc763811783c5729f1c2574600f6"
|
||||
integrity sha512-ld3cCNMeXt4uJXmLZBHFGMvVpK9KsLVEhPpFRXnvSVAqABKbuNZg/+dsq3NuM+wxFLb/UrVkz7m1ciWmkMfTbg==
|
||||
|
||||
bcrypt-pbkdf@^1.0.0:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz#a4301d389b6a43f9b67ff3ca11a3f6637e360e9e"
|
||||
|
||||
Reference in New Issue
Block a user