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