1use near_sdk::{
2 json_types::{Base58CryptoHash, Base64VecU8, U64},
3 near,
4};
5
6#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
12#[near(serializers = [json, borsh])]
13pub enum DeployMode {
14 Normal,
15 GlobalHash,
16}
17
18#[derive(Debug, Clone, PartialEq, Eq)]
30#[near(serializers = [json, borsh(use_discriminant = true)])]
31#[repr(u8)]
32pub enum VersionSource {
33 Stored(Base64VecU8) = 0,
35 PublishGlobal(Base64VecU8) = 1,
37 ExistingGlobal(Base58CryptoHash) = 2,
39}
40
41impl std::fmt::Display for DeployMode {
42 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
43 match self {
44 DeployMode::Normal => write!(f, "Normal"),
45 DeployMode::GlobalHash => write!(f, "GlobalHash"),
46 }
47 }
48}
49
50#[derive(Clone, Debug, PartialEq, Eq)]
51#[near(serializers = [borsh, json])]
52pub struct Deployment {
53 pub version_key: String,
54 pub code_hash: Base58CryptoHash,
55 pub block_height: U64,
56}
57
58#[derive(Clone, Copy, Debug, PartialEq, Eq)]
63#[near(serializers = [json])]
64pub enum VersionAvailability {
65 Stored { code_len: u32 },
67 Global,
69 Removed,
71}
72
73impl VersionAvailability {
74 pub fn is_deployable(self) -> bool {
75 matches!(self, Self::Stored { .. } | Self::Global)
76 }
77}
78
79#[derive(Clone, Copy, Debug, PartialEq, Eq)]
81#[near(serializers = [json])]
82pub struct VersionInfo {
83 pub code_hash: Base58CryptoHash,
86 pub availability: VersionAvailability,
87}
88
89#[derive(Clone, Debug, PartialEq, Eq)]
94#[near(serializers = [json])]
95pub enum RegistryEntryView {
96 Reserved,
98 Deployed(Deployment),
99}
100
101impl RegistryEntryView {
102 pub fn deployment(&self) -> Option<&Deployment> {
103 match self {
104 Self::Reserved => None,
105 Self::Deployed(deployment) => Some(deployment),
106 }
107 }
108}
109
110#[cfg(test)]
111mod tests {
112 use near_sdk::serde_json::{self, json};
113 use rstest::rstest;
114
115 use super::*;
116
117 fn deployment() -> Deployment {
118 Deployment {
119 version_key: "market@1.5.0".to_string(),
120 code_hash: Base58CryptoHash::from([7u8; 32]),
121 block_height: 42.into(),
122 }
123 }
124
125 #[rstest]
126 #[case(VersionAvailability::Stored { code_len: 521_039 }, json!({ "Stored": { "code_len": 521_039 } }))]
127 #[case(VersionAvailability::Global, json!("Global"))]
128 #[case(VersionAvailability::Removed, json!("Removed"))]
129 fn availability_wire_format(
130 #[case] availability: VersionAvailability,
131 #[case] expected: serde_json::Value,
132 ) {
133 assert_eq!(serde_json::to_value(availability).unwrap(), expected);
134 assert_eq!(
135 serde_json::from_value::<VersionAvailability>(expected).unwrap(),
136 availability,
137 );
138 }
139
140 #[rstest]
143 #[case(VersionAvailability::Stored { code_len: 1 }, true)]
144 #[case(VersionAvailability::Global, true)]
145 #[case(VersionAvailability::Removed, false)]
146 fn deployability(#[case] availability: VersionAvailability, #[case] expected: bool) {
147 assert_eq!(availability.is_deployable(), expected);
148 }
149
150 #[test]
151 fn version_info_round_trips() {
152 let info = VersionInfo {
153 code_hash: Base58CryptoHash::from([3u8; 32]),
154 availability: VersionAvailability::Stored { code_len: 128 },
155 };
156 let value = serde_json::to_value(info).unwrap();
157 assert_eq!(serde_json::from_value::<VersionInfo>(value).unwrap(), info);
158 }
159
160 #[rstest]
163 #[case(DeployMode::Normal, VersionSource::Stored(Base64VecU8(vec![0xde, 0xad])))]
164 #[case(DeployMode::GlobalHash, VersionSource::PublishGlobal(Base64VecU8(vec![0xde, 0xad])))]
165 fn borsh_is_wire_compatible_with_deploy_mode(
166 #[case] mode: DeployMode,
167 #[case] source: VersionSource,
168 ) {
169 let version_key = "market@1.5.0";
170 assert_eq!(
171 near_sdk::borsh::to_vec(&(version_key, &source)).unwrap(),
172 near_sdk::borsh::to_vec(&(version_key, mode, vec![0xdeu8, 0xad])).unwrap(),
173 );
174 }
175
176 #[rstest]
179 #[case(VersionSource::Stored(Base64VecU8(vec![0xaa])), vec![0, 1, 0, 0, 0, 0xaa])]
180 #[case(VersionSource::PublishGlobal(Base64VecU8(vec![0xaa])), vec![1, 1, 0, 0, 0, 0xaa])]
181 #[case(
182 VersionSource::ExistingGlobal(Base58CryptoHash::from([7u8; 32])),
183 [&[2u8][..], &[7u8; 32][..]].concat(),
184 )]
185 fn borsh_discriminants_are_stable(#[case] source: VersionSource, #[case] expected: Vec<u8>) {
186 assert_eq!(near_sdk::borsh::to_vec(&source).unwrap(), expected);
187 }
188
189 #[rstest]
190 #[case(VersionSource::Stored(Base64VecU8(vec![1, 2, 3])))]
191 #[case(VersionSource::PublishGlobal(Base64VecU8(vec![1, 2, 3])))]
192 #[case(VersionSource::ExistingGlobal(Base58CryptoHash::from([9u8; 32])))]
193 fn version_source_round_trips(#[case] source: VersionSource) {
194 let bytes = near_sdk::borsh::to_vec(&source).unwrap();
195 assert_eq!(
196 near_sdk::borsh::from_slice::<VersionSource>(&bytes).unwrap(),
197 source,
198 );
199 let value = serde_json::to_value(&source).unwrap();
200 assert_eq!(
201 serde_json::from_value::<VersionSource>(value).unwrap(),
202 source,
203 );
204 }
205
206 #[test]
207 fn reserved_is_distinguishable_from_deployed() {
208 let reserved = RegistryEntryView::Reserved;
209 assert_eq!(serde_json::to_value(&reserved).unwrap(), json!("Reserved"));
210 assert_eq!(reserved.deployment(), None);
211
212 let deployed = RegistryEntryView::Deployed(deployment());
213 assert_eq!(deployed.deployment(), Some(&deployment()));
214 assert_eq!(
215 serde_json::from_value::<RegistryEntryView>(serde_json::to_value(&deployed).unwrap())
216 .unwrap(),
217 deployed,
218 );
219 }
220}