sui_bridge/server/
governance_verifier.rs

1// Copyright (c) Mysten Labs, Inc.
2// SPDX-License-Identifier: Apache-2.0
3
4use std::collections::HashMap;
5
6use crate::error::{BridgeError, BridgeResult};
7use crate::types::{BridgeAction, BridgeActionDigest};
8
9#[derive(Debug)]
10pub struct GovernanceVerifier {
11    approved_goverance_actions: HashMap<BridgeActionDigest, BridgeAction>,
12}
13
14impl GovernanceVerifier {
15    pub fn new(approved_actions: Vec<BridgeAction>) -> BridgeResult<Self> {
16        // TOOD(audit-blocking): verify chain ids
17        let mut approved_goverance_actions = HashMap::new();
18        for action in approved_actions {
19            if !action.is_governace_action() {
20                return Err(BridgeError::ActionIsNotGovernanceAction(action));
21            }
22            approved_goverance_actions.insert(action.digest(), action);
23        }
24        Ok(Self {
25            approved_goverance_actions,
26        })
27    }
28
29    pub async fn verify(&self, key: BridgeAction) -> BridgeResult<BridgeAction> {
30        // TODO: an optimization would be to check the current nonce on chain and err for older ones
31        if !key.is_governace_action() {
32            return Err(BridgeError::ActionIsNotGovernanceAction(key));
33        }
34        if let Some(approved_action) = self.approved_goverance_actions.get(&key.digest()) {
35            assert_eq!(
36                &key, approved_action,
37                "Mismatched action found in approved_actions"
38            );
39            return Ok(key);
40        }
41
42        Err(BridgeError::GovernanceActionIsNotApproved)
43    }
44}
45
46#[cfg(test)]
47mod tests {
48    use super::*;
49    use crate::{
50        test_utils::get_test_sui_to_eth_bridge_action,
51        types::{BridgeAction, EmergencyAction, EmergencyActionType, LimitUpdateAction},
52    };
53    use sui_types::bridge::BridgeChainId;
54
55    #[tokio::test]
56    async fn test_governance_verifier() {
57        let action_1 = BridgeAction::EmergencyAction(EmergencyAction {
58            chain_id: BridgeChainId::EthCustom,
59            nonce: 1,
60            action_type: EmergencyActionType::Pause,
61        });
62        let action_2 = BridgeAction::LimitUpdateAction(LimitUpdateAction {
63            chain_id: BridgeChainId::EthCustom,
64            sending_chain_id: BridgeChainId::SuiCustom,
65            nonce: 1,
66            new_usd_limit: 10000,
67        });
68
69        let verifier = GovernanceVerifier::new(vec![action_1.clone(), action_2.clone()]).unwrap();
70        assert_eq!(
71            verifier.verify(action_1.clone()).await.unwrap(),
72            action_1.clone()
73        );
74        assert_eq!(
75            verifier.verify(action_2.clone()).await.unwrap(),
76            action_2.clone()
77        );
78
79        let action_3 = BridgeAction::LimitUpdateAction(LimitUpdateAction {
80            chain_id: BridgeChainId::EthCustom,
81            sending_chain_id: BridgeChainId::SuiCustom,
82            nonce: 2,
83            new_usd_limit: 10000,
84        });
85        assert_eq!(
86            verifier.verify(action_3).await.unwrap_err(),
87            BridgeError::GovernanceActionIsNotApproved
88        );
89
90        // Token transfer action is not allowed
91        let action_4 = get_test_sui_to_eth_bridge_action(None, None, None, None, None, None, None);
92        assert!(matches!(
93            GovernanceVerifier::new(vec![action_1, action_2, action_4.clone()]).unwrap_err(),
94            BridgeError::ActionIsNotGovernanceAction(..)
95        ));
96
97        // Token transfer action will be rejected
98        assert!(matches!(
99            verifier.verify(action_4).await.unwrap_err(),
100            BridgeError::ActionIsNotGovernanceAction(..)
101        ));
102    }
103}