iota_bridge/server/
governance_verifier.rs

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