mirror of
https://github.com/getwax/wax.git
synced 2026-01-09 23:27:58 -05:00
pulled changes from main
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,
|
||||
|
||||
1
packages/plugins/lib/erc7579-implementation
Submodule
1
packages/plugins/lib/erc7579-implementation
Submodule
Submodule packages/plugins/lib/erc7579-implementation added at 42aa538397
1
packages/plugins/lib/reference-implementation
Submodule
1
packages/plugins/lib/reference-implementation
Submodule
Submodule packages/plugins/lib/reference-implementation added at 8b9ba71c25
@@ -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",
|
||||
"@anon-aadhaar/contracts": "^2.0.3",
|
||||
"@anon-aadhaar/core": "^2.0.3",
|
||||
|
||||
@@ -2,8 +2,11 @@ ds-test/=lib/forge-std/lib/ds-test/src/
|
||||
forge-std/=lib/forge-std/src/
|
||||
openzeppelin-contracts/=lib/openzeppelin-contracts/
|
||||
@openzeppelin/contracts/=lib/openzeppelin-contracts/contracts/
|
||||
account-abstraction/=lib/account-abstraction/
|
||||
@eth-infinitism/account-abstraction/=lib/reference-implementation/lib/account-abstraction/contracts/
|
||||
account-abstraction/=lib/account-abstraction/contracts/
|
||||
safe-contracts/=lib/safe-contracts/
|
||||
kernel/=lib/kernel/
|
||||
I4337/=lib/kernel/lib/I4337/src/
|
||||
solady/=lib/kernel/lib/solady/src/
|
||||
solady/=lib/kernel/lib/solady/src/
|
||||
erc7579-implementation/=lib/erc7579-implementation/
|
||||
erc6900-reference-implementation/=lib/reference-implementation/src/
|
||||
@@ -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 = [
|
||||
|
||||
@@ -1,16 +0,0 @@
|
||||
FROM accountabstraction/bundler:0.6.2
|
||||
|
||||
RUN apt-get update && \
|
||||
apt-get install -y coreutils && \
|
||||
rm -rf /var/lib/apt/lists/*
|
||||
|
||||
ARG EXPECTED_DIGEST="727838bd8705dd319970fb4a86c9abe5334e17312230143e01043abf0732b5f7"
|
||||
|
||||
# Modify /app/bundler.js to add support for estimating (but not running) aggregate bundles
|
||||
RUN sed -i 's/(errorResult.errorName !== '\''ValidationResult'\'')/(!errorResult.errorName.startsWith('\''ValidationResult'\''))/g' /app/bundler.js
|
||||
|
||||
# Fail if we didn't achieve the expected SHA256 digest
|
||||
RUN if [ "$(sha256sum /app/bundler.js | awk '{print $1}')" != "$EXPECTED_DIGEST" ]; then \
|
||||
echo "SHA256 digest does not match. Aborting."; \
|
||||
exit 1; \
|
||||
fi
|
||||
@@ -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 \
|
||||
&
|
||||
|
||||
365
packages/plugins/src/ERC6900/ERC6900ZkEmailRecoveryModule.sol
Normal file
365
packages/plugins/src/ERC6900/ERC6900ZkEmailRecoveryModule.sol
Normal file
@@ -0,0 +1,365 @@
|
||||
// SPDX-License-Identifier: GPL-3.0
|
||||
pragma solidity ^0.8.19;
|
||||
|
||||
import {BasePlugin} from "erc6900-reference-implementation/plugins/BasePlugin.sol";
|
||||
import {IPluginExecutor} from "erc6900-reference-implementation/interfaces/IPluginExecutor.sol";
|
||||
import {ManifestFunction, ManifestAssociatedFunctionType, ManifestAssociatedFunction, PluginManifest, PluginMetadata, IPlugin} from "erc6900-reference-implementation/interfaces/IPlugin.sol";
|
||||
import {MockGroth16Verifier} from "../safe/utils/MockGroth16Verifier.sol";
|
||||
import {MockDKIMRegsitry} from "../safe/utils/MockDKIMRegsitry.sol";
|
||||
import {IDKIMRegsitry} from "../safe/interface/IDKIMRegsitry.sol";
|
||||
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
THIS CONTRACT IS STILL IN ACTIVE DEVELOPMENT. NOT FOR PRODUCTION USE
|
||||
//////////////////////////////////////////////////////////////////////////*/
|
||||
|
||||
struct RecoveryRequest {
|
||||
bytes32 recoveryHash;
|
||||
bytes32 dkimPublicKeyHash;
|
||||
uint256 executeAfter;
|
||||
address pendingNewOwner;
|
||||
}
|
||||
|
||||
/// @title ZK Email Recovery Plugin
|
||||
/// @author Wax
|
||||
/// @notice This plugin recovers a ERC 6900 account via zk email guardians
|
||||
contract ERC6900ZkEmailRecoveryModule is BasePlugin {
|
||||
// metadata used by the pluginMetadata() method down below
|
||||
string public constant NAME = "ZK Email Recovery Plugin";
|
||||
string public constant VERSION = "1.0.0";
|
||||
string public constant AUTHOR = "Wax";
|
||||
|
||||
// this is a constant used in the manifest, to reference our only dependency: the single owner plugin
|
||||
// since it is the first, and only, plugin the index 0 will reference the single owner plugin
|
||||
// we can use this to tell the modular account that we should use the single owner plugin to validate our user op
|
||||
// in other words, we'll say "make sure the person calling the recovery functions is an owner of the account using our single plugin" // TODO: revisit this - recovery is more complicated as the owner is compromised
|
||||
uint256
|
||||
internal constant _MANIFEST_DEPENDENCY_INDEX_OWNER_USER_OP_VALIDATION =
|
||||
0;
|
||||
|
||||
/** Default DKIM public key hashes registry */
|
||||
IDKIMRegsitry public immutable defaultDkimRegistry;
|
||||
|
||||
/** verifier */
|
||||
MockGroth16Verifier public immutable verifier;
|
||||
|
||||
/** Default delay has been set to a large timeframe on purpose. Please use a default delay suited to your specific context */
|
||||
uint256 public constant defaultDelay = 2 weeks;
|
||||
|
||||
/** recovery hash domain */
|
||||
bytes32 immutable RECOVERY_HASH_DOMAIN;
|
||||
|
||||
/** recovery request */
|
||||
RecoveryRequest public recoveryRequest;
|
||||
|
||||
/** custom recovery delay */
|
||||
uint256 public recoveryDelay;
|
||||
|
||||
/** dkim registry address */
|
||||
address public dkimRegistry;
|
||||
|
||||
error RECOVERY_ALREADY_INITIATED();
|
||||
error RECOVERY_NOT_CONFIGURED();
|
||||
error INVALID_DKIM_KEY_HASH(
|
||||
address account,
|
||||
string emailDomain,
|
||||
bytes32 dkimPublicKeyHash
|
||||
);
|
||||
error INVALID_PROOF();
|
||||
error RECOVERY_NOT_INITIATED();
|
||||
error DELAY_NOT_PASSED();
|
||||
|
||||
event RecoveryConfigured(
|
||||
address indexed account,
|
||||
address indexed owner,
|
||||
bytes32 recoveryHash,
|
||||
bytes32 dkimPublicKeyHash,
|
||||
address dkimRegistry,
|
||||
uint256 customDelay
|
||||
);
|
||||
event RecoveryInitiated(
|
||||
address indexed account,
|
||||
address newOwner,
|
||||
uint256 executeAfter
|
||||
);
|
||||
event AccountRecovered(address indexed account, address newOwner);
|
||||
event RecoveryCancelled(address indexed account);
|
||||
event RecoveryDelaySet(address indexed account, uint256 delay);
|
||||
|
||||
// ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
|
||||
// ┃ Execution functions ┃
|
||||
// ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
|
||||
|
||||
/**
|
||||
* @notice Initiates a recovery for an account using a zk email proof.
|
||||
* @dev Rotates the account owner address to a new address. Uses the
|
||||
* default delay period if no custom delay has been set. This is the second
|
||||
* function that should be called in the recovery process - after configureRecovery
|
||||
* @param newOwner The new owner address of the account
|
||||
* @param emailDomain Domain name of the sender's email
|
||||
* @param a Part of the proof
|
||||
* @param b Part of the proof
|
||||
* @param c Part of the proof
|
||||
*/
|
||||
function initiateRecovery(
|
||||
address newOwner,
|
||||
string memory emailDomain,
|
||||
uint256[2] memory a,
|
||||
uint256[2][2] memory b,
|
||||
uint256[2] memory c
|
||||
) external {
|
||||
address account = msg.sender;
|
||||
|
||||
if (recoveryRequest.recoveryHash == bytes32(0)) {
|
||||
revert RECOVERY_NOT_CONFIGURED();
|
||||
}
|
||||
|
||||
if (recoveryRequest.executeAfter > 0) {
|
||||
revert RECOVERY_ALREADY_INITIATED();
|
||||
}
|
||||
|
||||
if (
|
||||
!this.isDKIMPublicKeyHashValid(
|
||||
emailDomain,
|
||||
recoveryRequest.dkimPublicKeyHash
|
||||
)
|
||||
) {
|
||||
revert INVALID_DKIM_KEY_HASH(
|
||||
account,
|
||||
emailDomain,
|
||||
recoveryRequest.dkimPublicKeyHash
|
||||
);
|
||||
}
|
||||
|
||||
uint256[4] memory publicSignals = [
|
||||
uint256(uint160(account)),
|
||||
uint256(recoveryRequest.recoveryHash),
|
||||
uint256(uint160(newOwner)),
|
||||
uint256(recoveryRequest.dkimPublicKeyHash)
|
||||
];
|
||||
|
||||
// verify proof
|
||||
bool verified = verifier.verifyProof(a, b, c, publicSignals);
|
||||
if (!verified) revert INVALID_PROOF();
|
||||
|
||||
uint256 executeAfter = block.timestamp + recoveryDelay;
|
||||
|
||||
recoveryRequest.executeAfter = executeAfter;
|
||||
recoveryRequest.pendingNewOwner = newOwner;
|
||||
|
||||
emit RecoveryInitiated(account, newOwner, executeAfter);
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice Recovers an account using a zk email proof.
|
||||
* @dev Rotates the account owner address to a new address.
|
||||
* This function is the third and final function that needs to be called in the
|
||||
* recovery process. After configureRecovery & initiateRecovery
|
||||
*/
|
||||
function recoverAccount() public {
|
||||
address account = msg.sender;
|
||||
|
||||
if (recoveryRequest.executeAfter == 0) {
|
||||
revert RECOVERY_NOT_INITIATED();
|
||||
}
|
||||
|
||||
if (block.timestamp > recoveryRequest.executeAfter) {
|
||||
delete recoveryRequest;
|
||||
|
||||
// TODO: implement recovery logic for 6900 owner plugin
|
||||
// owner = recoveryRequest.pendingNewOwner;
|
||||
|
||||
emit AccountRecovered(account, recoveryRequest.pendingNewOwner);
|
||||
} else {
|
||||
revert DELAY_NOT_PASSED();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice Cancels the recovery process of the sender if it exits.
|
||||
* @dev Deletes the recovery request accociated with a account. Assumes
|
||||
* the msg.sender is the account that the recovery request is being deleted for
|
||||
*/
|
||||
function cancelRecovery() external {
|
||||
address account = msg.sender;
|
||||
delete recoveryRequest;
|
||||
emit RecoveryCancelled(account);
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice Sets a custom delay for recovering the account.
|
||||
* @dev Custom delay is used instead of the default delay when recovering the
|
||||
* account. Custom delays should be configured with care as they can be
|
||||
* used to bypass the default delay.
|
||||
* @param delay The custom delay to be used when recovering the account
|
||||
*/
|
||||
function setRecoveryDelay(uint256 delay) external {
|
||||
address account = msg.sender;
|
||||
recoveryDelay = delay;
|
||||
emit RecoveryDelaySet(account, delay);
|
||||
}
|
||||
|
||||
/// @notice Return the DKIM public key hash for a given email domain and account address
|
||||
/// @param emailDomain Email domain for which the DKIM public key hash is to be returned
|
||||
function isDKIMPublicKeyHashValid(
|
||||
string memory emailDomain,
|
||||
bytes32 publicKeyHash
|
||||
) public view returns (bool) {
|
||||
if (dkimRegistry == address(0)) {
|
||||
return
|
||||
defaultDkimRegistry.isDKIMPublicKeyHashValid(
|
||||
emailDomain,
|
||||
publicKeyHash
|
||||
);
|
||||
} else {
|
||||
return
|
||||
IDKIMRegsitry(dkimRegistry).isDKIMPublicKeyHashValid(
|
||||
emailDomain,
|
||||
publicKeyHash
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
|
||||
// ┃ Plugin interface functions ┃
|
||||
// ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
|
||||
|
||||
/// @inheritdoc BasePlugin
|
||||
function onInstall(bytes calldata data) external override {
|
||||
(
|
||||
bytes32 recoveryHash,
|
||||
bytes32 dkimPublicKeyHash,
|
||||
address _dkimRegistry,
|
||||
uint256 customDelay
|
||||
) = abi.decode(data, (bytes32, bytes32, address, uint256));
|
||||
|
||||
address account = msg.sender;
|
||||
|
||||
if (recoveryRequest.executeAfter > 0) {
|
||||
revert RECOVERY_ALREADY_INITIATED();
|
||||
}
|
||||
|
||||
if (customDelay > 0) {
|
||||
recoveryDelay = customDelay;
|
||||
} else {
|
||||
recoveryDelay = defaultDelay;
|
||||
}
|
||||
|
||||
recoveryRequest = RecoveryRequest({
|
||||
recoveryHash: recoveryHash,
|
||||
dkimPublicKeyHash: dkimPublicKeyHash,
|
||||
executeAfter: 0,
|
||||
pendingNewOwner: address(0)
|
||||
});
|
||||
dkimRegistry = _dkimRegistry; // FIXME: could be zero
|
||||
|
||||
emit RecoveryConfigured(
|
||||
account,
|
||||
msg.sender,
|
||||
recoveryHash,
|
||||
dkimPublicKeyHash,
|
||||
dkimRegistry,
|
||||
customDelay
|
||||
);
|
||||
}
|
||||
|
||||
/// @inheritdoc BasePlugin
|
||||
function onUninstall(bytes calldata) external pure override {}
|
||||
|
||||
/// @inheritdoc BasePlugin
|
||||
function pluginManifest()
|
||||
external
|
||||
pure
|
||||
override
|
||||
returns (PluginManifest memory)
|
||||
{
|
||||
PluginManifest memory manifest;
|
||||
|
||||
// since we are using the modular account, we will specify one depedency
|
||||
// which will handle the user op validation for ownership
|
||||
// you can find this depedency specified in the installPlugin call in the tests
|
||||
manifest.dependencyInterfaceIds = new bytes4[](1);
|
||||
manifest.dependencyInterfaceIds[0] = type(IPlugin).interfaceId;
|
||||
|
||||
// we have several execution functions that can be called, here we define
|
||||
// those functions on the manifest as something that can be called during execution
|
||||
manifest.executionFunctions = new bytes4[](5);
|
||||
manifest.executionFunctions[0] = this.initiateRecovery.selector;
|
||||
manifest.executionFunctions[1] = this.recoverAccount.selector;
|
||||
manifest.executionFunctions[2] = this.cancelRecovery.selector;
|
||||
manifest.executionFunctions[3] = this.setRecoveryDelay.selector;
|
||||
manifest.executionFunctions[4] = this.isDKIMPublicKeyHashValid.selector;
|
||||
|
||||
// you can think of ManifestFunction as a reference to a function somewhere,
|
||||
// we want to say "use this function" for some purpose - in this case,
|
||||
// we'll be using the user op validation function from the single owner dependency
|
||||
// and this is specified by the depdendency index
|
||||
ManifestFunction
|
||||
memory ownerUserOpValidationFunction = ManifestFunction({
|
||||
functionType: ManifestAssociatedFunctionType.DEPENDENCY,
|
||||
functionId: 0, // unused since it's a dependency
|
||||
dependencyIndex: _MANIFEST_DEPENDENCY_INDEX_OWNER_USER_OP_VALIDATION
|
||||
});
|
||||
|
||||
// here we will link together the recovery functions with the single owner user op validation
|
||||
// this basically says "use this user op validation function and make sure everythings okay before calling the recovery functions"
|
||||
// this will ensure that only an owner of the account can call the recovery functions
|
||||
manifest.userOpValidationFunctions = new ManifestAssociatedFunction[](
|
||||
1
|
||||
);
|
||||
manifest.userOpValidationFunctions[0] = ManifestAssociatedFunction({
|
||||
executionSelector: this.initiateRecovery.selector,
|
||||
associatedFunction: ownerUserOpValidationFunction
|
||||
});
|
||||
// TODO: recoverAccount should be permissionless if threshold is met
|
||||
manifest.userOpValidationFunctions[1] = ManifestAssociatedFunction({
|
||||
executionSelector: this.recoverAccount.selector,
|
||||
associatedFunction: ownerUserOpValidationFunction
|
||||
});
|
||||
manifest.userOpValidationFunctions[2] = ManifestAssociatedFunction({
|
||||
executionSelector: this.cancelRecovery.selector,
|
||||
associatedFunction: ownerUserOpValidationFunction
|
||||
});
|
||||
manifest.userOpValidationFunctions[3] = ManifestAssociatedFunction({
|
||||
executionSelector: this.setRecoveryDelay.selector,
|
||||
associatedFunction: ownerUserOpValidationFunction
|
||||
});
|
||||
manifest.userOpValidationFunctions[4] = ManifestAssociatedFunction({
|
||||
executionSelector: this.isDKIMPublicKeyHashValid.selector,
|
||||
associatedFunction: ownerUserOpValidationFunction
|
||||
});
|
||||
|
||||
// TODO: research best way to utilise this part of the manifest
|
||||
// finally here we will always deny runtime calls to the initiateRecovery function as we will only call it through user ops
|
||||
// this avoids a potential issue where a future plugin may define
|
||||
// a runtime validation function for it and unauthorized calls may occur due to that
|
||||
manifest.preRuntimeValidationHooks = new ManifestAssociatedFunction[](
|
||||
1
|
||||
);
|
||||
manifest.preRuntimeValidationHooks[0] = ManifestAssociatedFunction({
|
||||
executionSelector: this.initiateRecovery.selector,
|
||||
associatedFunction: ManifestFunction({
|
||||
functionType: ManifestAssociatedFunctionType
|
||||
.PRE_HOOK_ALWAYS_DENY,
|
||||
functionId: 0,
|
||||
dependencyIndex: 0
|
||||
})
|
||||
});
|
||||
|
||||
return manifest;
|
||||
}
|
||||
|
||||
/// @inheritdoc BasePlugin
|
||||
function pluginMetadata()
|
||||
external
|
||||
pure
|
||||
virtual
|
||||
override
|
||||
returns (PluginMetadata memory)
|
||||
{
|
||||
PluginMetadata memory metadata;
|
||||
metadata.name = NAME;
|
||||
metadata.version = VERSION;
|
||||
metadata.author = AUTHOR;
|
||||
return metadata;
|
||||
}
|
||||
}
|
||||
275
packages/plugins/src/SimpleAccountWithRecovery.sol
Normal file
275
packages/plugins/src/SimpleAccountWithRecovery.sol
Normal file
@@ -0,0 +1,275 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
pragma solidity ^0.8.0;
|
||||
|
||||
/* solhint-disable avoid-low-level-calls */
|
||||
/* solhint-disable no-inline-assembly */
|
||||
/* solhint-disable reason-string */
|
||||
|
||||
import {SimpleAccount} from "account-abstraction/samples/SimpleAccount.sol";
|
||||
import {IEntryPoint} from "account-abstraction/interfaces/IEntryPoint.sol";
|
||||
|
||||
import {MockGroth16Verifier} from "./safe/utils/MockGroth16Verifier.sol";
|
||||
import {MockDKIMRegsitry} from "./safe/utils/MockDKIMRegsitry.sol";
|
||||
import {IDKIMRegsitry} from "./safe/interface/IDKIMRegsitry.sol";
|
||||
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
THIS CONTRACT IS STILL IN ACTIVE DEVELOPMENT. NOT FOR PRODUCTION USE
|
||||
//////////////////////////////////////////////////////////////////////////*/
|
||||
|
||||
struct RecoveryRequest {
|
||||
bytes32 recoveryHash;
|
||||
bytes32 dkimPublicKeyHash;
|
||||
uint256 executeAfter;
|
||||
address pendingNewOwner;
|
||||
}
|
||||
|
||||
/**
|
||||
* minimal account.
|
||||
* this is sample minimal account with ZK Email recovery for rotating the owner
|
||||
*/
|
||||
contract SimpleAccountWithRecovery is SimpleAccount {
|
||||
/** Default DKIM public key hashes registry */
|
||||
IDKIMRegsitry public immutable defaultDkimRegistry;
|
||||
|
||||
/** verifier */
|
||||
MockGroth16Verifier public immutable verifier;
|
||||
|
||||
/** Default delay has been set to a large timeframe on purpose. Please use a default delay suited to your specific context */
|
||||
uint256 public constant defaultDelay = 2 weeks;
|
||||
|
||||
/** recovery hash domain */
|
||||
bytes32 immutable RECOVERY_HASH_DOMAIN;
|
||||
|
||||
/** recovery request */
|
||||
RecoveryRequest public recoveryRequest;
|
||||
|
||||
/** custom recovery delay */
|
||||
uint256 public recoveryDelay;
|
||||
|
||||
/** dkim registry address */
|
||||
address public dkimRegistry;
|
||||
|
||||
error RECOVERY_ALREADY_INITIATED();
|
||||
error RECOVERY_NOT_CONFIGURED();
|
||||
error INVALID_DKIM_KEY_HASH(
|
||||
address account,
|
||||
string emailDomain,
|
||||
bytes32 dkimPublicKeyHash
|
||||
);
|
||||
error INVALID_PROOF();
|
||||
error RECOVERY_NOT_INITIATED();
|
||||
error DELAY_NOT_PASSED();
|
||||
|
||||
event RecoveryConfigured(
|
||||
address indexed account,
|
||||
address indexed owner,
|
||||
bytes32 recoveryHash,
|
||||
bytes32 dkimPublicKeyHash,
|
||||
address dkimRegistry,
|
||||
uint256 customDelay
|
||||
);
|
||||
event RecoveryInitiated(
|
||||
address indexed account,
|
||||
address newOwner,
|
||||
uint256 executeAfter
|
||||
);
|
||||
event AccountRecovered(address indexed account, address newOwner);
|
||||
event RecoveryCancelled(address indexed account);
|
||||
event RecoveryDelaySet(address indexed account, uint256 delay);
|
||||
|
||||
constructor(
|
||||
IEntryPoint anEntryPoint,
|
||||
address _verifier,
|
||||
address _defaultDkimRegistry
|
||||
) SimpleAccount(anEntryPoint) {
|
||||
verifier = MockGroth16Verifier(_verifier);
|
||||
defaultDkimRegistry = IDKIMRegsitry(_defaultDkimRegistry);
|
||||
|
||||
RECOVERY_HASH_DOMAIN = keccak256(
|
||||
abi.encode(
|
||||
keccak256(
|
||||
"EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"
|
||||
),
|
||||
keccak256("SimpleAccountWithRecovery"),
|
||||
keccak256("1"),
|
||||
block.chainid,
|
||||
address(this)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice Stores a recovery hash that can be used to recover the account
|
||||
* @dev dkimRegistry can be a zero address if the user wants to use the
|
||||
* defaultDkimRegistry. customDelay can be 0 if the user wants to use defaultDelay
|
||||
* This is the first function that must be called when setting up recovery.
|
||||
* @param recoveryHash Hash of domain, email and salt - keccak256(abi.encodePacked(RECOVERY_HASH_DOMAIN, email, salt))
|
||||
* @param dkimPublicKeyHash Hash of DKIM public key - keccak256(abi.encodePacked(dkimPublicKey))
|
||||
* @param _dkimRegistry Address of a user-defined DKIM registry
|
||||
* @param customDelay A custom delay to set the recoveryDelay value that is associated with a account.
|
||||
*/
|
||||
function configureRecovery(
|
||||
bytes32 recoveryHash,
|
||||
bytes32 dkimPublicKeyHash,
|
||||
address _dkimRegistry,
|
||||
uint256 customDelay
|
||||
) external onlyOwner {
|
||||
address account = address(this);
|
||||
|
||||
if (recoveryRequest.executeAfter > 0) {
|
||||
revert RECOVERY_ALREADY_INITIATED();
|
||||
}
|
||||
|
||||
if (customDelay > 0) {
|
||||
recoveryDelay = customDelay;
|
||||
} else {
|
||||
recoveryDelay = defaultDelay;
|
||||
}
|
||||
|
||||
recoveryRequest = RecoveryRequest({
|
||||
recoveryHash: recoveryHash,
|
||||
dkimPublicKeyHash: dkimPublicKeyHash,
|
||||
executeAfter: 0,
|
||||
pendingNewOwner: address(0)
|
||||
});
|
||||
dkimRegistry = _dkimRegistry; // FIXME: could be zero
|
||||
|
||||
emit RecoveryConfigured(
|
||||
account,
|
||||
owner,
|
||||
recoveryHash,
|
||||
dkimPublicKeyHash,
|
||||
dkimRegistry,
|
||||
customDelay
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice Initiates a recovery for an account using a zk email proof.
|
||||
* @dev Rotates the account owner address to a new address. Uses the
|
||||
* default delay period if no custom delay has been set. This is the second
|
||||
* function that should be called in the recovery process - after configureRecovery
|
||||
* @param newOwner The new owner address of the account
|
||||
* @param emailDomain Domain name of the sender's email
|
||||
* @param a Part of the proof
|
||||
* @param b Part of the proof
|
||||
* @param c Part of the proof
|
||||
*/
|
||||
function initiateRecovery(
|
||||
address newOwner,
|
||||
string memory emailDomain,
|
||||
uint256[2] memory a,
|
||||
uint256[2][2] memory b,
|
||||
uint256[2] memory c
|
||||
) external {
|
||||
address account = address(this);
|
||||
|
||||
if (recoveryRequest.recoveryHash == bytes32(0)) {
|
||||
revert RECOVERY_NOT_CONFIGURED();
|
||||
}
|
||||
|
||||
if (recoveryRequest.executeAfter > 0) {
|
||||
revert RECOVERY_ALREADY_INITIATED();
|
||||
}
|
||||
|
||||
if (
|
||||
!this.isDKIMPublicKeyHashValid(
|
||||
emailDomain,
|
||||
recoveryRequest.dkimPublicKeyHash
|
||||
)
|
||||
) {
|
||||
revert INVALID_DKIM_KEY_HASH(
|
||||
account,
|
||||
emailDomain,
|
||||
recoveryRequest.dkimPublicKeyHash
|
||||
);
|
||||
}
|
||||
|
||||
uint256[4] memory publicSignals = [
|
||||
uint256(uint160(account)),
|
||||
uint256(recoveryRequest.recoveryHash),
|
||||
uint256(uint160(newOwner)),
|
||||
uint256(recoveryRequest.dkimPublicKeyHash)
|
||||
];
|
||||
|
||||
// verify proof
|
||||
bool verified = verifier.verifyProof(a, b, c, publicSignals);
|
||||
if (!verified) revert INVALID_PROOF();
|
||||
|
||||
uint256 executeAfter = block.timestamp + recoveryDelay;
|
||||
|
||||
recoveryRequest.executeAfter = executeAfter;
|
||||
recoveryRequest.pendingNewOwner = newOwner;
|
||||
|
||||
emit RecoveryInitiated(account, newOwner, executeAfter);
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice Recovers an account using a zk email proof.
|
||||
* @dev Rotates the account owner address to a new address.
|
||||
* This function is the third and final function that needs to be called in the
|
||||
* recovery process. After configureRecovery & initiateRecovery
|
||||
*/
|
||||
function recoverAccount() public {
|
||||
address account = address(this);
|
||||
|
||||
if (recoveryRequest.executeAfter == 0) {
|
||||
revert RECOVERY_NOT_INITIATED();
|
||||
}
|
||||
|
||||
if (block.timestamp > recoveryRequest.executeAfter) {
|
||||
delete recoveryRequest;
|
||||
|
||||
owner = recoveryRequest.pendingNewOwner;
|
||||
|
||||
emit AccountRecovered(account, recoveryRequest.pendingNewOwner);
|
||||
} else {
|
||||
revert DELAY_NOT_PASSED();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice Cancels the recovery process of the sender if it exits.
|
||||
* @dev Deletes the recovery request accociated with a account. Assumes
|
||||
* the msg.sender is the account that the recovery request is being deleted for
|
||||
*/
|
||||
function cancelRecovery() external {
|
||||
address account = address(this);
|
||||
delete recoveryRequest;
|
||||
emit RecoveryCancelled(account);
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice Sets a custom delay for recovering the account.
|
||||
* @dev Custom delay is used instead of the default delay when recovering the
|
||||
* account. Custom delays should be configured with care as they can be
|
||||
* used to bypass the default delay.
|
||||
* @param delay The custom delay to be used when recovering the account
|
||||
*/
|
||||
function setRecoveryDelay(uint256 delay) external {
|
||||
address account = address(this);
|
||||
recoveryDelay = delay;
|
||||
emit RecoveryDelaySet(account, delay);
|
||||
}
|
||||
|
||||
/// @notice Return the DKIM public key hash for a given email domain and account address
|
||||
/// @param emailDomain Email domain for which the DKIM public key hash is to be returned
|
||||
function isDKIMPublicKeyHashValid(
|
||||
string memory emailDomain,
|
||||
bytes32 publicKeyHash
|
||||
) public view returns (bool) {
|
||||
if (dkimRegistry == address(0)) {
|
||||
return
|
||||
defaultDkimRegistry.isDKIMPublicKeyHashValid(
|
||||
emailDomain,
|
||||
publicKeyHash
|
||||
);
|
||||
} else {
|
||||
return
|
||||
IDKIMRegsitry(dkimRegistry).isDKIMPublicKeyHashValid(
|
||||
emailDomain,
|
||||
publicKeyHash
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
276
packages/plugins/src/erc7579/ERC7579ZkEmailRecoveryModule.sol
Normal file
276
packages/plugins/src/erc7579/ERC7579ZkEmailRecoveryModule.sol
Normal file
@@ -0,0 +1,276 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
pragma solidity ^0.8.0;
|
||||
|
||||
import {IValidator, VALIDATION_SUCCESS, VALIDATION_FAILED, MODULE_TYPE_VALIDATOR} from "erc7579-implementation/src/interfaces/IERC7579Module.sol";
|
||||
import {ModeLib, ModeCode, CallType, CALLTYPE_SINGLE} from "erc7579-implementation/src/lib/ModeLib.sol";
|
||||
import {IERC7579Account} from "erc7579-implementation/src/interfaces/IERC7579Account.sol";
|
||||
import {ExecutionLib} from "erc7579-implementation/src/lib/ExecutionLib.sol";
|
||||
import {PackedUserOperation} from "account-abstraction/interfaces/IEntryPoint.sol";
|
||||
import {ValidationData} from "account-abstraction/core/Helpers.sol";
|
||||
import {MockGroth16Verifier} from "../safe/utils/MockGroth16Verifier.sol";
|
||||
import {MockDKIMRegsitry} from "../safe/utils/MockDKIMRegsitry.sol";
|
||||
import {IDKIMRegsitry} from "../safe/interface/IDKIMRegsitry.sol";
|
||||
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
THIS CONTRACT IS STILL IN ACTIVE DEVELOPMENT. NOT FOR PRODUCTION USE
|
||||
//////////////////////////////////////////////////////////////////////////*/
|
||||
|
||||
struct RecoveryRequest {
|
||||
bytes32 recoveryHash;
|
||||
bytes32 dkimPublicKeyHash;
|
||||
uint256 executeAfter;
|
||||
address pendingNewOwner;
|
||||
}
|
||||
|
||||
contract ERC7579ZkEmailRecoveryModule is IValidator {
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
CONSTANTS & STORAGE
|
||||
//////////////////////////////////////////////////////////////////////////*/
|
||||
|
||||
/** Default DKIM public key hashes registry */
|
||||
IDKIMRegsitry public immutable defaultDkimRegistry;
|
||||
|
||||
/** verifier */
|
||||
MockGroth16Verifier public immutable verifier;
|
||||
|
||||
/** Default delay has been set to a large timeframe on purpose. Please use a default
|
||||
delay suited to your specific context */
|
||||
uint256 public constant defaultDelay = 2 weeks;
|
||||
|
||||
/** recovery hash domain */
|
||||
bytes32 immutable RECOVERY_HASH_DOMAIN;
|
||||
|
||||
/** recovery request */
|
||||
RecoveryRequest public recoveryRequest;
|
||||
|
||||
/** custom recovery delay */
|
||||
uint256 public recoveryDelay;
|
||||
|
||||
/** dkim registry address */
|
||||
address public dkimRegistry;
|
||||
|
||||
mapping(address => bool) internal initialized;
|
||||
|
||||
error RECOVERY_ALREADY_INITIATED();
|
||||
error RECOVERY_NOT_CONFIGURED();
|
||||
error INVALID_DKIM_KEY_HASH(
|
||||
address account,
|
||||
string emailDomain,
|
||||
bytes32 dkimPublicKeyHash
|
||||
);
|
||||
error INVALID_PROOF();
|
||||
error RECOVERY_NOT_INITIATED();
|
||||
error DELAY_NOT_PASSED();
|
||||
error UNSUPPORTED_OPERATION();
|
||||
|
||||
event RecoveryConfigured(
|
||||
address indexed account,
|
||||
address indexed owner,
|
||||
bytes32 recoveryHash,
|
||||
bytes32 dkimPublicKeyHash,
|
||||
address dkimRegistry,
|
||||
uint256 customDelay
|
||||
);
|
||||
event RecoveryInitiated(
|
||||
address indexed account,
|
||||
address newOwner,
|
||||
uint256 executeAfter
|
||||
);
|
||||
event AccountRecovered(address indexed account, address newOwner);
|
||||
event RecoveryCancelled(address indexed account);
|
||||
event RecoveryDelaySet(address indexed account, uint256 delay);
|
||||
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
CONFIG
|
||||
//////////////////////////////////////////////////////////////////////////*/
|
||||
|
||||
function onInstall(bytes calldata data) external override {
|
||||
if (isInitialized(msg.sender)) revert AlreadyInitialized(msg.sender);
|
||||
|
||||
(
|
||||
bytes32 recoveryHash,
|
||||
bytes32 dkimPublicKeyHash,
|
||||
address _dkimRegistry,
|
||||
uint256 customDelay
|
||||
) = abi.decode(data, (bytes32, bytes32, address, uint256));
|
||||
|
||||
address account = msg.sender;
|
||||
|
||||
if (recoveryRequest.executeAfter > 0) {
|
||||
revert RECOVERY_ALREADY_INITIATED();
|
||||
}
|
||||
|
||||
if (customDelay > 0) {
|
||||
recoveryDelay = customDelay;
|
||||
} else {
|
||||
recoveryDelay = defaultDelay;
|
||||
}
|
||||
|
||||
recoveryRequest = RecoveryRequest({
|
||||
recoveryHash: recoveryHash,
|
||||
dkimPublicKeyHash: dkimPublicKeyHash,
|
||||
executeAfter: 0,
|
||||
pendingNewOwner: address(0)
|
||||
});
|
||||
dkimRegistry = _dkimRegistry; // FIXME: could be zero
|
||||
|
||||
initialized[msg.sender] = true;
|
||||
|
||||
emit RecoveryConfigured(
|
||||
account,
|
||||
msg.sender,
|
||||
recoveryHash,
|
||||
dkimPublicKeyHash,
|
||||
dkimRegistry,
|
||||
customDelay
|
||||
);
|
||||
}
|
||||
|
||||
function onUninstall(bytes calldata) external override {
|
||||
if (!isInitialized(msg.sender)) revert NotInitialized(msg.sender);
|
||||
initialized[msg.sender] = false;
|
||||
}
|
||||
|
||||
function isInitialized(address smartAccount) public view returns (bool) {
|
||||
return initialized[smartAccount];
|
||||
}
|
||||
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
MODULE LOGIC
|
||||
//////////////////////////////////////////////////////////////////////////*/
|
||||
|
||||
function validateUserOp(
|
||||
PackedUserOperation calldata userOp,
|
||||
bytes32 userOpHash
|
||||
) external override returns (uint256) {
|
||||
address account = address(this);
|
||||
|
||||
(
|
||||
address newOwner,
|
||||
string memory emailDomain,
|
||||
uint256[2] memory a,
|
||||
uint256[2][2] memory b,
|
||||
uint256[2] memory c
|
||||
) = abi.decode(
|
||||
userOp.signature,
|
||||
(address, string, uint256[2], uint256[2][2], uint256[2])
|
||||
);
|
||||
|
||||
// Check if the execution is allowed
|
||||
bool isAllowedExecution;
|
||||
bytes4 selector = bytes4(userOp.callData[0:4]);
|
||||
if (selector == IERC7579Account.execute.selector) {
|
||||
// Decode and check the execution
|
||||
// Only single executions to installed validators are allowed
|
||||
isAllowedExecution = _decodeAndCheckExecution(userOp.callData);
|
||||
}
|
||||
|
||||
if (recoveryRequest.recoveryHash == bytes32(0)) {
|
||||
return VALIDATION_FAILED;
|
||||
}
|
||||
|
||||
if (recoveryRequest.executeAfter > 0) {
|
||||
return VALIDATION_FAILED;
|
||||
}
|
||||
|
||||
if (
|
||||
!this.isDKIMPublicKeyHashValid(
|
||||
emailDomain,
|
||||
recoveryRequest.dkimPublicKeyHash
|
||||
)
|
||||
) {
|
||||
return VALIDATION_FAILED;
|
||||
}
|
||||
|
||||
uint256[4] memory publicSignals = [
|
||||
uint256(uint160(account)),
|
||||
uint256(recoveryRequest.recoveryHash),
|
||||
uint256(uint160(newOwner)),
|
||||
uint256(recoveryRequest.dkimPublicKeyHash)
|
||||
];
|
||||
|
||||
// verify proof
|
||||
bool verified = verifier.verifyProof(a, b, c, publicSignals);
|
||||
if (!verified) return VALIDATION_FAILED;
|
||||
|
||||
uint256 executeAfter = block.timestamp + recoveryDelay;
|
||||
recoveryRequest.executeAfter = executeAfter;
|
||||
recoveryRequest.pendingNewOwner = newOwner;
|
||||
|
||||
emit RecoveryInitiated(account, newOwner, executeAfter);
|
||||
return VALIDATION_SUCCESS;
|
||||
}
|
||||
|
||||
function isValidSignatureWithSender(
|
||||
address,
|
||||
bytes32 hash,
|
||||
bytes calldata data
|
||||
) external view override returns (bytes4) {
|
||||
// ERC-1271 not supported for recovery
|
||||
revert UNSUPPORTED_OPERATION();
|
||||
}
|
||||
|
||||
/// @notice Return the DKIM public key hash for a given email domain and account address
|
||||
/// @param emailDomain Email domain for which the DKIM public key hash is to be returned
|
||||
function isDKIMPublicKeyHashValid(
|
||||
string memory emailDomain,
|
||||
bytes32 publicKeyHash
|
||||
) public view returns (bool) {
|
||||
if (dkimRegistry == address(0)) {
|
||||
return
|
||||
defaultDkimRegistry.isDKIMPublicKeyHashValid(
|
||||
emailDomain,
|
||||
publicKeyHash
|
||||
);
|
||||
} else {
|
||||
return
|
||||
IDKIMRegsitry(dkimRegistry).isDKIMPublicKeyHashValid(
|
||||
emailDomain,
|
||||
publicKeyHash
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
INTERNAL
|
||||
//////////////////////////////////////////////////////////////////////////*/
|
||||
|
||||
function _decodeAndCheckExecution(
|
||||
bytes calldata callData
|
||||
) internal returns (bool isAllowedExecution) {
|
||||
// Get the mode and call type
|
||||
ModeCode mode = ModeCode.wrap(bytes32(callData[4:36]));
|
||||
CallType calltype = ModeLib.getCallType(mode);
|
||||
|
||||
if (calltype == CALLTYPE_SINGLE) {
|
||||
// Decode the calldata
|
||||
(address to, , ) = ExecutionLib.decodeSingle(callData[100:]);
|
||||
|
||||
// Check if the module is installed as a validator
|
||||
return
|
||||
IERC7579Account(msg.sender).isModuleInstalled(
|
||||
MODULE_TYPE_VALIDATOR,
|
||||
to,
|
||||
""
|
||||
);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
METADATA
|
||||
//////////////////////////////////////////////////////////////////////////*/
|
||||
|
||||
function name() external pure returns (string memory) {
|
||||
return "SocialRecoveryValidator";
|
||||
}
|
||||
|
||||
function version() external pure returns (string memory) {
|
||||
return "0.0.1";
|
||||
}
|
||||
|
||||
function isModuleType(uint256 typeID) external view returns (bool) {
|
||||
return typeID == MODULE_TYPE_VALIDATOR;
|
||||
}
|
||||
}
|
||||
@@ -5,7 +5,11 @@ pragma solidity >=0.8.4 <0.9.0;
|
||||
import {IKernelValidator, UserOperation} from "kernel/src/interfaces/IKernelValidator.sol";
|
||||
import {ValidationData} from "kernel/src/common/Types.sol";
|
||||
import {SIG_VALIDATION_FAILED, ValidationData} from "kernel/src/common/Constants.sol";
|
||||
import {BLS} from "account-abstraction/contracts/samples/bls/lib/hubble-contracts/contracts/libs/BLS.sol";
|
||||
import {BLS} from "account-abstraction/samples/bls/lib/hubble-contracts/contracts/libs/BLS.sol";
|
||||
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
THIS CONTRACT IS STILL IN ACTIVE DEVELOPMENT. NOT FOR PRODUCTION USE
|
||||
//////////////////////////////////////////////////////////////////////////*/
|
||||
|
||||
// BLSValidator is a validator that uses BLS signatures to validate transactions.
|
||||
// TODO: Consider account recovery, aggregate signatures, and use EIP 712.
|
||||
|
||||
@@ -3,6 +3,10 @@ pragma solidity ^0.8.12;
|
||||
|
||||
import {FCL_WebAuthn} from "./libraries/FCL_Webauthn.sol";
|
||||
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
THIS CONTRACT IS STILL IN ACTIVE DEVELOPMENT. NOT FOR PRODUCTION USE
|
||||
//////////////////////////////////////////////////////////////////////////*/
|
||||
|
||||
contract WebAuthn {
|
||||
function verifySignature(
|
||||
bytes calldata authenticatorData,
|
||||
@@ -13,15 +17,14 @@ contract WebAuthn {
|
||||
uint256[2] calldata signature,
|
||||
uint256[2] calldata publicKey
|
||||
) internal returns (bool verified) {
|
||||
verified =
|
||||
FCL_WebAuthn.checkSignature(
|
||||
authenticatorData,
|
||||
authenticatorDataFlagMask,
|
||||
clientData,
|
||||
clientChallenge,
|
||||
clientChallengeDataOffset,
|
||||
signature,
|
||||
publicKey
|
||||
);
|
||||
verified = FCL_WebAuthn.checkSignature(
|
||||
authenticatorData,
|
||||
authenticatorDataFlagMask,
|
||||
clientData,
|
||||
clientChallenge,
|
||||
clientChallengeDataOffset,
|
||||
signature,
|
||||
publicKey
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,12 +4,16 @@ pragma abicoder v2;
|
||||
|
||||
import {HandlerContext} from "safe-contracts/contracts/handler/HandlerContext.sol";
|
||||
|
||||
import {IEntryPoint, UserOperation} 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 {IEntryPoint, PackedUserOperation} from "account-abstraction/interfaces/IEntryPoint.sol";
|
||||
import {BLS} from "account-abstraction/samples/bls/lib/hubble-contracts/contracts/libs/BLS.sol";
|
||||
import {IBLSAccount} from "account-abstraction/samples/bls/IBLSAccount.sol";
|
||||
|
||||
import {Safe4337Base, ISafe} from "./utils/Safe4337Base.sol";
|
||||
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
THIS CONTRACT IS STILL IN ACTIVE DEVELOPMENT. NOT FOR PRODUCTION USE
|
||||
//////////////////////////////////////////////////////////////////////////*/
|
||||
|
||||
error IncorrectSignatureLength(uint256 length);
|
||||
|
||||
contract SafeBlsPlugin is Safe4337Base, IBLSAccount {
|
||||
@@ -59,14 +63,15 @@ 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;
|
||||
|
||||
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(
|
||||
|
||||
@@ -6,11 +6,15 @@ import {Safe} from "safe-contracts/contracts/Safe.sol";
|
||||
import {SafeProxyFactory} from "safe-contracts/contracts/proxies/SafeProxyFactory.sol";
|
||||
import {SafeProxy} from "safe-contracts/contracts/proxies/SafeProxy.sol";
|
||||
|
||||
import {EntryPoint} from "account-abstraction/contracts/core/EntryPoint.sol";
|
||||
import {EntryPoint} from "account-abstraction/core/EntryPoint.sol";
|
||||
|
||||
import {SafeCompressionPlugin} from "./SafeCompressionPlugin.sol";
|
||||
import {IDecompressor} from "../compression/decompressors/IDecompressor.sol";
|
||||
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
THIS CONTRACT IS STILL IN ACTIVE DEVELOPMENT. NOT FOR PRODUCTION USE
|
||||
//////////////////////////////////////////////////////////////////////////*/
|
||||
|
||||
contract SafeCompressionFactory {
|
||||
function create(
|
||||
Safe safeSingleton,
|
||||
@@ -23,9 +27,9 @@ contract SafeCompressionFactory {
|
||||
) external returns (SafeCompressionPlugin) {
|
||||
bytes32 salt = keccak256(abi.encodePacked(owner, saltNonce));
|
||||
|
||||
Safe safe = Safe(payable(new SafeProxy{salt: salt}(
|
||||
address(safeSingleton)
|
||||
)));
|
||||
Safe safe = Safe(
|
||||
payable(new SafeProxy{salt: salt}(address(safeSingleton)))
|
||||
);
|
||||
|
||||
address[] memory owners = new address[](1);
|
||||
owners[0] = owner;
|
||||
|
||||
@@ -4,14 +4,19 @@ pragma abicoder v2;
|
||||
|
||||
import {HandlerContext} from "safe-contracts/contracts/handler/HandlerContext.sol";
|
||||
|
||||
import {IEntryPoint, UserOperation} 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 {IEntryPoint, PackedUserOperation} from "account-abstraction/interfaces/IEntryPoint.sol";
|
||||
import {BLS} from "account-abstraction/samples/bls/lib/hubble-contracts/contracts/libs/BLS.sol";
|
||||
import {IBLSAccount} from "account-abstraction/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";
|
||||
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
THIS CONTRACT IS STILL IN ACTIVE DEVELOPMENT. NOT FOR PRODUCTION USE
|
||||
//////////////////////////////////////////////////////////////////////////*/
|
||||
|
||||
error IncorrectSignatureLength(uint256 length);
|
||||
|
||||
contract SafeCompressionPlugin is Safe4337Base, IBLSAccount {
|
||||
@@ -38,12 +43,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 +60,7 @@ contract SafeCompressionPlugin is Safe4337Base, IBLSAccount {
|
||||
}
|
||||
}
|
||||
|
||||
function setDecompressor(
|
||||
IDecompressor decompressorParam
|
||||
) public {
|
||||
function setDecompressor(IDecompressor decompressorParam) public {
|
||||
_requireFromCurrentSafeOrEntryPoint();
|
||||
_decompressor = decompressorParam;
|
||||
}
|
||||
@@ -77,14 +78,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(
|
||||
|
||||
@@ -6,10 +6,14 @@ import {Safe} from "safe-contracts/contracts/Safe.sol";
|
||||
import {SafeProxyFactory} from "safe-contracts/contracts/proxies/SafeProxyFactory.sol";
|
||||
import {SafeProxy} from "safe-contracts/contracts/proxies/SafeProxy.sol";
|
||||
|
||||
import {EntryPoint} from "account-abstraction/contracts/core/EntryPoint.sol";
|
||||
import {EntryPoint} from "account-abstraction/core/EntryPoint.sol";
|
||||
|
||||
import {SafeECDSAPlugin} from "./SafeECDSAPlugin.sol";
|
||||
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
THIS CONTRACT IS STILL IN ACTIVE DEVELOPMENT. NOT FOR PRODUCTION USE
|
||||
//////////////////////////////////////////////////////////////////////////*/
|
||||
|
||||
contract SafeECDSAFactory {
|
||||
function create(
|
||||
Safe safeSingleton,
|
||||
@@ -19,9 +23,9 @@ contract SafeECDSAFactory {
|
||||
) external returns (SafeECDSAPlugin) {
|
||||
bytes32 salt = keccak256(abi.encodePacked(owner, saltNonce));
|
||||
|
||||
Safe safe = Safe(payable(new SafeProxy{salt: salt}(
|
||||
address(safeSingleton)
|
||||
)));
|
||||
Safe safe = Safe(
|
||||
payable(new SafeProxy{salt: salt}(address(safeSingleton)))
|
||||
);
|
||||
|
||||
address[] memory owners = new address[](1);
|
||||
owners[0] = owner;
|
||||
|
||||
@@ -2,11 +2,16 @@
|
||||
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/interfaces/IEntryPoint.sol";
|
||||
import {PackedUserOperation} from "account-abstraction/interfaces/IEntryPoint.sol";
|
||||
|
||||
import {ECDSA} from "openzeppelin-contracts/contracts/utils/cryptography/ECDSA.sol";
|
||||
import {MessageHashUtils} from "@openzeppelin/contracts/utils/cryptography/MessageHashUtils.sol";
|
||||
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
THIS CONTRACT IS STILL IN ACTIVE DEVELOPMENT. NOT FOR PRODUCTION USE
|
||||
//////////////////////////////////////////////////////////////////////////*/
|
||||
|
||||
interface ISafe {
|
||||
function enableModule(address module) external;
|
||||
@@ -88,11 +93,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,11 @@
|
||||
pragma solidity ^0.8.0;
|
||||
|
||||
import {ECDSA} from "openzeppelin-contracts/contracts/utils/cryptography/ECDSA.sol";
|
||||
import {MessageHashUtils} from "@openzeppelin/contracts/utils/cryptography/MessageHashUtils.sol";
|
||||
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
THIS CONTRACT IS STILL IN ACTIVE DEVELOPMENT. NOT FOR PRODUCTION USE
|
||||
//////////////////////////////////////////////////////////////////////////*/
|
||||
|
||||
contract Enum {
|
||||
enum Operation {
|
||||
@@ -114,7 +119,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();
|
||||
|
||||
@@ -2,11 +2,15 @@
|
||||
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 {BaseAccount} from "account-abstraction/core/BaseAccount.sol";
|
||||
import {IEntryPoint, PackedUserOperation} from "account-abstraction/interfaces/IEntryPoint.sol";
|
||||
import {WebAuthn} from "../primitives/WebAuthn.sol";
|
||||
|
||||
import {Safe4337Base} from "./utils/Safe4337Base.sol";
|
||||
import {Safe4337Base, SIG_VALIDATION_FAILED} from "./utils/Safe4337Base.sol";
|
||||
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
THIS CONTRACT IS STILL IN ACTIVE DEVELOPMENT. NOT FOR PRODUCTION USE
|
||||
//////////////////////////////////////////////////////////////////////////*/
|
||||
|
||||
interface ISafe {
|
||||
function enableModule(address module) external;
|
||||
@@ -66,7 +70,7 @@ contract SafeWebAuthnPlugin is Safe4337Base, WebAuthn {
|
||||
}
|
||||
|
||||
function _validateSignature(
|
||||
UserOperation calldata userOp,
|
||||
PackedUserOperation calldata userOp,
|
||||
bytes32 /*userOpHash*/
|
||||
) internal override returns (uint256 validationData) {
|
||||
bytes calldata authenticatorData;
|
||||
@@ -80,7 +84,10 @@ contract SafeWebAuthnPlugin is Safe4337Base, WebAuthn {
|
||||
// parse length of all fixed-length params (including length)
|
||||
uint i = 0;
|
||||
uint dataLen = 32;
|
||||
uint256 paramLen = abi.decode(userOp.signature[i:i+dataLen], (uint256));
|
||||
uint256 paramLen = abi.decode(
|
||||
userOp.signature[i:i + dataLen],
|
||||
(uint256)
|
||||
);
|
||||
// Fixed-length params (bytes1, (uint256?), bytes32, uint256, uint256[2], uint256[2]). Expect 9 slots (288 bytes)
|
||||
i += dataLen; // advance index
|
||||
|
||||
@@ -88,12 +95,12 @@ contract SafeWebAuthnPlugin is Safe4337Base, WebAuthn {
|
||||
dataLen = paramLen - 32; // length already read
|
||||
dataLen -= 2 * 2 * 32; // exclude fixed length arrays
|
||||
(
|
||||
wrapper.authenticatorDataFlagMask,
|
||||
, // some number
|
||||
wrapper.authenticatorDataFlagMask, // some number
|
||||
,
|
||||
wrapper.clientChallenge,
|
||||
wrapper.clientChallengeDataOffset
|
||||
) = abi.decode(
|
||||
userOp.signature[i:i+dataLen],
|
||||
userOp.signature[i:i + dataLen],
|
||||
(
|
||||
bytes1,
|
||||
uint256, //not sure what is encoded here
|
||||
@@ -103,38 +110,37 @@ contract SafeWebAuthnPlugin is Safe4337Base, WebAuthn {
|
||||
);
|
||||
i += dataLen; // advance index
|
||||
|
||||
|
||||
bytes calldata calldataLocation;
|
||||
// load fixed length array params (pointers to calldata)
|
||||
dataLen = 2 * 32;
|
||||
calldataLocation = userOp.signature[i:i+dataLen];
|
||||
assembly{
|
||||
calldataLocation = userOp.signature[i:i + dataLen];
|
||||
assembly {
|
||||
signature := calldataLocation.offset
|
||||
}
|
||||
i += dataLen; // advance index
|
||||
|
||||
calldataLocation = userOp.signature[i:i+dataLen];
|
||||
assembly{
|
||||
calldataLocation = userOp.signature[i:i + dataLen];
|
||||
assembly {
|
||||
pubKey := calldataLocation.offset
|
||||
}
|
||||
i += dataLen; // advance index
|
||||
|
||||
// parse length of authenticatorData
|
||||
dataLen = 32;
|
||||
paramLen = abi.decode(userOp.signature[i:i+dataLen], (uint256));
|
||||
paramLen = abi.decode(userOp.signature[i:i + dataLen], (uint256));
|
||||
i += dataLen; // advance index
|
||||
// assign authenticatorData to sig splice
|
||||
dataLen = paramLen;
|
||||
authenticatorData = userOp.signature[i:i+dataLen];
|
||||
authenticatorData = userOp.signature[i:i + dataLen];
|
||||
i += ((dataLen >> 5) + 1) << 5; // advance index (round up to next slot)
|
||||
|
||||
// parse length of clientData
|
||||
dataLen = 32;
|
||||
paramLen = abi.decode(userOp.signature[i:i+dataLen], (uint256));
|
||||
paramLen = abi.decode(userOp.signature[i:i + dataLen], (uint256));
|
||||
i += dataLen; // advance index
|
||||
// assign clientData to sig splice
|
||||
dataLen = paramLen;
|
||||
clientData = userOp.signature[i:i+dataLen];
|
||||
clientData = userOp.signature[i:i + dataLen];
|
||||
// i += ((dataLen >> 5) + 1) << 5; // advance index (round up to next slot)
|
||||
} // end scope to free vars from stack
|
||||
|
||||
|
||||
@@ -6,11 +6,15 @@ import {Safe} from "safe-contracts/contracts/Safe.sol";
|
||||
import {SafeProxyFactory} from "safe-contracts/contracts/proxies/SafeProxyFactory.sol";
|
||||
import {SafeProxy} from "safe-contracts/contracts/proxies/SafeProxy.sol";
|
||||
|
||||
import {EntryPoint} from "account-abstraction/contracts/core/EntryPoint.sol";
|
||||
import {EntryPoint} from "account-abstraction/core/EntryPoint.sol";
|
||||
|
||||
import {SafeZKPPasswordPlugin} from "./SafeZKPPasswordPlugin.sol";
|
||||
import {IGroth16Verifier} from "./interface/IGroth16Verifier.sol";
|
||||
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
THIS CONTRACT IS STILL IN ACTIVE DEVELOPMENT. NOT FOR PRODUCTION USE
|
||||
//////////////////////////////////////////////////////////////////////////*/
|
||||
|
||||
contract SafeZKPPasswordFactory {
|
||||
function create(
|
||||
Safe safeSingleton,
|
||||
|
||||
@@ -4,8 +4,12 @@ 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/interfaces/IEntryPoint.sol";
|
||||
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
THIS CONTRACT IS STILL IN ACTIVE DEVELOPMENT. NOT FOR PRODUCTION USE
|
||||
//////////////////////////////////////////////////////////////////////////*/
|
||||
|
||||
struct ZKPPasswordOwnerStorage {
|
||||
address owner;
|
||||
@@ -76,7 +80,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
|
||||
|
||||
@@ -6,6 +6,10 @@ import {MockDKIMRegsitry} from "./utils/MockDKIMRegsitry.sol";
|
||||
import {IDKIMRegsitry} from "./interface/IDKIMRegsitry.sol";
|
||||
import {ISafe} from "./utils/Safe4337Base.sol";
|
||||
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
THIS CONTRACT IS STILL IN ACTIVE DEVELOPMENT. NOT FOR PRODUCTION USE
|
||||
//////////////////////////////////////////////////////////////////////////*/
|
||||
|
||||
interface ISafeECDSAPlugin {
|
||||
function getOwner(address safe) external view returns (address);
|
||||
}
|
||||
|
||||
@@ -3,8 +3,8 @@ pragma solidity >=0.7.0 <0.9.0;
|
||||
pragma abicoder v2;
|
||||
|
||||
import {HandlerContext} from "safe-contracts/contracts/handler/HandlerContext.sol";
|
||||
|
||||
import {BaseAccount} from "account-abstraction/contracts/core/BaseAccount.sol";
|
||||
import {SIG_VALIDATION_FAILED} from "account-abstraction/core/Helpers.sol";
|
||||
import {BaseAccount} from "account-abstraction/core/BaseAccount.sol";
|
||||
|
||||
interface ISafe {
|
||||
/**
|
||||
|
||||
@@ -14,6 +14,6 @@ import {MultiSendCallOnly} from "safe-contracts/contracts/libraries/MultiSendCal
|
||||
import {SignMessageLib} from "safe-contracts/contracts/libraries/SignMessageLib.sol";
|
||||
import {SafeL2} from "safe-contracts/contracts/SafeL2.sol";
|
||||
import {Safe} from "safe-contracts/contracts/Safe.sol";
|
||||
import {EntryPoint} from "account-abstraction/contracts/core/EntryPoint.sol";
|
||||
import {SimpleAccountFactory} from "account-abstraction/contracts/samples/SimpleAccountFactory.sol";
|
||||
import {BLSSignatureAggregator} from "account-abstraction/contracts/samples/bls/BLSSignatureAggregator.sol";
|
||||
import {EntryPoint} from "account-abstraction/core/EntryPoint.sol";
|
||||
import {SimpleAccountFactory} from "account-abstraction/samples/SimpleAccountFactory.sol";
|
||||
import {BLSSignatureAggregator} from "account-abstraction/samples/bls/BLSSignatureAggregator.sol";
|
||||
|
||||
@@ -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 { BytesLike, ethers, getBytes, NonceManager, Signer } from "ethers";
|
||||
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";
|
||||
@@ -12,12 +10,19 @@ import {
|
||||
} from "../../../typechain-types";
|
||||
import receiptOf from "./receiptOf";
|
||||
import { calculateProxyAddress } from "./calculateProxyAddress";
|
||||
import { getFeeData, getGasEstimates } from "./getGasEstimates";
|
||||
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";
|
||||
@@ -13,7 +13,7 @@ import {ERC4337Utils} from "kernel/test/foundry/utils/ERC4337Utils.sol";
|
||||
|
||||
import {IEntryPoint} from "I4337/interfaces/IEntryPoint.sol";
|
||||
import {ENTRYPOINT_0_6_ADDRESS, ENTRYPOINT_0_6_BYTECODE, CREATOR_0_6_BYTECODE, CREATOR_0_6_ADDRESS} from "I4337/artifacts/EntryPoint_0_6.sol";
|
||||
import {EntryPoint} from "account-abstraction/contracts/core/EntryPoint.sol";
|
||||
import {EntryPoint} from "account-abstraction/core/EntryPoint.sol";
|
||||
|
||||
import {BLSValidator} from "../../../src/kernel/BLSValidator.sol";
|
||||
import "forge-std/Test.sol";
|
||||
|
||||
@@ -7,8 +7,7 @@ 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";
|
||||
import {BLSSignatureAggregator} from "account-abstraction/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/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/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/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"
|
||||
@@ -904,25 +912,20 @@
|
||||
"@nomicfoundation/solidity-analyzer-win32-ia32-msvc" "0.1.1"
|
||||
"@nomicfoundation/solidity-analyzer-win32-x64-msvc" "0.1.1"
|
||||
|
||||
"@nomiclabs/hardhat-ethers@npm:hardhat-deploy-ethers":
|
||||
version "0.4.1"
|
||||
resolved "https://registry.yarnpkg.com/hardhat-deploy-ethers/-/hardhat-deploy-ethers-0.4.1.tgz#dd70b0cc413ed99e98994047b383a004cf1c14f8"
|
||||
integrity sha512-RM6JUcD0dOCjemxnKLtK7XQQI7NWn+LxF5qicGYax0PtWayEUXAewOb4WIHZ/yearhj+s2t6dL0MnHyLTENwJg==
|
||||
"@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==
|
||||
|
||||
"@pcd/pcd-types@^0.10.0":
|
||||
version "0.10.0"
|
||||
resolved "https://registry.yarnpkg.com/@pcd/pcd-types/-/pcd-types-0.10.0.tgz#2076b37fe3d95fd2545e321a37c7b3a24bfd70a5"
|
||||
integrity sha512-QoBlHYVhNA8nbjpl8hmwHLkV78XolFCC7hdpt0U+STY7dfQO+aAegVYbx18yHo4G76nFj9xRUSfjVFV8ZzxIlQ==
|
||||
|
||||
"@pcd/tsconfig@^0.6.0":
|
||||
version "0.6.2"
|
||||
resolved "https://registry.yarnpkg.com/@pcd/tsconfig/-/tsconfig-0.6.2.tgz#64d3b279eb69f40c58d218b628828b9c9e51ce33"
|
||||
integrity sha512-sdva2fJW+fB1a4xQ63oNhDwSJFrNn1Rc5TyJpuSeRH48U7YAQOYkYiKJz1jlRW6Ak0QJaSDgVhBmzsIE2DmNIg==
|
||||
"@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"
|
||||
@@ -1325,22 +1328,31 @@
|
||||
"@typescript-eslint/types" "6.7.0"
|
||||
eslint-visitor-keys "^3.4.1"
|
||||
|
||||
"@zk-email/helpers@^3.1.3":
|
||||
version "3.2.3"
|
||||
resolved "https://registry.yarnpkg.com/@zk-email/helpers/-/helpers-3.2.3.tgz#a31aa06f6fc97938cc6ae766233febb1f477298e"
|
||||
integrity sha512-jhHqRqnCkwg6a2k3OkNRUd99sO7zkG/H/Pd/HL4PHhtS17Lqby/btOu0W3y7AX7wWn13xhNdonjuMEsISYRpQg==
|
||||
"@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:
|
||||
addressparser "^1.0.1"
|
||||
atob "^2.1.2"
|
||||
circomlibjs "^0.1.7"
|
||||
libmime "^5.2.1"
|
||||
localforage "^1.10.0"
|
||||
lodash "^4.17.21"
|
||||
node-forge "^1.3.1"
|
||||
pako "^2.1.0"
|
||||
pki "^1.1.0"
|
||||
psl "^1.9.0"
|
||||
snarkjs "https://github.com/sampritipanda/snarkjs.git#fef81fc51d17a734637555c6edbd585ecda02d9e"
|
||||
"@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"
|
||||
@@ -1726,6 +1738,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