Skip to content

Commit 1673140

Browse files
authored
Merge pull request #340 from devkryssie/feat/bridge-multisig-validation-203
feat(bridge): add multi-signature validator enforcement for cross-chain transactions
2 parents 3d271ce + f57f813 commit 1673140

3 files changed

Lines changed: 258 additions & 85 deletions

File tree

contracts/bridge/src/lib.rs

Lines changed: 40 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,10 @@ mod bridge {
4444
/// Bridge operators
4545
bridge_operators: Vec<AccountId>,
4646

47+
/// Registered validators for multi-signature cross-chain transactions.
48+
/// Only accounts in this set may sign bridge requests (issue #203).
49+
validators: Vec<AccountId>,
50+
4751
/// Request counter
4852
request_counter: u64,
4953

@@ -163,6 +167,7 @@ mod bridge {
163167
verified_transactions: Mapping::default(),
164168
cross_chain_trades: Mapping::default(),
165169
bridge_operators: vec![caller],
170+
validators: Vec::new(),
166171
request_counter: 0,
167172
transaction_counter: 0,
168173
cross_chain_trade_counter: 0,
@@ -272,8 +277,8 @@ mod bridge {
272277
pub fn sign_bridge_request(&mut self, request_id: u64, approve: bool) -> Result<(), Error> {
273278
let caller = self.env().caller();
274279

275-
// Check if caller is a bridge operator
276-
if !self.bridge_operators.contains(&caller) {
280+
// Check if caller is a registered validator (issue #203: only validators may sign)
281+
if !self.validators.contains(&caller) {
277282
return Err(Error::Unauthorized);
278283
}
279284

@@ -688,6 +693,39 @@ mod bridge {
688693
self.bridge_operators.clone()
689694
}
690695

696+
/// Adds a validator (admin only). Only validators may sign bridge requests (issue #203).
697+
#[ink(message)]
698+
pub fn add_validator(&mut self, validator: AccountId) -> Result<(), Error> {
699+
if self.env().caller() != self.admin {
700+
return Err(Error::Unauthorized);
701+
}
702+
if !self.validators.contains(&validator) {
703+
self.validators.push(validator);
704+
}
705+
Ok(())
706+
}
707+
708+
/// Removes a validator (admin only).
709+
#[ink(message)]
710+
pub fn remove_validator(&mut self, validator: AccountId) -> Result<(), Error> {
711+
if self.env().caller() != self.admin {
712+
return Err(Error::Unauthorized);
713+
}
714+
self.validators.retain(|v| v != &validator);
715+
Ok(())
716+
}
717+
718+
/// Returns all registered validators.
719+
#[ink(message)]
720+
pub fn get_validators(&self) -> Vec<AccountId> {
721+
self.validators.clone()
722+
}
723+
724+
/// Returns whether an account is a registered validator.
725+
#[ink(message)]
726+
pub fn is_validator(&self, account: AccountId) -> bool {
727+
self.validators.contains(&account)
728+
}
691729
/// Updates bridge configuration (admin only)
692730
#[ink(message)]
693731
pub fn update_config(&mut self, config: BridgeConfig) -> Result<(), Error> {

contracts/bridge/src/tests.rs

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,10 @@ mod tests {
4141
let mut bridge = setup_bridge();
4242
let accounts = test::default_accounts::<DefaultEnvironment>();
4343

44+
// Register alice as a validator before signing (issue #203)
4445
test::set_caller::<DefaultEnvironment>(accounts.alice);
46+
bridge.add_validator(accounts.alice).expect("admin can add validator");
47+
4548
let metadata = PropertyMetadata {
4649
location: String::from("Test Property"),
4750
size: 1000,
@@ -54,9 +57,71 @@ mod tests {
5457
.initiate_bridge_multisig(1, 2, accounts.bob, 2, Some(50), metadata)
5558
.expect("Bridge initiation should succeed in test");
5659

60+
test::set_caller::<DefaultEnvironment>(accounts.alice);
61+
let result = bridge.sign_bridge_request(request_id, true);
62+
assert!(result.is_ok());
63+
}
64+
65+
#[ink::test]
66+
fn test_non_validator_cannot_sign() {
67+
let mut bridge = setup_bridge();
5768
let accounts = test::default_accounts::<DefaultEnvironment>();
69+
5870
test::set_caller::<DefaultEnvironment>(accounts.alice);
71+
let metadata = PropertyMetadata {
72+
location: String::from("Test Property"),
73+
size: 1000,
74+
legal_description: String::from("Test"),
75+
valuation: 100000,
76+
documents_url: String::from("ipfs://test"),
77+
};
78+
let request_id = bridge
79+
.initiate_bridge_multisig(1, 2, accounts.bob, 2, Some(50), metadata)
80+
.expect("initiation should succeed");
81+
82+
// bob is a bridge operator but NOT a validator — must be rejected
83+
bridge.add_bridge_operator(accounts.bob).expect("admin can add operator");
84+
test::set_caller::<DefaultEnvironment>(accounts.bob);
5985
let result = bridge.sign_bridge_request(request_id, true);
86+
assert_eq!(result, Err(Error::Unauthorized));
87+
}
88+
89+
#[ink::test]
90+
fn test_threshold_enforced_at_execution() {
91+
let mut bridge = setup_bridge();
92+
let accounts = test::default_accounts::<DefaultEnvironment>();
93+
94+
// Register two validators
95+
test::set_caller::<DefaultEnvironment>(accounts.alice);
96+
bridge.add_validator(accounts.alice).expect("add validator alice");
97+
bridge.add_validator(accounts.bob).expect("add validator bob");
98+
bridge.add_bridge_operator(accounts.bob).expect("add operator bob");
99+
100+
let metadata = PropertyMetadata {
101+
location: String::from("Test Property"),
102+
size: 1000,
103+
legal_description: String::from("Test"),
104+
valuation: 100000,
105+
documents_url: String::from("ipfs://test"),
106+
};
107+
let request_id = bridge
108+
.initiate_bridge_multisig(1, 2, accounts.charlie, 2, Some(50), metadata)
109+
.expect("initiation should succeed");
110+
111+
// Only one signature — execution must fail
112+
test::set_caller::<DefaultEnvironment>(accounts.alice);
113+
bridge.sign_bridge_request(request_id, true).expect("alice signs");
114+
115+
test::set_caller::<DefaultEnvironment>(accounts.alice);
116+
let result = bridge.execute_bridge(request_id);
117+
assert_eq!(result, Err(Error::InvalidRequest)); // status not Locked yet
118+
119+
// Second signature — now threshold met, execution succeeds
120+
test::set_caller::<DefaultEnvironment>(accounts.bob);
121+
bridge.sign_bridge_request(request_id, true).expect("bob signs");
122+
123+
test::set_caller::<DefaultEnvironment>(accounts.alice);
124+
let result = bridge.execute_bridge(request_id);
60125
assert!(result.is_ok());
61126
}
62127

0 commit comments

Comments
 (0)