Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Account/safe7579 #84

Merged
merged 67 commits into from
May 21, 2024
Merged
Show file tree
Hide file tree
Changes from 14 commits
Commits
Show all changes
67 commits
Select commit Hold shift + click to select a range
1526387
🧑‍💻 Adding safe query on 7579 acocuntId()
zeroknots Mar 4, 2024
b14fc84
proxy creation
zeroknots Mar 8, 2024
8987bde
userOp working. predicting address for userop.sender not working yet
zeroknots Mar 8, 2024
ccaa93c
feat: predicting address for safe factory now works
zeroknots Mar 8, 2024
3e5f10c
refine setup
zeroknots Mar 8, 2024
0e534ff
🔥 Tests passing again
zeroknots Mar 8, 2024
36bd03e
adding ERC1271
zeroknots Mar 8, 2024
f1ad273
✨ Safe7579 now supports msg.sig specific fallbacks
zeroknots Mar 8, 2024
0774d2b
✨ Safe7579 now supports complex fallbacks
zeroknots Mar 14, 2024
5c10bc7
🔥 Changed static/call in safe7579 to use direct call to handler
zeroknots Mar 14, 2024
c0271d7
✨ Installation and deinstallation now works with calldata
zeroknots Mar 14, 2024
d70c3a5
rm: comment
zeroknots Mar 14, 2024
c9997c7
feat: adding default case for safe signature
zeroknots Mar 14, 2024
8510513
fixed fallbacks
zeroknots Mar 14, 2024
6c96a29
rm: remove not implemented calltype
zeroknots Mar 15, 2024
00261ac
✨ Add ERC1271 fallback for safe native signatures
zeroknots Mar 16, 2024
d37a77a
Merge pull request #87 from rhinestonewtf/account/safe7579-fix1271
zeroknots Mar 18, 2024
b10a3f1
chore: fix validator call
zeroknots Apr 4, 2024
691f926
add hook calls
zeroknots Apr 4, 2024
b2187bd
feat: implement staticcalls via safes simulate feature
zeroknots Apr 4, 2024
28d66b8
feat: blacklisting onInstall and onUninstall msg.sigs for fallbacks. …
zeroknots Apr 4, 2024
7cce2a0
chore: cleaning up
zeroknots Apr 4, 2024
2a99dd2
🔥 Changing initAccount to only use validators for ERC4337 compliance
zeroknots Apr 4, 2024
7432b7a
feat: implement come's feedback
kopy-kat Apr 4, 2024
b122877
fix: access control on validateUserOp
kopy-kat Apr 4, 2024
579ea0b
fix: pass correct deinitdata to fallback
kopy-kat Apr 4, 2024
ec3760e
fix: use msg.sender instead of userOp.sender
kopy-kat Apr 4, 2024
08009a2
⚡️ Adding ERC7484 registry
zeroknots Apr 8, 2024
b0ac136
refactoring executions
zeroknots Apr 8, 2024
6fcb644
chore: fixing deps and tests
zeroknots Apr 8, 2024
6e620ce
clean up
zeroknots Apr 8, 2024
56fb4b1
note
zeroknots Apr 9, 2024
c0d450d
implemented executeUserOp
zeroknots Apr 9, 2024
53dc4bd
🐛 Setup working
zeroknots Apr 10, 2024
cf083a1
chore: updating docs
zeroknots Apr 10, 2024
d41b0fe
chore: refactoring initialization
zeroknots Apr 10, 2024
9e940da
refactor event emitions and initialization
zeroknots Apr 10, 2024
993d40f
clean up
zeroknots Apr 10, 2024
cdb2533
feat: implement execution after initialization
zeroknots Apr 10, 2024
ab258b1
clean up
zeroknots Apr 10, 2024
00c411e
bug: excluding initData.calldata from inithash
zeroknots Apr 10, 2024
049cd40
📝 Adding inline docs
zeroknots Apr 11, 2024
acf362d
fix bug in 1271
zeroknots Apr 11, 2024
97ef933
working
zeroknots Apr 16, 2024
c95efad
tests
zeroknots Apr 17, 2024
82ed685
refactor to use DCUtil
zeroknots Apr 17, 2024
6877538
refactor hooks
zeroknots Apr 18, 2024
95cdd99
modulemanager refactor
zeroknots Apr 18, 2024
8841547
refactoring to avoid stack too deep
zeroknots Apr 18, 2024
981bcec
refactoring for erc1271
zeroknots Apr 19, 2024
e642164
linting
zeroknots Apr 19, 2024
4c9b2bb
feat: adding global and sig specific hooks
zeroknots Apr 19, 2024
b8033a7
feat: adding multitype install
zeroknots Apr 19, 2024
1563fd4
adding docs
zeroknots Apr 19, 2024
ceba7b8
feat: wrote comprehensive hook tests
zeroknots Apr 22, 2024
7d33ac8
chore: linting
zeroknots Apr 22, 2024
bc1c2fd
multi install
zeroknots Apr 22, 2024
3ede7af
docs
zeroknots Apr 22, 2024
350eef2
cleaning
zeroknots Apr 22, 2024
901a0ae
add docs
zeroknots Apr 22, 2024
9a8aa73
Merge branch 'account/safe7579' into account/safe7579-fix-init
zeroknots Apr 22, 2024
0a2f09b
refactor
zeroknots Apr 23, 2024
7066672
Merge pull request #90 from rhinestonewtf/account/safe7579-fix-init
zeroknots Apr 23, 2024
471e96b
cleaning up
zeroknots Apr 23, 2024
e4b91f8
feat: add userOp constructor
kopy-kat May 1, 2024
7ac113c
feat: update userop builder to latest interface
kopy-kat May 1, 2024
5676f3f
chore: add deployments
kopy-kat May 9, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
66 changes: 0 additions & 66 deletions accounts/safe7579/README.md

This file was deleted.

Empty file added accounts/safe7579/notes.txt
Empty file.
1 change: 1 addition & 0 deletions accounts/safe7579/script/Deploy.s.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@

131 changes: 112 additions & 19 deletions accounts/safe7579/src/SafeERC7579.sol
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,12 @@ pragma solidity ^0.8.23;
import { IERC7579Account, Execution } from "erc7579/interfaces/IERC7579Account.sol";
import { IMSA } from "erc7579/interfaces/IMSA.sol";
import {
CallType, ModeCode, ModeLib, CALLTYPE_SINGLE, CALLTYPE_BATCH
CallType,
ModeCode,
ModeLib,
CALLTYPE_SINGLE,
CALLTYPE_BATCH,
CALLTYPE_DELEGATECALL
} from "erc7579/lib/ModeLib.sol";
import { ExecutionLib } from "erc7579/lib/ExecutionLib.sol";
import {
Expand All @@ -23,20 +28,31 @@ import {
UserOperationLib
} from "@ERC4337/account-abstraction/contracts/core/UserOperationLib.sol";
import { _packValidationData } from "@ERC4337/account-abstraction/contracts/core/Helpers.sol";
import { IEntryPoint } from "@ERC4337/account-abstraction/contracts/interfaces/IEntryPoint.sol";
import { ISafe7579Init } from "./interfaces/ISafe7579Init.sol";

/**
* @title ERC7579 Adapter for Safe accounts.
* By using Safe's Fallback and Execution modules,
* this contract creates full ERC7579 compliance to Safe accounts
* @author zeroknots.eth | rhinestone.wtf
*/
contract SafeERC7579 is ISafeOp, IERC7579Account, AccessControl, IMSA, HookManager {
contract SafeERC7579 is
ISafeOp,
IERC7579Account,
ISafe7579Init,
AccessControl,
IMSA,
HookManager
{
using UserOperationLib for PackedUserOperation;
using ModeLib for ModeCode;
using ExecutionLib for bytes;

error Unsupported();

event Safe7579Initialized(address indexed safe);

bytes32 private constant DOMAIN_SEPARATOR_TYPEHASH =
0x47e79534a245952e8b16893a336b85a3d9ea9fa8c573f3d803afb92a79469218;

Expand All @@ -62,6 +78,10 @@ contract SafeERC7579 is ISafeOp, IERC7579Account, AccessControl, IMSA, HookManag
(address target, uint256 value, bytes calldata callData) =
executionCalldata.decodeSingle();
_execute(msg.sender, target, value, callData);
} else if (callType == CALLTYPE_DELEGATECALL) {
address target = address(bytes20(executionCalldata[:20]));
bytes calldata callData = executionCalldata[20:];
_executeDelegateCall(msg.sender, target, callData);
} else {
revert UnsupportedCallType(callType);
}
Expand Down Expand Up @@ -91,6 +111,11 @@ contract SafeERC7579 is ISafeOp, IERC7579Account, AccessControl, IMSA, HookManag
executionCalldata.decodeSingle();
returnData = new bytes[](1);
returnData[0] = _executeReturnData(msg.sender, target, value, callData);
} else if (callType == CALLTYPE_DELEGATECALL) {
address target = address(bytes20(executionCalldata[:20]));
bytes calldata callData = executionCalldata[20:];
returnData = new bytes[](1);
returnData[0] = _executeDelegateCallReturnData(msg.sender, target, callData);
} else {
revert UnsupportedCallType(callType);
}
Expand Down Expand Up @@ -124,16 +149,19 @@ contract SafeERC7579 is ISafeOp, IERC7579Account, AccessControl, IMSA, HookManag
{
address validator;
uint256 nonce = userOp.nonce;

// solhint-disable-next-line no-inline-assembly
assembly {
validator := shr(96, nonce)
}

// check if validator is enabled. If not, use Safe's checkSignatures()
if (!_isValidatorInstalled(validator)) return _validateSignatures(userOp);

// bubble up the return value of the validator module
validSignature = IValidator(validator).validateUserOp(userOp, userOpHash);
if (validator == address(0) || !_isValidatorInstalled(validator)) {
return _validateSignatures(userOp);
} else {
// bubble up the return value of the validator module
validSignature = IValidator(validator).validateUserOp(userOp, userOpHash);
}

// pay prefund
if (missingAccountFunds != 0) {
Expand Down Expand Up @@ -175,7 +203,20 @@ contract SafeERC7579 is ISafeOp, IERC7579Account, AccessControl, IMSA, HookManag
/**
* @inheritdoc IERC7579Account
*/
function isValidSignature(bytes32 hash, bytes calldata data) external view returns (bytes4) { }
function isValidSignature(
bytes32 hash,
bytes calldata data
)
external
view
returns (bytes4 magicValue)
{
address validationModule = address(bytes20(data[:20]));

if (!_isValidatorInstalled(validationModule)) return 0xFFFFFFFF;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should we also use safe if validationModule is address(0)

magicValue =
IValidator(validationModule).isValidSignatureWithSender(msg.sender, hash, data[20:]);
}

/**
* @inheritdoc IERC7579Account
Expand Down Expand Up @@ -226,6 +267,7 @@ contract SafeERC7579 is ISafeOp, IERC7579Account, AccessControl, IMSA, HookManag
CallType callType = encodedMode.getCallType();
if (callType == CALLTYPE_BATCH) return true;
else if (callType == CALLTYPE_SINGLE) return true;
else if (callType == CALLTYPE_DELEGATECALL) return true;
else return false;
}

Expand All @@ -246,25 +288,32 @@ contract SafeERC7579 is ISafeOp, IERC7579Account, AccessControl, IMSA, HookManag
function isModuleInstalled(
uint256 moduleType,
address module,
bytes calldata /*additionalContext*/
bytes calldata additionalContext
)
external
view
override
returns (bool)
{
if (moduleType == MODULE_TYPE_VALIDATOR) return _isValidatorInstalled(module);
else if (moduleType == MODULE_TYPE_EXECUTOR) return _isExecutorInstalled(module);
else if (moduleType == MODULE_TYPE_FALLBACK) return _isFallbackHandlerInstalled(module);
else if (moduleType == MODULE_TYPE_HOOK) return _isHookInstalled(module);
else return false;
if (moduleType == MODULE_TYPE_VALIDATOR) {
return _isValidatorInstalled(module);
} else if (moduleType == MODULE_TYPE_EXECUTOR) {
return _isExecutorInstalled(module);
} else if (moduleType == MODULE_TYPE_FALLBACK) {
return _isFallbackHandlerInstalled(module, additionalContext);
} else if (moduleType == MODULE_TYPE_HOOK) {
return _isHookInstalled(module);
} else {
return false;
}
}

/**
* @inheritdoc IERC7579Account
*/
function accountId() external pure override returns (string memory accountImplementationId) {
return "safe-erc7579.v0.0.0";
function accountId() external view override returns (string memory accountImplementationId) {
string memory safeVersion = ISafe(_msgSender()).VERSION();
return string(abi.encodePacked(safeVersion, "erc7579.v0.0.0"));
}

/**
Expand Down Expand Up @@ -352,12 +401,56 @@ contract SafeERC7579 is ISafeOp, IERC7579Account, AccessControl, IMSA, HookManag
return keccak256(abi.encode(DOMAIN_SEPARATOR_TYPEHASH, block.chainid, this));
}

function initializeAccount(bytes calldata data) external payable {
function initializeAccount(bytes calldata callData) external payable override {
// TODO: destructuring callData
}

function initializeAccount(
ModuleInit[] calldata validators,
ModuleInit[] calldata executors,
ModuleInit[] calldata fallbacks,
ModuleInit[] calldata hooks
)
public
payable
override
{
_initModuleManager();

(address bootstrap, bytes memory bootstrapCall) = abi.decode(data, (address, bytes));
uint256 length = validators.length;
for (uint256 i; i < length; i++) {
ModuleInit calldata validator = validators[i];
_installValidator(validator.module, validator.initData);
}

length = executors.length;
for (uint256 i; i < length; i++) {
ModuleInit calldata executor = executors[i];
_installExecutor(executor.module, executor.initData);
}

length = fallbacks.length;
for (uint256 i; i < length; i++) {
ModuleInit calldata fallBack = fallbacks[i];
_installFallbackHandler(fallBack.module, fallBack.initData);
}

length = hooks.length;
for (uint256 i; i < length; i++) {
ModuleInit calldata hook = hooks[i];
_installFallbackHandler(hook.module, hook.initData);
}

emit Safe7579Initialized(msg.sender);
}

(bool success,) = bootstrap.delegatecall(bootstrapCall);
if (!success) revert AccountInitializationFailed();
/**
* Safe7579 is using validator selection encoding in the userop nonce.
* to make it easier for SDKs / devs to integrate, this function can be
* called to get the next nonce for a specific validator
*/
function getNonce(address safe, address validator) external view returns (uint256 nonce) {
uint192 key = uint192(bytes24(bytes20(address(validator))));
nonce = IEntryPoint(entryPoint()).getNonce(safe, key);
}
}
45 changes: 45 additions & 0 deletions accounts/safe7579/src/core/ExecutionHelper.sol
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,25 @@ abstract contract ExecutionHelper {
}
}

function _executeDelegateCall(address safe, address target, bytes calldata callData) internal {
bool success = ISafe(safe).execTransactionFromModule(target, 0, callData, 1);
if (!success) revert ExecutionFailed();
}

function _executeDelegateCallReturnData(
address safe,
address target,
bytes calldata callData
)
internal
returns (bytes memory returnData)
{
bool success;
(success, returnData) =
ISafe(safe).execTransactionFromModuleReturnData(target, 0, callData, 1);
if (!success) revert ExecutionFailed();
}

/**
* Execute call on Safe
* @dev This function will revert if the call fails
Expand All @@ -93,4 +112,30 @@ abstract contract ExecutionHelper {
_executeReturnData(safe, execution.target, execution.value, execution.callData);
}
}

/**
* Execute staticcall on Safe, get return value from call
* @dev This function will revert if the call fails
* @param safe address of the safe
* @param target address of the contract to call
* @param value value of the transaction
* @param callData data of the transaction
* @return returnData data returned from the call
*/
function _executeStaticReturnData(
address safe,
address target,
uint256 value,
bytes memory callData
)
internal
view
returns (bytes memory returnData)
{
bool success;
(success, returnData) = safe.staticcall(
abi.encodeCall(ISafe.execTransactionFromModuleReturnData, (target, value, callData, 0))
);
if (!success) revert ExecutionFailed();
}
}
Loading
Loading