sui_bridge/server/
governance_verifier.rs

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