Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,4 @@ venv*/
target/
.spr.yml
.snfoundry_cache/
.gstack/
32 changes: 18 additions & 14 deletions Scarb.lock
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,13 @@ dependencies = [
"starkware_utils_testing",
]

[[package]]
name = "counter"
version = "0.1.0"
dependencies = [
"snforge_std",
]

[[package]]
name = "earn_reporter"
version = "0.1.0"
Expand All @@ -43,6 +50,17 @@ dependencies = [
"testing_utils",
]

[[package]]
name = "helpers"
version = "0.1.0"
dependencies = [
"account_factory",
"openzeppelin",
"snforge_std",
"starkware_utils_testing",
"testing_utils",
]

[[package]]
name = "openzeppelin"
version = "2.0.0"
Expand Down Expand Up @@ -194,20 +212,6 @@ dependencies = [
"starkware_utils",
]

[[package]]
name = "strategy_implementation"
version = "0.1.0"
dependencies = [
"account_factory",
"contracts",
"earn_reporter",
"openzeppelin",
"snforge_std",
"starkware_utils",
"starkware_utils_testing",
"testing_utils",
]

[[package]]
name = "testing_utils"
version = "0.1.0"
Expand Down
2 changes: 1 addition & 1 deletion Scarb.toml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
[workspace]
members = ["contracts", "eth_712_account", "account_factory", "earn_reporter", "strategy_implementation", "testing_utils"]
members = ["contracts", "eth_712_account", "account_factory", "earn_reporter", "counter", "helpers", "testing_utils"]

[workspace.package]
edition = "2024_07"
Expand Down
1 change: 1 addition & 0 deletions account_factory/Scarb.toml
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ testing_utils = { path = "../testing_utils" }

[[target.starknet-contract]]
sierra = true
casm = true

[[test]]
build-external-contracts = [
Expand Down
33 changes: 24 additions & 9 deletions account_factory/src/account_factory.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,12 @@ use starknet::{ClassHash, ContractAddress, EthAddress};
pub trait IAccountFactory<TContractState> {
fn account_class_hash(self: @TContractState) -> ClassHash;
fn set_account_class_hash(ref self: TContractState, new_class_hash: ClassHash);
/// `session_eth_address` is the Ethereum-format address of a fresh secp256k1 key
/// the client derived from a one-time MetaMask bootstrap signature. It is NOT the
/// user's MetaMask address; it has no on-chain history outside this account.
/// Passing the real MM address here would defeat the unlinkability property.
fn deploy_account(
ref self: TContractState, eth_address: EthAddress, signature: Signature,
ref self: TContractState, session_eth_address: EthAddress, signature: Signature,
) -> ContractAddress;
}

Expand Down Expand Up @@ -74,10 +78,14 @@ pub mod AccountFactory {
pub new_class_hash: ClassHash,
}

/// Emitted on first deploy of an account. `eth_address` is intentionally NOT
/// included so that on-chain indexers cannot trivially associate the account
/// with any externally-meaningful Ethereum identity. The deterministic salt
/// (`session_eth_address`) is recoverable off-chain only by the wallet that
/// holds the bootstrap signature.
#[derive(Drop, starknet::Event, Debug, PartialEq)]
pub struct AccountDeployed {
pub account_class_hash: ClassHash,
pub eth_address: EthAddress,
pub account_address: ContractAddress,
}

Expand Down Expand Up @@ -114,12 +122,18 @@ pub mod AccountFactory {
);
}

/// Returns the deterministic account contract address for the given Ethereum
/// address, deploying and upgrading a Primer contract on first use.
/// Returns the deterministic account contract address for the given session
/// Ethereum-format address, deploying and upgrading a Primer contract on
/// first use.
///
/// Tier 2 unlinkability: callers MUST pass `session_eth_address` — the
/// address of a fresh secp256k1 key derived from a one-time MetaMask
/// bootstrap signature — NOT the user's real MetaMask address. The whole
/// privacy property collapses if the real MM address is used here.
fn deploy_account(
ref self: ContractState, eth_address: EthAddress, signature: Signature,
ref self: ContractState, session_eth_address: EthAddress, signature: Signature,
) -> ContractAddress {
let account_address = eth_address_to_account(:eth_address);
let account_address = eth_address_to_account(eth_address: session_eth_address);
// If the account contract is deployed, return the address.
if is_deployed(addr: account_address) {
return account_address;
Expand All @@ -131,7 +145,7 @@ pub mod AccountFactory {
// 3. Initialize the account contract.
let (deployed_address, _retdata) = syscalls::deploy_syscall(
class_hash: PRIMER_CLASS_HASH,
contract_address_salt: eth_address.into(),
contract_address_salt: session_eth_address.into(),
calldata: [].span(),
deploy_from_zero: false,
)
Expand All @@ -147,11 +161,12 @@ pub mod AccountFactory {
let eth_account_initializer = IEthAccountInitializerDispatcher {
contract_address: account_address,
};
eth_account_initializer.initialize(:eth_address, :signature);
eth_account_initializer
.initialize(owner_eth_address: session_eth_address, :signature);
self
.emit(
Event::AccountDeployed(
AccountDeployed { account_class_hash, eth_address, account_address },
AccountDeployed { account_class_hash, account_address },
),
);

Expand Down
46 changes: 23 additions & 23 deletions account_factory/src/test.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,11 @@ use testing_utils::event_helpers::{get_event_by_selector, get_event_by_selector_


fn deploy_account_wrapper(
account_factory_addr: ContractAddress, eth_address: EthAddress,
account_factory_addr: ContractAddress, session_eth_address: EthAddress,
) -> ContractAddress {
let account_factory = IAccountFactoryDispatcher { contract_address: account_factory_addr };
let signature = Signature { r: 0x1012, s: 0x1012, y_parity: true };
account_factory.deploy_account(:eth_address, :signature)
account_factory.deploy_account(:session_eth_address, :signature)
}


Expand Down Expand Up @@ -106,16 +106,16 @@ fn test_deploy_account_deploys_once_and_reuses() {
let account_factory = IAccountFactoryDispatcher { contract_address: account_factory_addr };
let mut spy = snforge_std::spy_events();

let eth_address: EthAddress = '0x1012'.try_into().unwrap();
let session_eth_address: EthAddress = '0x1012'.try_into().unwrap();

// Compute the expected account address using the same derivation logic as the contract.
let expected_account = eth_address_to_account(
account_factory: account_factory_addr, :eth_address,
account_factory: account_factory_addr, eth_address: session_eth_address,
);

// First call: lazily deploys the primer, upgrades it to the account_class_hash, and
// leaves the account contract at the expected address.
let account_address = deploy_account_wrapper(:account_factory_addr, :eth_address);
let account_address = deploy_account_wrapper(:account_factory_addr, :session_eth_address);
assert!(account_address == expected_account, "unexpected account address after first deploy");
let class_hash_after_first = get_class_hash_at_syscall(account_address).unwrap_syscall();
let expected_class_hash = account_factory.account_class_hash();
Expand All @@ -130,7 +130,7 @@ fn test_deploy_account_deploys_once_and_reuses() {
)
.unwrap();
let expected_event = AccountDeployed {
account_class_hash: class_hash_after_first, eth_address, account_address,
account_class_hash: class_hash_after_first, account_address,
};
assert_expected_event_emitted(
spied_event: spied_event,
Expand All @@ -141,7 +141,7 @@ fn test_deploy_account_deploys_once_and_reuses() {

// Second call with the same parameters should reuse the same account and leave the
// class hash unchanged.
let second_account_address = deploy_account_wrapper(:account_factory_addr, :eth_address);
let second_account_address = deploy_account_wrapper(:account_factory_addr, :session_eth_address);
assert!(second_account_address == account_address, "account address changed on reuse");
let class_hash_after_second = get_class_hash_at_syscall(account_address).unwrap_syscall();
assert!(
Expand All @@ -162,13 +162,13 @@ fn test_after_change_account_class_hash_reuses_existing_account() {
let account_factory_addr = setup_account_factory_test_env();
let account_factory = IAccountFactoryDispatcher { contract_address: account_factory_addr };

let eth_address: EthAddress = '0x1012'.try_into().unwrap();
let session_eth_address: EthAddress = '0x1012'.try_into().unwrap();
let expected_account = eth_address_to_account(
account_factory: account_factory_addr, :eth_address,
account_factory: account_factory_addr, eth_address: session_eth_address,
);

// First call: deploys and upgrades to the initial account_class_hash.
let account_address = deploy_account_wrapper(:account_factory_addr, :eth_address);
let account_address = deploy_account_wrapper(:account_factory_addr, :session_eth_address);
assert!(account_address == expected_account, "unexpected account address after first deploy");
let class_hash_after_first = get_class_hash_at_syscall(account_address).unwrap_syscall();
let initial_class_hash = account_factory.account_class_hash();
Expand All @@ -187,7 +187,7 @@ fn test_after_change_account_class_hash_reuses_existing_account() {
// Second call with the same parameters should reuse the same account and leave the
// class hash unchanged.
let mut second_spy = snforge_std::spy_events();
let second_account_address = deploy_account_wrapper(:account_factory_addr, :eth_address);
let second_account_address = deploy_account_wrapper(:account_factory_addr, :session_eth_address);
assert!(second_account_address == account_address, "account address changed on reuse");
let class_hash_after_second = get_class_hash_at_syscall(account_address).unwrap_syscall();
assert!(
Expand All @@ -213,13 +213,15 @@ fn test_change_account_class_hash_affects_only_new_users() {
let account_factory = IAccountFactoryDispatcher { contract_address: account_factory_addr };
let mut spy = snforge_std::spy_events();

let eth_address: EthAddress = '0x1012'.try_into().unwrap();
let session_eth_address: EthAddress = '0x1012'.try_into().unwrap();
let expected_account = eth_address_to_account(
account_factory: account_factory_addr, :eth_address,
account_factory: account_factory_addr, eth_address: session_eth_address,
);

// First call: deploys and upgrades to the initial account_class_hash.
let first_account_address = deploy_account_wrapper(:account_factory_addr, :eth_address);
let first_account_address = deploy_account_wrapper(
:account_factory_addr, :session_eth_address,
);
assert!(
first_account_address == expected_account, "unexpected account address after first deploy",
);
Expand All @@ -238,18 +240,18 @@ fn test_change_account_class_hash_affects_only_new_users() {
);
account_factory.set_account_class_hash(new_class_hash: new_hash);

// Build parameters for a *new* Eth address so the contract derives a second account.
let new_eth_address: EthAddress = '0x1013'.try_into().unwrap();
// Build parameters for a *new* session Eth address so the contract derives a second account.
let new_session_eth_address: EthAddress = '0x1013'.try_into().unwrap();
let expected_new_account = eth_address_to_account(
account_factory: account_factory_addr, eth_address: new_eth_address,
account_factory: account_factory_addr, eth_address: new_session_eth_address,
);

let new_account_address = deploy_account_wrapper(
:account_factory_addr, eth_address: new_eth_address,
:account_factory_addr, session_eth_address: new_session_eth_address,
);
assert!(
new_account_address == expected_new_account,
"new account should be derived as expected from new Eth address",
"new account should be derived as expected from new session Eth address",
);
let class_hash_new_account = get_class_hash_at_syscall(new_account_address).unwrap_syscall();
assert!(
Expand All @@ -262,9 +264,7 @@ fn test_change_account_class_hash_affects_only_new_users() {
let spied_event = get_event_by_selector_n(:events, selector: selector!("AccountDeployed"), n: 1)
.unwrap();
let expected_event = AccountDeployed {
account_class_hash: new_hash,
eth_address: new_eth_address,
account_address: new_account_address,
account_class_hash: new_hash, account_address: new_account_address,
};
assert_expected_event_emitted(
spied_event: spied_event,
Expand All @@ -275,7 +275,7 @@ fn test_change_account_class_hash_affects_only_new_users() {

// Check that the original account doesn't change even though the account class hash has
// changed.
let prev_account_address = deploy_account_wrapper(:account_factory_addr, :eth_address);
let prev_account_address = deploy_account_wrapper(:account_factory_addr, :session_eth_address);
assert!(prev_account_address == expected_account, "original account should not change");
let class_hash_prev_account = get_class_hash_at_syscall(prev_account_address).unwrap_syscall();
assert!(
Expand Down
11 changes: 9 additions & 2 deletions account_factory/src/utils.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -12,17 +12,24 @@ pub const PRIMER_CLASS_HASH: ClassHash =
.try_into()
.unwrap();

// Recomputed for scarb 2.16.1 release-profile builds. If you upgrade scarb
// or change Primer source, rerun `sncast utils class-hash --package contracts
// --contract-name Primer` and update this constant — the on-chain Primer
// declaration's class hash MUST match this value or the factory's
// deploy_syscall will fail with CLASS_NOT_DECLARED.
#[cfg(not(target: "test"))]
pub const PRIMER_CLASS_HASH: ClassHash =
0x00123e6bc1c14ae9934e933d3f64916a6116dd6b036a922b2b1f0815e0d1d300
0x03edae2158f4aea6295470678fc7de27e19d7e40f295cda90d786d17b3531fdf
.try_into()
.unwrap();

const CONTRACT_ADDRESS_PREFIX: felt252 = 'STARKNET_CONTRACT_ADDRESS';

#[starknet::interface]
pub(crate) trait IEthAccountInitializer<TContractState> {
fn initialize(ref self: TContractState, eth_address: EthAddress, signature: Signature);
fn initialize(
ref self: TContractState, owner_eth_address: EthAddress, signature: Signature,
);
}

/// Computes the Pedersen hash on the elements of the span using a hash state.
Expand Down
1 change: 1 addition & 0 deletions contracts/Scarb.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ starkware_utils_testing.workspace = true

[[target.starknet-contract]]
sierra = true
casm = true

[profile.dev.cairo]
unstable-add-statements-functions-debug-info = true
Expand Down
24 changes: 24 additions & 0 deletions counter/Scarb.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
[package]
name = "counter"
edition.workspace = true
version.workspace = true

[lib]

[dependencies]
starknet.workspace = true

[dev-dependencies]
snforge_std.workspace = true

[[target.starknet-contract]]
sierra = true
casm = true

[profile.dev.cairo]
unstable-add-statements-functions-debug-info = true
unstable-add-statements-code-locations-debug-info = true
inlining-strategy = "avoid"

[scripts]
test = "snforge test"
Loading