-
Notifications
You must be signed in to change notification settings - Fork 3
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
fix: poc for bricked account #13
Changes from 3 commits
ce43c9a
f405295
eba0e8f
eca6f26
3469874
874d8c3
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -3,7 +3,7 @@ | |
|
||
import { ISafe7579 } from "../ISafe7579.sol"; | ||
import { ISafe } from "../interfaces/ISafe.sol"; | ||
import "../DataTypes.sol"; | ||
import { ModuleInstallUtil } from "../utils/DCUtil.sol"; | ||
import { ModuleManager } from "./ModuleManager.sol"; | ||
|
||
|
@@ -15,7 +15,7 @@ | |
} from "erc7579/interfaces/IERC7579Module.sol"; | ||
import { IERC7484 } from "../interfaces/IERC7484.sol"; | ||
import { SentinelList4337Lib } from "sentinellist/SentinelList4337.sol"; | ||
import { SentinelListLib } from "sentinellist/SentinelList.sol"; | ||
|
||
/** | ||
* Functions that can be used to initialze Safe7579 for a Safe Account | ||
|
@@ -36,17 +36,19 @@ | |
override | ||
onlyEntryPointOrSelf | ||
{ | ||
// this will revert if already initialized | ||
$validators.init({ account: msg.sender }); | ||
uint256 length = validators.length; | ||
for (uint256 i; i < length; i++) { | ||
ModuleInit calldata validator = validators[i]; | ||
$validators.push({ account: msg.sender, newEntry: validator.module }); | ||
// @dev No events emitted here. Launchpad is expected to do this. | ||
// at this point, the safeproxy singleton is not yet updated to the SafeSingleton | ||
// calling execTransactionFromModule is not available yet. | ||
if (!$validators.alreadyInitialized({ account: msg.sender })) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. think this is a quite elegant solution tbh. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. TODO: we should write some docs for this if we go with this solution |
||
// this will revert if already initialized | ||
$validators.init({ account: msg.sender }); | ||
uint256 length = validators.length; | ||
for (uint256 i; i < length; i++) { | ||
ModuleInit calldata validator = validators[i]; | ||
$validators.push({ account: msg.sender, newEntry: validator.module }); | ||
// @dev No events emitted here. Launchpad is expected to do this. | ||
// at this point, the safeproxy singleton is not yet updated to the SafeSingleton | ||
// calling execTransactionFromModule is not available yet. | ||
} | ||
emit Safe7579Initialized(msg.sender); | ||
} | ||
emit Safe7579Initialized(msg.sender); | ||
} | ||
|
||
/** | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,252 @@ | ||
// SPDX-License-Identifier: MIT | ||
pragma solidity ^0.8.23; | ||
|
||
import "forge-std/Test.sol"; | ||
import { Safe7579 } from "src/Safe7579.sol"; | ||
import { ISafe7579 } from "src/ISafe7579.sol"; | ||
import { IERC7484 } from "src/interfaces/IERC7484.sol"; | ||
import "src/DataTypes.sol"; | ||
import { ModuleManager } from "src/core/ModuleManager.sol"; | ||
import { MockValidator } from "module-bases/mocks/MockValidator.sol"; | ||
import { MockRegistry } from "./mocks/MockRegistry.sol"; | ||
import { MockExecutor } from "./mocks/MockExecutor.sol"; | ||
import { MockFallback } from "./mocks/MockFallback.sol"; | ||
import { ExecutionLib } from "erc7579/lib/ExecutionLib.sol"; | ||
import { ModeLib } from "erc7579/lib/ModeLib.sol"; | ||
import { IERC7579Account, Execution } from "erc7579/interfaces/IERC7579Account.sol"; | ||
import { MockTarget } from "./mocks/MockTarget.sol"; | ||
|
||
import { Safe } from "@safe-global/safe-contracts/contracts/Safe.sol"; | ||
import { | ||
SafeProxy, | ||
SafeProxyFactory | ||
} from "@safe-global/safe-contracts/contracts/proxies/SafeProxyFactory.sol"; | ||
import { LibClone } from "solady/utils/LibClone.sol"; | ||
import { Safe7579Launchpad } from "src/Safe7579Launchpad.sol"; | ||
|
||
import { Solarray } from "solarray/Solarray.sol"; | ||
import "./dependencies/EntryPoint.sol"; | ||
|
||
import { Simulator } from "@rhinestone/erc4337-validation/src/Simulator.sol"; | ||
|
||
contract RevertTarget { | ||
function set(uint256 value) public { | ||
revert("RevertTarget: revert"); | ||
} | ||
} | ||
|
||
contract BrickedTest is Test { | ||
using Simulator for PackedUserOperation; // or UserOperation | ||
|
||
Safe7579 safe7579; | ||
Safe singleton; | ||
Safe safe; | ||
SafeProxyFactory safeProxyFactory; | ||
Safe7579Launchpad launchpad; | ||
|
||
MockValidator defaultValidator; | ||
MockExecutor defaultExecutor; | ||
RevertTarget target; | ||
|
||
Account signer1 = makeAccount("signer1"); | ||
Account signer2 = makeAccount("signer2"); | ||
|
||
IEntryPoint entrypoint; | ||
bytes userOpInitCode; | ||
IERC7484 registry; | ||
|
||
struct Setup { | ||
address singleton; | ||
address signerFactory; | ||
bytes signerData; | ||
address setupTo; | ||
bytes setupData; | ||
address fallbackHandler; | ||
} | ||
|
||
function setUp() public virtual { | ||
// Set up EntryPoint | ||
entrypoint = etchEntrypoint(); | ||
singleton = new Safe(); | ||
safeProxyFactory = new SafeProxyFactory(); | ||
registry = new MockRegistry(); | ||
safe7579 = new Safe7579(); | ||
launchpad = new Safe7579Launchpad(address(entrypoint), registry); | ||
|
||
// Set up Modules | ||
defaultValidator = new MockValidator(); | ||
defaultExecutor = new MockExecutor(); | ||
target = new RevertTarget(); | ||
|
||
bytes32 salt; | ||
|
||
ModuleInit[] memory validators = new ModuleInit[](1); | ||
validators[0] = ModuleInit({ module: address(defaultValidator), initData: bytes("") }); | ||
ModuleInit[] memory executors = new ModuleInit[](1); | ||
executors[0] = ModuleInit({ module: address(defaultExecutor), initData: bytes("") }); | ||
ModuleInit[] memory fallbacks = new ModuleInit[](0); | ||
ModuleInit[] memory hooks = new ModuleInit[](0); | ||
|
||
Safe7579Launchpad.InitData memory initData = Safe7579Launchpad.InitData({ | ||
singleton: address(singleton), | ||
owners: Solarray.addresses(signer1.addr), | ||
threshold: 1, | ||
setupTo: address(launchpad), | ||
setupData: abi.encodeCall( | ||
Safe7579Launchpad.initSafe7579, | ||
( | ||
address(safe7579), | ||
executors, | ||
fallbacks, | ||
hooks, | ||
Solarray.addresses(makeAddr("attester1"), makeAddr("attester2")), | ||
2 | ||
) | ||
), | ||
safe7579: ISafe7579(safe7579), | ||
validators: validators, | ||
callData: abi.encodeCall( | ||
IERC7579Account.execute, | ||
( | ||
ModeLib.encodeSimpleSingle(), | ||
ExecutionLib.encodeSingle({ | ||
target: address(target), | ||
value: 0, | ||
callData: abi.encodeCall(RevertTarget.set, (1337)) | ||
}) | ||
) | ||
) | ||
}); | ||
bytes32 initHash = launchpad.hash(initData); | ||
|
||
bytes memory factoryInitializer = | ||
abi.encodeCall(Safe7579Launchpad.preValidationSetup, (initHash, address(0), "")); | ||
|
||
PackedUserOperation memory userOp = | ||
getDefaultUserOp(address(safe), address(defaultValidator)); | ||
|
||
{ | ||
userOp.callData = abi.encodeCall(Safe7579Launchpad.setupSafe, (initData)); | ||
userOp.initCode = _initCode(factoryInitializer, salt); | ||
} | ||
|
||
address predict = launchpad.predictSafeAddress({ | ||
singleton: address(launchpad), | ||
safeProxyFactory: address(safeProxyFactory), | ||
creationCode: type(SafeProxy).creationCode, | ||
salt: salt, | ||
factoryInitializer: factoryInitializer | ||
}); | ||
userOp.sender = predict; | ||
assertEq(userOp.sender, predict); | ||
userOp.signature = abi.encodePacked( | ||
uint48(0), uint48(type(uint48).max), hex"4141414141414141414141414141414141" | ||
); | ||
|
||
bytes32 userOpHash = entrypoint.getUserOpHash(userOp); | ||
PackedUserOperation[] memory userOps = new PackedUserOperation[](1); | ||
userOps[0] = userOp; | ||
deal(address(userOp.sender), 1 ether); | ||
|
||
userOp.simulateUserOp(address(entrypoint)); | ||
entrypoint.handleOps(userOps, payable(address(0x69))); | ||
|
||
safe = Safe(payable(predict)); | ||
} | ||
|
||
function _initCode( | ||
bytes memory initializer, | ||
bytes32 salt | ||
) | ||
internal | ||
view | ||
returns (bytes memory _initCode) | ||
{ | ||
_initCode = abi.encodePacked( | ||
address(safeProxyFactory), | ||
abi.encodeCall( | ||
SafeProxyFactory.createProxyWithNonce, | ||
(address(launchpad), initializer, uint256(salt)) | ||
) | ||
); | ||
} | ||
|
||
function test_foo() public { | ||
PackedUserOperation memory userOp = | ||
getDefaultUserOp(address(safe), address(defaultValidator)); | ||
|
||
ModuleInit[] memory validators = new ModuleInit[](1); | ||
validators[0] = ModuleInit({ module: address(defaultValidator), initData: bytes("") }); | ||
ModuleInit[] memory executors = new ModuleInit[](1); | ||
executors[0] = ModuleInit({ module: address(defaultExecutor), initData: bytes("") }); | ||
ModuleInit[] memory fallbacks = new ModuleInit[](0); | ||
ModuleInit[] memory hooks = new ModuleInit[](0); | ||
|
||
Safe7579Launchpad.InitData memory initData = Safe7579Launchpad.InitData({ | ||
singleton: address(singleton), | ||
owners: Solarray.addresses(signer1.addr), | ||
threshold: 1, | ||
setupTo: address(launchpad), | ||
setupData: abi.encodeCall( | ||
Safe7579Launchpad.initSafe7579, | ||
( | ||
address(safe7579), | ||
executors, | ||
fallbacks, | ||
hooks, | ||
Solarray.addresses(makeAddr("attester1"), makeAddr("attester2")), | ||
2 | ||
) | ||
), | ||
safe7579: ISafe7579(safe7579), | ||
validators: validators, | ||
callData: abi.encodeCall( | ||
IERC7579Account.execute, | ||
( | ||
ModeLib.encodeSimpleSingle(), | ||
ExecutionLib.encodeSingle({ | ||
target: address(target), | ||
value: 0, | ||
callData: abi.encodeCall(RevertTarget.set, (1337)) | ||
}) | ||
) | ||
) | ||
}); | ||
|
||
userOp.callData = abi.encodeCall(Safe7579Launchpad.setupSafe, (initData)); | ||
|
||
userOp.signature = abi.encodePacked( | ||
uint48(0), uint48(type(uint48).max), hex"4141414141414141414141414141414141" | ||
); | ||
|
||
bytes32 userOpHash = entrypoint.getUserOpHash(userOp); | ||
PackedUserOperation[] memory userOps = new PackedUserOperation[](1); | ||
userOps[0] = userOp; | ||
|
||
userOp.simulateUserOp(address(entrypoint)); | ||
entrypoint.handleOps(userOps, payable(address(0x69))); | ||
|
||
assertTrue(true); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. we should add a testcase here that verifies that the safe is operational |
||
} | ||
|
||
function getDefaultUserOp( | ||
address account, | ||
address validator | ||
) | ||
internal | ||
view | ||
returns (PackedUserOperation memory userOp) | ||
{ | ||
userOp = PackedUserOperation({ | ||
sender: account, | ||
nonce: safe7579.getNonce(account, validator), | ||
initCode: "", | ||
callData: "", | ||
accountGasLimits: bytes32(abi.encodePacked(uint128(2e6), uint128(2e6))), | ||
preVerificationGas: 2e6, | ||
gasFees: bytes32(abi.encodePacked(uint128(2e6), uint128(2e6))), | ||
paymasterAndData: bytes(""), | ||
signature: abi.encodePacked(hex"41414141") | ||
}); | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nice. this looks good