Skip to content

Commit

Permalink
feat: safe implementation (#11)
Browse files Browse the repository at this point in the history
* feat: safe implementation

* chore: ignore test & increase coverage of bundler tests

* fix: dummy signature

* chore: disable Safe test

* chore: fix some things
  • Loading branch information
chris13524 authored Sep 6, 2024
1 parent 305ee0a commit 904c24f
Show file tree
Hide file tree
Showing 10 changed files with 607 additions and 96 deletions.
3 changes: 3 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,6 @@
[submodule "crates/yttrium/safe-modules"]
path = crates/yttrium/safe-modules
url = https://github.com/safe-global/safe-modules
[submodule "crates/yttrium/safe7579"]
path = crates/yttrium/safe7579
url = https://github.com/rhinestonewtf/safe7579
1 change: 1 addition & 0 deletions crates/yttrium/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ alloy = { git = "https://github.com/alloy-rs/alloy", rev = "b000e16", features =
"node-bindings",
"rpc-types-trace",
"signer-mnemonic",
"eip712",
] }
# foundry-block-explorers = "0.2.3"
getrandom = { version = "0.2", features = ["js"] }
Expand Down
21 changes: 0 additions & 21 deletions crates/yttrium/contracts/Account7702.sol

This file was deleted.

1 change: 1 addition & 0 deletions crates/yttrium/safe7579
Submodule safe7579 added at 144eb3
8 changes: 4 additions & 4 deletions crates/yttrium/src/bundler/models/user_operation_receipt.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ pub struct UserOperationReceiptReceipt {
pub contract_address: Option<String>,
pub status: String,
pub logs_bloom: String,
pub r#type: String,
// pub r#type: String,
pub effective_gas_price: String,
}

Expand All @@ -30,7 +30,7 @@ pub struct UserOperationReceipt {
pub actual_gas_cost: String,
pub actual_gas_used: String,
pub success: bool,
pub reason: String,
// pub reason: String,
pub receipt: UserOperationReceiptReceipt,
// TODO: add `logs` property
}
Expand All @@ -48,7 +48,7 @@ impl UserOperationReceipt {
actual_gas_cost: "0x11bed797b2d5c8".to_string(),
actual_gas_used: "0x20725".to_string(),
success: true,
reason: "".to_string(),
// reason: "".to_string(),
receipt: UserOperationReceiptReceipt {
transaction_hash: "0x68b5465c1efe05e5a29f8551c3808e5fd3b0a46e7abb007e11c586632cf46c23".to_string(),
transaction_index: "0x85".to_string(),
Expand All @@ -61,7 +61,7 @@ impl UserOperationReceipt {
contract_address: None,
status: "0x1".to_string(),
logs_bloom: "0x04400000000040002000000000000000000000000000000000000000000000000008000000000000000200010000000000100000000000000000020000000000000000000000000000000008000000000100000000000000000000000000000000000000080000000008000000000000000000000000000000000010000000000000000000040040100088000000000000000000000000000000000000000000000000000000000100400000000008000000000000000000000002000000000000000002000000100001000000000000000000002000000000000040000000000000000000000000200000000000000000000000000000000000000000000010".to_string(),
r#type: "0x2".to_string(),
// r#type: "0x2".to_string(),
effective_gas_price: "0x86cb70a28".to_string(),
},
}
Expand Down
157 changes: 101 additions & 56 deletions crates/yttrium/src/smart_accounts/safe.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use alloy::{
dyn_abi::DynSolValue,
primitives::{address, Address, Bytes, U256},
primitives::{address, keccak256, Address, Bytes, Uint, U256},
sol,
sol_types::SolCall,
};
Expand All @@ -17,27 +17,69 @@ sol!(
sol!(
#[allow(clippy::too_many_arguments)]
#[allow(missing_docs)]
#[sol(rpc)]
#[sol(rpc, abi)]
Safe,
"safe-smart-account/build/artifacts/contracts/Safe.sol/Safe.json"
);

// https://github.com/WalletConnect/secure-web3modal/blob/c19a1e7b21c6188261728f4d521a17f94da4f055/src/core/SmartAccountSdk/utils.ts#L178
// https://github.com/WalletConnect/secure-web3modal/blob/c19a1e7b21c6188261728f4d521a17f94da4f055/src/core/SmartAccountSdk/constants.ts#L24
const SEPOLIA_SAFE_ERC_7579_LAUNCHPAD_ADDRESS: Address =
sol!(
#[allow(clippy::too_many_arguments)]
#[allow(missing_docs)]
#[sol(rpc)]
Safe7579Launchpad,
"safe7579/artifacts/Safe7579Launchpad.json"
);

sol!(
#[allow(missing_docs)]
#[sol(rpc)]
Safe7579,
"safe7579/artifacts/Safe7579.json"
);

// Had to copy from safe7579/artifacts/interfaces/IERC7579Account.json
// This struct doesn't seem to be in generated ABIs
sol!(
#[allow(missing_docs)]
#[sol(rpc, abi)]
struct Execution {
address target;
uint256 value;
bytes callData;
}
);

// https://github.com/WalletConnect/secure-web3modal/blob/f1d16f973a313e598d124a0e4751aee12d5de628/src/core/SmartAccountSdk/utils.ts#L180
pub const SAFE_ERC_7579_LAUNCHPAD_ADDRESS: Address =
address!("EBe001b3D534B9B6E2500FB78E67a1A137f561CE");
const SEPOLIA_SAFE_4337_MODULE_ADDRESS: Address =
// https://github.com/WalletConnect/secure-web3modal/blob/f1d16f973a313e598d124a0e4751aee12d5de628/src/core/SmartAccountSdk/utils.ts#L181
// https://docs.pimlico.io/permissionless/how-to/accounts/use-erc7579-account
// https://docs.safe.global/advanced/erc-7579/tutorials/7579-tutorial
pub const SAFE_4337_MODULE_ADDRESS: Address =
address!("3Fdb5BC686e861480ef99A6E3FaAe03c0b9F32e2");

// // https://github.com/pimlicolabs/permissionless.js/blob/b8798c121eecba6a71f96f8ddf8e0ad2e98a3236/packages/permissionless/accounts/safe/toSafeSmartAccount.ts#L436
pub const SEPOLIA_SAFE_ERC_7579_SINGLETON_ADDRESS: Address =
address!("41675C099F32341bf84BFc5382aF534df5C7461a");

// https://github.com/safe-global/safe-modules-deployments/blob/d6642d90659de19e54bb4a20d646b30bd0a51885/src/assets/safe-4337-module/v0.3.0/safe-4337-module.json#L7
// https://github.com/pimlicolabs/permissionless.js/blob/b8798c121eecba6a71f96f8ddf8e0ad2e98a3236/packages/permissionless/accounts/safe/toSafeSmartAccount.ts#L432
// const SEPOLIA_SAFE_4337_MODULE_ADDRESS: Address =
// address!("75cf11467937ce3F2f357CE24ffc3DBF8fD5c226");

// https://github.com/pimlicolabs/permissionless.js/blob/b8798c121eecba6a71f96f8ddf8e0ad2e98a3236/packages/permissionless/accounts/safe/toSafeSmartAccount.ts#L438C36-L438C76
const SAFE_MULTI_SEND_ADDRESS: Address =
address!("38869bf66a61cF6bDB996A6aE40D5853Fd43B526");
// Only used for non-ERC-7579 accounts
// const SAFE_MULTI_SEND_ADDRESS: Address =
// address!("38869bf66a61cF6bDB996A6aE40D5853Fd43B526");

// https://github.com/safe-global/safe-modules-deployments/blob/d6642d90659de19e54bb4a20d646b30bd0a51885/src/assets/safe-4337-module/v0.3.0/safe-module-setup.json#L7
// https://github.com/pimlicolabs/permissionless.js/blob/b8798c121eecba6a71f96f8ddf8e0ad2e98a3236/packages/permissionless/accounts/safe/toSafeSmartAccount.ts#L431
const SAFE_MODULE_SETUP_ADDRESS: Address =
const _SAFE_MODULE_SETUP_ADDRESS: Address =
address!("2dd68b007B46fBe91B9A7c3EDa5A7a1063cB5b47");

pub const SAFE_PROXY_FACTORY_ADDRESS: Address =
address!("4e1DCf7AD4e460CfD30791CCC4F9c8a4f820ec67");

sol!(
#[allow(missing_docs)]
#[sol(rpc)]
Expand All @@ -52,63 +94,66 @@ sol!(
"safe-smart-account/build/artifacts/contracts/libraries/MultiSend.sol/MultiSend.json"
);

pub const DUMMY_SIGNATURE_HEX: &str = "0x000000000000000000000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff";

// https://github.com/WalletConnect/secure-web3modal/blob/c19a1e7b21c6188261728f4d521a17f94da4f055/src/core/SmartAccountSdk/constants.ts#L10
// const APPKIT_SALT: U256 = U256::from_str("zg3ijy0p46");

fn encode_internal_transaction(
to: Address,
data: Vec<u8>,
value: U256,
operation: bool,
) -> Bytes {
// https://github.com/pimlicolabs/permissionless.js/blob/b8798c121eecba6a71f96f8ddf8e0ad2e98a3236/packages/permissionless/accounts/safe/toSafeSmartAccount.ts#L486
DynSolValue::Tuple(vec![
DynSolValue::Uint(U256::from(if operation { 1 } else { 0 }), 8),
DynSolValue::Address(to),
DynSolValue::Uint(value, 256),
DynSolValue::Uint(U256::from(data.len()), 256),
DynSolValue::Bytes(data),
])
pub fn init_data() -> Safe7579Launchpad::initSafe7579Call {
Safe7579Launchpad::initSafe7579Call {
safe7579: SAFE_4337_MODULE_ADDRESS,
executors: vec![],
fallbacks: vec![],
hooks: vec![],
attesters: vec![],
threshold: 0,
}
}

#[derive(Debug, Clone)]
pub struct Owners {
pub owners: Vec<Address>,
pub threshold: u8,
}

// permissionless -> getInitializerCode
fn get_initializer_code(owners: Owners) -> Bytes {
let init_hash = keccak256(
DynSolValue::Tuple(vec![
DynSolValue::Address(SEPOLIA_SAFE_ERC_7579_SINGLETON_ADDRESS),
DynSolValue::Array(
owners
.owners
.into_iter()
.map(DynSolValue::Address)
.collect::<Vec<_>>(),
),
DynSolValue::Uint(Uint::from(owners.threshold), 256),
DynSolValue::Address(SAFE_ERC_7579_LAUNCHPAD_ADDRESS),
DynSolValue::Bytes(init_data().abi_encode()),
DynSolValue::Address(SAFE_4337_MODULE_ADDRESS),
DynSolValue::Array(vec![]),
])
.abi_encode_params(),
);

Safe7579Launchpad::preValidationSetupCall {
initHash: init_hash,
to: Address::ZERO,
preInit: Bytes::new(),
}
.abi_encode()
.into()
}

fn init_code_call_data(
owner: Address,
pub fn factory_data(
owners: Owners,
) -> SafeProxyFactory::createProxyWithNonceCall {
// https://github.com/pimlicolabs/permissionless.js/blob/b8798c121eecba6a71f96f8ddf8e0ad2e98a3236/packages/permissionless/accounts/safe/toSafeSmartAccount.ts#L714C31-L714C46
let enable_modules = SafeModuleSetup::enableModulesCall {
modules: vec![SEPOLIA_SAFE_4337_MODULE_ADDRESS],
}
.abi_encode();

// https://github.com/pimlicolabs/permissionless.js/blob/b8798c121eecba6a71f96f8ddf8e0ad2e98a3236/packages/permissionless/accounts/safe/toSafeSmartAccount.ts#L486
let txn = encode_internal_transaction(
SAFE_MODULE_SETUP_ADDRESS,
enable_modules,
U256::ZERO,
true,
); // TODO join any setupTransactions

let multi_send_call_data =
MultiSend::multiSendCall { transactions: txn }.abi_encode().into();

// https://github.com/pimlicolabs/permissionless.js/blob/b8798c121eecba6a71f96f8ddf8e0ad2e98a3236/packages/permissionless/accounts/safe/toSafeSmartAccount.ts#L728
let initializer = Safe::setupCall {
_owners: vec![owner],
_threshold: U256::from(1),
to: SAFE_MULTI_SEND_ADDRESS,
data: multi_send_call_data,
fallbackHandler: SAFE_MODULE_SETUP_ADDRESS,
paymentToken: Address::ZERO,
payment: U256::ZERO,
paymentReceiver: Address::ZERO,
}
.abi_encode()
.into();
let initializer = get_initializer_code(owners);

// https://github.com/pimlicolabs/permissionless.js/blob/b8798c121eecba6a71f96f8ddf8e0ad2e98a3236/packages/permissionless/accounts/safe/toSafeSmartAccount.ts#L840
SafeProxyFactory::createProxyWithNonceCall {
_singleton: SEPOLIA_SAFE_ERC_7579_LAUNCHPAD_ADDRESS,
_singleton: SAFE_ERC_7579_LAUNCHPAD_ADDRESS,
initializer,
saltNonce: U256::ZERO,
}
Expand Down
1 change: 1 addition & 0 deletions crates/yttrium/src/transaction/send.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ use core::fmt;
use std::sync::Arc;
use tokio::sync::Mutex;

mod safe_test;
mod simple_account_test;

#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
Expand Down
Loading

0 comments on commit 904c24f

Please sign in to comment.