1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
//! Governance structures.
//!
//! # Note
//!
//! This **MUST** be kept in sync with go/governance/api.
//!
use std::collections::BTreeMap;

use crate::{
    common::{quantity::Quantity, version::ProtocolVersions},
    consensus::beacon::EpochTime,
};

/// A governance vote.
#[derive(
    Clone, Debug, Default, PartialEq, Eq, Hash, PartialOrd, Ord, cbor::Encode, cbor::Decode,
)]
#[repr(u8)]
pub enum Vote {
    /// Invalid vote that should never be explicitly set.
    #[default]
    Invalid = 0,
    /// Yes Vote.
    Yes = 1,
    /// No vote.
    No = 2,
    /// Abstained.
    Abstain = 3,
}

/// Vote for a proposal.
#[derive(Clone, Debug, Default, PartialEq, Eq, Hash, cbor::Encode, cbor::Decode)]
pub struct ProposalVote {
    /// Unique identifier of a proposal.
    pub id: u64,

    /// Proposal vote.
    pub vote: Vote,
}

/// Upgrade proposal content.
#[derive(Clone, Debug, Default, PartialEq, Eq, Hash, cbor::Encode, cbor::Decode)]
pub struct UpgradeProposal {
    pub v: u16,
    pub handler: String,
    pub target: ProtocolVersions,
    pub epoch: EpochTime,
}

/// Cancel proposal content.
#[derive(Clone, Debug, Default, PartialEq, Eq, Hash, cbor::Encode, cbor::Decode)]
pub struct CancelUpgradeProposal {
    pub proposal_id: u64,
}

/// Change parameters proposal content.
#[derive(Clone, Debug, Default, PartialEq, Eq, cbor::Encode, cbor::Decode)]
pub struct ChangeParametersProposal {
    pub module: String,
    pub changes: Option<cbor::Value>,
}

/// Consensus layer governance proposal content.
#[derive(Clone, Debug, Default, PartialEq, Eq, cbor::Encode, cbor::Decode)]
pub struct ProposalContent {
    #[cbor(optional)]
    pub upgrade: Option<UpgradeProposal>,
    #[cbor(optional)]
    pub cancel_upgrade: Option<CancelUpgradeProposal>,
    #[cbor(optional)]
    pub change_parameters: Option<ChangeParametersProposal>,
}

// Allowed governance consensus parameter changes.
#[derive(Clone, Debug, Default, PartialEq, Eq, Hash, cbor::Encode, cbor::Decode)]
pub struct ConsensusParameterChanges {
    #[cbor(optional)]
    pub gas_costs: BTreeMap<String, u64>,
    #[cbor(optional)]
    pub min_proposal_deposit: Option<Quantity>,
    #[cbor(optional)]
    pub voting_period: Option<EpochTime>,
    #[cbor(optional)]
    pub stake_threshold: Option<u8>,
    #[cbor(optional)]
    pub upgrade_min_epoch_diff: Option<EpochTime>,
    #[cbor(optional)]
    pub upgrade_cancel_min_epoch_diff: Option<EpochTime>,
    #[cbor(optional)]
    pub enable_change_parameters_proposal: Option<bool>,
}

#[cfg(test)]
mod tests {
    use base64::prelude::*;

    use super::*;

    #[test]
    fn test_consistent_proposal_vote() {
        // NOTE: These tests MUST be synced with go/governance/api/api_test.go.
        let tcs = vec![
            (
                "omJpZAtkdm90ZQE=",
                ProposalVote {
                    id: 11,
                    vote: Vote::Yes,
                },
            ),
            (
                "omJpZAxkdm90ZQI=",
                ProposalVote {
                    id: 12,
                    vote: Vote::No,
                },
            ),
            (
                "omJpZA1kdm90ZQM=",
                ProposalVote {
                    id: 13,
                    vote: Vote::Abstain,
                },
            ),
        ];
        for (encoded_base64, vote) in tcs {
            let dec: ProposalVote =
                cbor::from_slice(&BASE64_STANDARD.decode(encoded_base64).unwrap())
                    .expect("proposal vote should deserialize correctly");
            assert_eq!(
                dec, vote,
                "decoded proposal vote should match the expected value"
            );

            let ser = BASE64_STANDARD.encode(cbor::to_vec(dec));
            assert_eq!(
                ser, encoded_base64,
                "proposal vote should serialize correctly"
            );
        }
    }

    #[test]
    fn test_consistent_proposal_content() {
        // NOTE: These tests MUST be synced with go/governance/api/api_test.go.
        let tcs = vec![
            (
                "oW5jYW5jZWxfdXBncmFkZaFrcHJvcG9zYWxfaWQYKg==",
                ProposalContent {
                    cancel_upgrade: Some(CancelUpgradeProposal { proposal_id: 42 }),
                    ..Default::default()
                },
            ),
            (
                "oWd1cGdyYWRlpGF2AmVlcG9jaBgqZnRhcmdldKNyY29uc2Vuc3VzX3Byb3RvY29soWVwYXRjaBh7dXJ1bnRpbWVfaG9zdF9wcm90b2NvbKFlcGF0Y2gZAch4GnJ1bnRpbWVfY29tbWl0dGVlX3Byb3RvY29soWVwYXRjaBkDFWdoYW5kbGVybHRlc3QtaGFuZGxlcg==",
                ProposalContent {
                    upgrade: Some(UpgradeProposal {
                        v: 2,
                        handler: "test-handler".into(),
                        target: ProtocolVersions { consensus_protocol: 123.into(), runtime_host_protocol: 456.into(), runtime_committee_protocol: 789.into() } ,
                        epoch: 42,
                    }),
                    ..Default::default()
                },
            ),
            (
                "oXFjaGFuZ2VfcGFyYW1ldGVyc6JmbW9kdWxla3Rlc3QtbW9kdWxlZ2NoYW5nZXOhbXZvdGluZ19wZXJpb2QYew==",
                ProposalContent {
                    change_parameters: Some(ChangeParametersProposal {
                        module: "test-module".into(),
                        changes: Some(cbor::to_value(ConsensusParameterChanges{
                            voting_period: Some(123),
                            ..Default::default()
                        })),
                     }),
                    ..Default::default()
                }
            ),
        ];
        for (encoded_base64, content) in tcs {
            let dec: ProposalContent =
                cbor::from_slice(&BASE64_STANDARD.decode(encoded_base64).unwrap())
                    .expect("proposal content should deserialize correctly");
            assert_eq!(
                dec, content,
                "decoded proposal content should match the expected value"
            );

            let ser = BASE64_STANDARD.encode(cbor::to_vec(dec));
            assert_eq!(
                ser, encoded_base64,
                "proposal content should serialize correctly"
            );
        }
    }
}