-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
chore: Safe boilerplate & simple account automated tests (#7)
* feat: Safe * chore: remove old code * chore: more Safe stuff * chore: test simple account on local * chore: run infra forked * fix: docker service dependencies * fix: other contract builds * chore: install pnpm * fix: docker compose cmd * fix: build path * chore: fix health endpoints * chore: fix Swift build * chore: remove use of mnemonic * chore: move `mod.rs` to `safe.rs`, fix warning --------- Co-authored-by: Jack Pooley <[email protected]>
- Loading branch information
1 parent
efa3553
commit 9c29b40
Showing
19 changed files
with
590 additions
and
4 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,9 @@ | ||
[submodule "crates/yttrium/src/contracts"] | ||
path = crates/yttrium/src/contracts | ||
url = https://github.com/eth-infinitism/account-abstraction.git | ||
[submodule "crates/yttrium/safe-smart-account"] | ||
path = crates/yttrium/safe-smart-account | ||
url = https://github.com/safe-global/safe-smart-account | ||
[submodule "crates/yttrium/safe-modules"] | ||
path = crates/yttrium/safe-modules | ||
url = https://github.com/safe-global/safe-modules |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,107 @@ | ||
use { | ||
// serde_json::Value, | ||
std::process::{Command, Stdio}, | ||
}; | ||
|
||
fn main() { | ||
build_contracts(); | ||
} | ||
|
||
const CONTRACTS_DIR: &str = "crates/yttrium/safe-smart-account/contracts"; | ||
|
||
fn build_contracts() { | ||
println!("cargo::rerun-if-changed={CONTRACTS_DIR}"); | ||
install_foundry(); | ||
compile_contracts(&format!("{CONTRACTS_DIR}/proxies")); | ||
// extract_bytecodes(); | ||
} | ||
|
||
fn format_foundry_dir(path: &str) -> String { | ||
format!( | ||
"{}/../../../../.foundry/{}", | ||
std::env::var("OUT_DIR").unwrap(), | ||
path | ||
) | ||
} | ||
|
||
fn install_foundry() { | ||
let bin_finished_flag = format_foundry_dir("bin/.finished"); | ||
if std::fs::metadata(&bin_finished_flag).is_ok() { | ||
return; | ||
} | ||
|
||
let bin_folder = format_foundry_dir("bin"); | ||
std::fs::remove_dir_all(&bin_folder).ok(); | ||
std::fs::create_dir_all(&bin_folder).unwrap(); | ||
let output = Command::new("bash") | ||
.args(["-c", &format!("curl https://raw.githubusercontent.com/foundry-rs/foundry/e0ea59cae26d945445d9cf21fdf22f4a18ac5bb2/foundryup/foundryup | FOUNDRY_DIR={} bash", format_foundry_dir(""))]) | ||
.stdout(Stdio::piped()) | ||
.stderr(Stdio::piped()) | ||
.spawn() | ||
.unwrap() | ||
.wait_with_output() | ||
.unwrap(); | ||
println!("foundryup status: {:?}", output.status); | ||
let stdout = String::from_utf8(output.stdout).unwrap(); | ||
println!("foundryup stdout: {stdout:?}"); | ||
let stderr = String::from_utf8(output.stderr).unwrap(); | ||
println!("foundryup stderr: {stderr:?}"); | ||
assert!(output.status.success()); | ||
|
||
std::fs::write(bin_finished_flag, "").unwrap(); | ||
} | ||
|
||
fn compile_contracts(contracts_dir: &str) { | ||
let output = Command::new(format_foundry_dir("bin/forge")) | ||
.args([ | ||
"build", | ||
&format!("--contracts={contracts_dir}"), | ||
"--skip=test", | ||
"--cache-path", | ||
&format_foundry_dir("forge/cache"), | ||
"--out", | ||
&format_foundry_dir("forge/out"), | ||
]) | ||
.stdout(Stdio::piped()) | ||
.stderr(Stdio::piped()) | ||
.spawn() | ||
.unwrap() | ||
.wait_with_output() | ||
.unwrap(); | ||
println!("forge status: {:?}", output.status); | ||
let stdout = String::from_utf8(output.stdout).unwrap(); | ||
println!("forge stdout: {stdout:?}"); | ||
let stderr = String::from_utf8(output.stderr).unwrap(); | ||
println!("forge stderr: {stderr:?}"); | ||
assert!(output.status.success()); | ||
} | ||
|
||
// const ERC6492_FILE: &str = "forge/out/Erc6492.sol/ValidateSigOffchain.json"; | ||
// const ERC6492_BYTECODE_FILE: &str = "forge/out/Erc6492.sol/ValidateSigOffchain.bytecode"; | ||
// const ERC1271_MOCK_FILE: &str = "forge/out/Erc1271Mock.sol/Erc1271Mock.json"; | ||
// const ERC1271_MOCK_BYTECODE_FILE: &str = "forge/out/Erc1271Mock.sol/Erc1271Mock.bytecode"; | ||
// fn extract_bytecodes() { | ||
// extract_bytecode( | ||
// &format_foundry_dir(ERC6492_FILE), | ||
// &format_foundry_dir(ERC6492_BYTECODE_FILE), | ||
// ); | ||
// extract_bytecode( | ||
// &format_foundry_dir(ERC1271_MOCK_FILE), | ||
// &format_foundry_dir(ERC1271_MOCK_BYTECODE_FILE), | ||
// ); | ||
// } | ||
|
||
// fn extract_bytecode(input_file: &str, output_file: &str) { | ||
// let contents = serde_json::from_slice::<Value>(&std::fs::read(input_file).unwrap()).unwrap(); | ||
// let bytecode = contents | ||
// .get("bytecode") | ||
// .unwrap() | ||
// .get("object") | ||
// .unwrap() | ||
// .as_str() | ||
// .unwrap() | ||
// .strip_prefix("0x") | ||
// .unwrap(); | ||
// let bytecode = alloy_primitives::hex::decode(bytecode).unwrap(); | ||
// std::fs::write(output_file, bytecode).unwrap(); | ||
// } |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
pragma solidity ^0.8.20; | ||
|
||
contract Account7702 { | ||
constructor() {} // TODO need owner | ||
|
||
struct Call { | ||
bytes data; | ||
address to; | ||
uint256 value; | ||
bytes signature; // TODO proper type | ||
} | ||
|
||
function execute(Call[] calldata calls) external payable { | ||
// TODO how to authenticate signture | ||
for (uint256 i = 0; i < calls.length; i++) { | ||
Call memory call = calls[i]; | ||
(bool success, ) = call.to.call{value: call.value}(call.data); | ||
require(success, "call reverted"); | ||
} | ||
} | ||
} |
Submodule safe-modules
added at
bc76ff
Submodule safe-smart-account
added at
c266ff
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,2 +1,3 @@ | ||
pub mod nonce; | ||
pub mod safe; | ||
pub mod simple_account; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,115 @@ | ||
use alloy::{ | ||
dyn_abi::DynSolValue, | ||
primitives::{address, Address, Bytes, U256}, | ||
sol, | ||
sol_types::SolCall, | ||
}; | ||
|
||
sol!( | ||
#[allow(missing_docs)] | ||
#[sol(rpc)] | ||
SafeProxyFactory, | ||
"safe-smart-account/build/artifacts/contracts/proxies/SafeProxyFactory.sol/SafeProxyFactory.json" | ||
// "../../target/.foundry/forge/out/SafeProxyFactory.sol/SafeProxyFactory.json" | ||
// concat!(env!("OUT_DIR"), "/../../../../.foundry/forge/out/SafeProxyFactory.sol/SafeProxyFactory.json") | ||
); | ||
|
||
sol!( | ||
#[allow(clippy::too_many_arguments)] | ||
#[allow(missing_docs)] | ||
#[sol(rpc)] | ||
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 = | ||
address!("EBe001b3D534B9B6E2500FB78E67a1A137f561CE"); | ||
const SEPOLIA_SAFE_4337_MODULE_ADDRESS: Address = | ||
address!("3Fdb5BC686e861480ef99A6E3FaAe03c0b9F32e2"); | ||
|
||
// https://github.com/pimlicolabs/permissionless.js/blob/b8798c121eecba6a71f96f8ddf8e0ad2e98a3236/packages/permissionless/accounts/safe/toSafeSmartAccount.ts#L438C36-L438C76 | ||
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 = | ||
address!("2dd68b007B46fBe91B9A7c3EDa5A7a1063cB5b47"); | ||
|
||
sol!( | ||
#[allow(missing_docs)] | ||
#[sol(rpc)] | ||
SafeModuleSetup, | ||
"safe-modules/modules/4337/build/artifacts/contracts/SafeModuleSetup.sol/SafeModuleSetup.json" | ||
); | ||
|
||
sol!( | ||
#[allow(missing_docs)] | ||
#[sol(rpc)] | ||
MultiSend, | ||
"safe-smart-account/build/artifacts/contracts/libraries/MultiSend.sol/MultiSend.json" | ||
); | ||
|
||
// 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), | ||
]) | ||
.abi_encode() | ||
.into() | ||
} | ||
|
||
fn init_code_call_data( | ||
owner: Address, | ||
) -> 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(); | ||
// https://github.com/pimlicolabs/permissionless.js/blob/b8798c121eecba6a71f96f8ddf8e0ad2e98a3236/packages/permissionless/accounts/safe/toSafeSmartAccount.ts#L840 | ||
SafeProxyFactory::createProxyWithNonceCall { | ||
_singleton: SEPOLIA_SAFE_ERC_7579_LAUNCHPAD_ADDRESS, | ||
initializer, | ||
saltNonce: U256::ZERO, | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
fn format_foundry_dir(path: &str) -> String { | ||
format!( | ||
"{}/../../../../.foundry/{}", | ||
std::env::var("OUT_DIR").unwrap(), | ||
path | ||
) | ||
} | ||
|
||
pub fn spawn_anvil() -> (AnvilInstance, String, ReqwestProvider, SigningKey) { | ||
let anvil = Anvil::at(format_foundry_dir("bin/anvil")).spawn(); | ||
let rpc_url = anvil.endpoint(); | ||
let provider = ReqwestProvider::<Ethereum>::new_http(anvil.endpoint_url()); | ||
let private_key = anvil.keys().first().unwrap().clone(); | ||
( | ||
anvil, | ||
rpc_url, | ||
provider, | ||
SigningKey::from_bytes(&private_key.to_bytes()).unwrap(), | ||
) | ||
} |
Oops, something went wrong.