Skip to content

Commit 8559ac9

Browse files
committed
implement contract controllers and logics
1 parent 7e4f317 commit 8559ac9

8 files changed

Lines changed: 185 additions & 9 deletions

File tree

Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,4 +6,6 @@ members = [
66
"contracts/shared",
77
"contracts/reserve_contract",
88
"contracts/native_transfer",
9+
"contracts/claim_verifier",
10+
911
]
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
[package]
2+
name = "claim-verifier"
3+
version = "0.1.0"
4+
edition = "2021"
5+
6+
[lib]
7+
crate-type = ["rlib", "cdylib"]
8+
9+
[dependencies]
10+
soroban-sdk = "22.0.0"
11+
bridgelet-shared = { path = "../shared" }
12+
13+
[dev-dependencies]
14+
soroban-sdk = { version = "22.0.0", features = ["testutils"] }
15+
bridgelet-shared = { path = "../shared", features = ["testutils"] }
16+
17+
[features]
18+
testutils = ["soroban-sdk/testutils"]
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
use soroban_sdk::contracterror;
2+
3+
#[contracterror]
4+
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
5+
pub enum Error {
6+
AlreadyInitialized = 1,
7+
NotInitialized = 2,
8+
AuthorizedSignerNotSet = 3,
9+
InvalidSignature = 4,
10+
SignatureVerificationFailed = 5,
11+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
use soroban_sdk::{contracttype, symbol_short, Address, Env};
2+
3+
#[contracttype]
4+
#[derive(Clone, Debug, Eq, PartialEq)]
5+
pub struct VerificationSucceeded {
6+
pub destination: Address,
7+
pub nonce: u64,
8+
}
9+
10+
pub fn emit_verification_succeeded(env: &Env, destination: Address, nonce: u64) {
11+
let event = VerificationSucceeded { destination, nonce };
12+
env.events()
13+
.publish((symbol_short!("verified"),), event);
14+
}
Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
#![no_std]
2+
3+
mod errors;
4+
mod events;
5+
6+
use soroban_sdk::{contract, contractimpl, Address, BytesN, Env};
7+
8+
pub use errors::Error;
9+
pub use events::VerificationSucceeded;
10+
11+
#[contract]
12+
pub struct ClaimVerifierContract;
13+
14+
#[contractimpl]
15+
impl ClaimVerifierContract {
16+
/// Initialize with the authorized signer public key
17+
///
18+
/// # Arguments
19+
/// * `authorized_signer` - Ed25519 public key (32 bytes)
20+
///
21+
/// # Errors
22+
/// * `Error::AlreadyInitialized` - called more than once
23+
pub fn initialize(env: Env, authorized_signer: BytesN<32>) -> Result<(), Error> {
24+
if env.storage().instance().has(&"signer") {
25+
return Err(Error::AlreadyInitialized);
26+
}
27+
28+
env.storage()
29+
.instance()
30+
.set(&"signer", &authorized_signer);
31+
32+
Ok(())
33+
}
34+
35+
/// Verify an Ed25519 sweep authorization signature
36+
///
37+
/// Message format matches sweep_controller/authorization.rs:
38+
/// hash(destination + nonce + contract_id)
39+
///
40+
/// # Arguments
41+
/// * `destination` - Destination wallet address
42+
/// * `nonce` - Current sweep nonce
43+
/// * `signature` - Ed25519 signature (64 bytes)
44+
///
45+
/// # Errors
46+
/// * `Error::NotInitialized` - contract not initialized
47+
/// * `Error::AuthorizedSignerNotSet` - no signer stored
48+
/// * `Error::SignatureVerificationFailed` - signature is invalid
49+
pub fn verify(
50+
env: Env,
51+
destination: Address,
52+
nonce: u64,
53+
signature: BytesN<64>,
54+
) -> Result<(), Error> {
55+
// Get authorized signer
56+
let authorized_signer: BytesN<32> = env
57+
.storage()
58+
.instance()
59+
.get(&"signer")
60+
.ok_or(Error::AuthorizedSignerNotSet)?;
61+
62+
// Construct message: hash(destination + nonce + contract_id)
63+
let message = Self::construct_message(&env, &destination, nonce);
64+
65+
// Verify Ed25519 signature
66+
env.crypto()
67+
.ed25519_verify(&authorized_signer, &message.into(), &signature);
68+
69+
// Emit success event
70+
events::emit_verification_succeeded(&env, destination, nonce);
71+
72+
Ok(())
73+
}
74+
75+
// Private helper — constructs the message hash identical to
76+
// sweep_controller/authorization.rs construct_sweep_message
77+
fn construct_message(env: &Env, destination: &Address, nonce: u64) -> BytesN<32> {
78+
use soroban_sdk::xdr::ToXdr;
79+
80+
let contract_id = env.current_contract_address();
81+
let mut message = soroban_sdk::Bytes::new(env);
82+
83+
let dest_bytes = destination.to_xdr(env);
84+
message.append(&dest_bytes);
85+
86+
message.push_back(((nonce >> 56) & 0xFF) as u8);
87+
message.push_back(((nonce >> 48) & 0xFF) as u8);
88+
message.push_back(((nonce >> 40) & 0xFF) as u8);
89+
message.push_back(((nonce >> 32) & 0xFF) as u8);
90+
message.push_back(((nonce >> 24) & 0xFF) as u8);
91+
message.push_back(((nonce >> 16) & 0xFF) as u8);
92+
message.push_back(((nonce >> 8) & 0xFF) as u8);
93+
message.push_back((nonce & 0xFF) as u8);
94+
95+
let contract_bytes = contract_id.to_xdr(env);
96+
message.append(&contract_bytes);
97+
98+
env.crypto().sha256(&message).into()
99+
}
100+
}
101+

contracts/sweep_controller/Cargo.toml

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,15 +8,18 @@ crate-type = ["cdylib", "rlib"]
88

99
[dependencies]
1010
soroban-sdk = "22.0.0"
11+
native-transfer = { path = "../native_transfer" }
1112
bridgelet-shared = { path = "../shared", version = "0.1.0" }
1213
ephemeral_account = { path = "../ephemeral_account", version = "0.1.0" }
14+
claim-verifier = { path = "../claim_verifier" }
1315

1416
soroban-token-sdk = "22.0.0"
1517

1618
[dev-dependencies]
1719
soroban-sdk = { version = "22.0.0", features = ["testutils"] }
1820
bridgelet-shared = { path = "../shared", features = ["testutils"] }
19-
21+
native-transfer = { path = "../native_transfer", features = ["testutils"] }
22+
claim-verifier = { path = "../claim_verifier", features = ["testutils"] }
2023

2124
[profile.release]
2225
opt-level = "z"
@@ -31,3 +34,4 @@ lto = true
3134
[profile.release-with-logs]
3235
inherits = "release"
3336
debug-assertions = true
37+

contracts/sweep_controller/src/lib.rs

Lines changed: 18 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
11
#![no_std]
2+
use claim_verifier::ClaimVerifierContractClient as ClaimVerifierClient;
3+
4+
25

36
mod authorization;
47
mod errors;
@@ -31,6 +34,9 @@ impl SweepController {
3134
authorized_signer: BytesN<32>,
3235
authorized_destination: Option<Address>,
3336
creator: Address,
37+
native_transfer_address: Address,
38+
native_asset_address: Address,
39+
claim_verifier_address: Address,
3440
) -> Result<(), Error> {
3541
// Check if already initialized
3642
if storage::get_authorized_signer(&env).is_some() {
@@ -47,6 +53,10 @@ impl SweepController {
4753

4854
// Initialize the sweep nonce to 0
4955
storage::init_sweep_nonce(&env);
56+
storage::set_native_transfer_address(&env, &native_transfer_address);
57+
storage::set_native_asset_address(&env, &native_asset_address);
58+
storage::set_claim_verifier_address(&env, &claim_verifier_address);
59+
5060

5161
// Store authorized destination if provided
5262
if let Some(destination) = authorized_destination {
@@ -84,17 +94,17 @@ impl SweepController {
8494
}
8595
}
8696

87-
// Verify authorization
88-
let auth_ctx = AuthContext::new(
89-
ephemeral_account.clone(),
90-
destination.clone(),
91-
auth_signature.clone(),
92-
);
93-
auth_ctx.verify(&env)?;
97+
// Verify authorization via claim_verifier contract
98+
let claim_verifier_address = storage::get_claim_verifier_address(&env)
99+
.ok_or(Error::AuthorizationFailed)?;
100+
let verifier = ClaimVerifierClient::new(&env, &claim_verifier_address);
101+
let nonce = storage::get_sweep_nonce(&env);
102+
verifier.verify(&destination, &nonce, &auth_signature);
94103

95-
// Increment nonce after successful verification to prevent replay attacks
104+
// Increment nonce after successful verification
96105
authorization::increment_nonce(&env);
97106

107+
98108
// Call ephemeral account contract to validate and authorize sweep
99109
// This triggers the account's sweep() method which updates state
100110
let account_client = EphemeralAccountClient::new(&env, &ephemeral_account);

contracts/sweep_controller/src/storage.rs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,9 @@ pub enum DataKey {
1212
AuthorizedDestination,
1313
/// Creator address (the address that initialized the contract)
1414
Creator,
15+
NativeTransferAddress,
16+
NativeAssetAddress,
17+
ClaimVerifierAddress,
1518
}
1619

1720
/// Set the authorized signer public key
@@ -125,3 +128,16 @@ pub fn set_creator(env: &Env, creator: &Address) {
125128
pub fn get_creator(env: &Env) -> Option<Address> {
126129
env.storage().instance().get(&DataKey::Creator)
127130
}
131+
132+
pub fn set_claim_verifier_address(env: &Env, address: &Address) {
133+
env.storage()
134+
.instance()
135+
.set(&DataKey::ClaimVerifierAddress, address);
136+
}
137+
138+
pub fn get_claim_verifier_address(env: &Env) -> Option<Address> {
139+
env.storage()
140+
.instance()
141+
.get(&DataKey::ClaimVerifierAddress)
142+
}
143+

0 commit comments

Comments
 (0)