Skip to content

Commit

Permalink
wip
Browse files Browse the repository at this point in the history
  • Loading branch information
mmv08 committed Sep 28, 2023
1 parent 4f2c59c commit 79b5f1c
Show file tree
Hide file tree
Showing 2 changed files with 63 additions and 21 deletions.
38 changes: 35 additions & 3 deletions contracts/contracts/ERC4337Plugin.sol
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@ interface ISafe {
function enablePlugin(address plugin, uint8 permissions) external;

function setFunctionHandler(bytes4 selector, address functionHandler) external;

function checkSignatures(bytes32 dataHash, bytes memory, bytes memory signatures) external;
}

struct UserOperation {
Expand Down Expand Up @@ -60,6 +62,10 @@ contract ERC4337Plugin is ISafeProtocolFunctionHandler, BasePluginWithEventMetad
ISafeProtocolManager public immutable SAFE_PROTOCOL_MANAGER;
address payable public immutable ENTRY_POINT;

//return value in case of signature failure, with no time-range.
// equivalent to _packValidationData(true,0,0);
uint256 internal constant SIG_VALIDATION_FAILED = 1;

constructor(
ISafeProtocolManager safeCoreProtocolManager,
address payable entryPoint
Expand All @@ -71,26 +77,32 @@ contract ERC4337Plugin is ISafeProtocolFunctionHandler, BasePluginWithEventMetad

function validateUserOp(UserOperation calldata userOp, bytes32, uint256 missingAccountFunds) external returns (uint256 validationData) {
require(msg.sender == address(PLUGIN_ADDRESS));

address payable safeAddress = payable(userOp.sender);
ISafe senderSafe = ISafe(safeAddress);
senderSafe.checkSignatures(getUserOpHash(userOp), "", userOp.signature);

if (missingAccountFunds != 0) {
senderSafe.execTransactionFromModule(ENTRY_POINT, missingAccountFunds, "", 0);
SAFE_PROTOCOL_MANAGER.executeTransaction(
userOp.sender,
SafeTransaction({to: ENTRY_POINT, value: missingAccountFunds, data: ""})
);
}

return 0;
}

function execTransaction(address payable to, uint256 value, bytes calldata data) external {
function execTransaction(address safe, address payable to, uint256 value, bytes calldata data) external {
require(msg.sender == address(PLUGIN_ADDRESS));
address payable safeAddress = payable(msg.sender);
ISafe safe = ISafe(safeAddress);

require(safe.execTransactionFromModule(to, value, data, 0), "tx failed");
require(SAFE_PROTOCOL_MANAGER.executeTransaction(safe, SafeTransaction({to: to, value: value, data: data})));
}

function handle(address safe, address sender, uint256 value, bytes calldata data) external returns (bytes memory result) {
bytes4 selector = bytes4(data[0:4]);
requireFromEntryPoint(sender);

if (selector == this.validateUserOp.selector) {
(, result) = PLUGIN_ADDRESS.call(data);
Expand Down Expand Up @@ -124,6 +136,26 @@ contract ERC4337Plugin is ISafeProtocolFunctionHandler, BasePluginWithEventMetad
location = abi.encode(address(this));
}

function packUserOperation(UserOperation calldata userOp) internal pure returns (bytes memory ret) {
//lighter signature scheme. must match UserOp.ts#packUserOp
bytes calldata sig = userOp.signature;
// copy directly the userOp from calldata up to (but not including) the signature.
// this encoding depends on the ABI encoding of calldata, but is much lighter to copy
// than referencing each field separately.
assembly {
let ofs := userOp
let len := sub(sub(sig.offset, ofs), 32)
ret := mload(0x40)
mstore(0x40, add(ret, add(len, 32)))
mstore(ret, len)
calldatacopy(add(ret, 32), ofs, len)
}
}

function getUserOpHash(UserOperation calldata userOp) internal pure returns (bytes32) {
return keccak256(packUserOperation(userOp));
}

function supportsInterface(bytes4 interfaceId) external pure override(BasePlugin, IERC165) returns (bool) {
return
interfaceId == type(ISafeProtocolPlugin).interfaceId ||
Expand Down
46 changes: 28 additions & 18 deletions contracts/test/ERC4337Plugin.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,26 +28,28 @@ const MNEMONIC = process.env.ERC4337_TEST_MNEMONIC;
const randomAddress = "0x1234567890123456789012345678901234567890";

describe("ERC4337 Plugin", () => {
const setup = deployments.createFixture(async ({ deployments }) => {
const signers = await hre.ethers.getSigners();
const hardhatNetworkSetup = deployments.createFixture(async ({ deployments }) => {
await deployments.fixture();
const signers = await hre.ethers.getSigners();
const manager = await ethers.getContractAt("MockContract", await getProtocolManagerAddress(hre));

const testRegistryFactory = await ethers.getContractFactory("TestSafeProtocolRegistryUnrestricted");
const testRegistryDeployment = await testRegistryFactory.deploy(signers[0].address);
const safeProtocolManager = await (
await ethers.getContractFactory("SafeProtocolManager")
).deploy(signers[0].address, testRegistryDeployment.getAddress());
const erc4337Plugin = await (
await ethers.getContractFactory("ERC4337Plugin")
).deploy(safeProtocolManager.getAddress(), randomAddress);
const testRegistryDeployment = await ethers
.getContractFactory("TestSafeProtocolRegistryUnrestricted")
.then((factory) => factory.deploy(signers[0].address));
const safeProtocolManager = await ethers
.getContractFactory("SafeProtocolManager")
.then((factory) => factory.deploy(signers[0].address, testRegistryDeployment.getAddress()));
const erc4337Plugin = await ethers
.getContractFactory("ERC4337Plugin")
.then((factory) => factory.deploy(safeProtocolManager.getAddress(), randomAddress));

await testRegistryDeployment
.addModule(erc4337Plugin.getAddress(), ModuleType.Plugin + ModuleType.FunctionHandler)
.then((tx) => tx.wait(1));

const account = await (await ethers.getContractFactory("ExecutableMockContract")).deploy();
const proxyFactory = await (await (await getSafeProxyFactoryContractFactory()).deploy()).waitForDeployment();
const safeSingleton = await (await (await getSafeSingletonContractFactory()).deploy()).waitForDeployment();
const account = await ethers.getContractFactory("ExecutableMockContract").then((f) => f.deploy());
const proxyFactory = await getSafeProxyFactoryContractFactory().then((f) => f.deploy());
const safeSingleton = await getSafeSingletonContractFactory().then((f) => f.deploy());

const callData = erc4337Plugin.interface.encodeFunctionData("enableSafeCoreProtocolWith4337Plugin");
const safe = await deploySafe(proxyFactory, safeSingleton, [signers[0].address], 1, await erc4337Plugin.getAddress(), callData);
Expand All @@ -62,14 +64,14 @@ describe("ERC4337 Plugin", () => {
});

it("should be initialized correctly", async () => {
const { erc4337Plugin } = await setup();
const { erc4337Plugin } = await hardhatNetworkSetup();
expect(await erc4337Plugin.name()).to.be.eq("ERC4337 Plugin");
expect(await erc4337Plugin.version()).to.be.eq("1.0.0");
expect(await erc4337Plugin.permissions()).to.be.eq(1);
});

it.only("can retrieve metadata for the module", async () => {
const { erc4337Plugin } = await setup();
it("can retrieve metadata for the module", async () => {
const { erc4337Plugin } = await hardhatNetworkSetup();
expect(await loadPluginMetadata(hre, erc4337Plugin)).to.be.deep.eq({
name: "ERC4337 Plugin",
version: "1.0.0",
Expand All @@ -79,10 +81,18 @@ describe("ERC4337 Plugin", () => {
});
});

it("can deploy a safe with manager and ERC4337 module", async () => {
const { erc4337Plugin, manager, signers, safe } = await setup();
it("can validate a signed user operation and send the prefund", async () => {
const { erc4337Plugin, manager, signers, safe } = await hardhatNetworkSetup();
});

it("rejects a signed user operation if the signature is invalid", async () => {});

it("can execute a transaction coming from the entrypoint", async () => {});

it("rejects validation requests coming from an address that is not the entrypoint", async () => {});

it("rejects execution requests coming from an address that is not the entrypoint", async () => {});

/**
* This test verifies the ERC4337 based on gas estimation for a user operation
* The user operation deploys a Safe with the ERC4337 module and a handler
Expand Down

0 comments on commit 79b5f1c

Please sign in to comment.