Skip to content

Commit cdfe2af

Browse files
feat(nns): Self describing value conversion for AddWasm and SubnetRentalRequest (#8322)
Implement conversion from AddWasm/SubnetRentalRequest to SelfDescribingValue, following a similar pattern as BitcoinSetConfig
1 parent 9664efe commit cdfe2af

2 files changed

Lines changed: 213 additions & 58 deletions

File tree

rs/nns/governance/src/proposals/execute_nns_function.rs

Lines changed: 131 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,13 @@ use crate::{
77
},
88
proposals::{
99
decode_candid_args_to_self_describing_value::decode_candid_args_to_self_describing_value,
10-
self_describing::ValueBuilder,
10+
self_describing::{SelfDescribingProstEnum, ValueBuilder},
1111
},
1212
};
1313

1414
use candid::{Decode, Encode};
1515
use ic_base_types::CanisterId;
16+
use ic_crypto_sha2::Sha256;
1617
use ic_management_canister_types_private::{CanisterMetadataRequest, CanisterMetadataResponse};
1718
use ic_nns_common::types::CallCanisterRequest;
1819
use ic_nns_constants::{
@@ -22,7 +23,7 @@ use ic_nns_constants::{
2223
};
2324
use ic_nns_governance_api::bitcoin::{BitcoinNetwork, BitcoinSetConfigProposal};
2425
use ic_nns_governance_api::subnet_rental::{SubnetRentalProposalPayload, SubnetRentalRequest};
25-
use ic_sns_wasm::pb::v1::{AddWasmRequest, SnsWasm};
26+
use ic_sns_wasm::pb::v1::{AddWasmRequest, SnsCanisterType, SnsWasm};
2627
use std::sync::Arc;
2728

2829
/// A partial Candid interface for the management canister (ic_00) that contains the necessary
@@ -67,6 +68,16 @@ impl ValidExecuteNnsFunction {
6768
let request = get_request_for_bitcoin_set_config(&self.payload)?;
6869
return Ok(SelfDescribingValue::from(request));
6970
}
71+
ValidNnsFunction::SubnetRentalRequest => {
72+
let subnet_rental_request =
73+
decode_subnet_rental_request(&self.payload).map_err(|e| e.error_message)?;
74+
return Ok(SelfDescribingValue::from(subnet_rental_request));
75+
}
76+
ValidNnsFunction::AddSnsWasm => {
77+
let add_wasm_request =
78+
decode_add_wasm_request(&self.payload).map_err(|e| e.error_message)?;
79+
return Ok(SelfDescribingValue::from(add_wasm_request));
80+
}
7081
_ => {}
7182
};
7283

@@ -142,39 +153,19 @@ impl ValidExecuteNnsFunction {
142153
Ok(encoded_request)
143154
}
144155
ValidNnsFunction::SubnetRentalRequest => {
145-
// Decode the payload to `SubnetRentalRequest`.
146-
let decoded_payload =
147-
Decode!([decoder_config()]; &self.payload, SubnetRentalRequest).map_err(
148-
|_| {
149-
GovernanceError::new_with_message(
150-
ErrorType::InvalidProposal,
151-
"Unable to decode SubnetRentalRequest proposal: {e}",
152-
)
153-
},
154-
)?;
155-
156-
// Convert the payload to `SubnetRentalProposalPayload`.
157-
let SubnetRentalRequest {
158-
user,
159-
rental_condition_id,
160-
} = decoded_payload;
161-
let proposal_creation_time_seconds = proposal_timestamp_seconds;
162-
let encoded_payload = Encode!(&SubnetRentalProposalPayload {
163-
user,
164-
rental_condition_id,
156+
let decoded_payload = decode_subnet_rental_request(&self.payload)?;
157+
let encoded_payload = encode_subnet_rental_proposal_payload(
158+
decoded_payload,
165159
proposal_id,
166-
proposal_creation_time_seconds,
167-
})
168-
.unwrap();
169-
160+
proposal_timestamp_seconds,
161+
)?;
170162
Ok(encoded_payload)
171163
}
172164

173165
ValidNnsFunction::AddSnsWasm => {
174-
let transformed_payload =
175-
add_proposal_id_to_add_wasm_request(&self.payload, proposal_id)?;
176-
177-
Ok(transformed_payload)
166+
let decoded_payload = decode_add_wasm_request(&self.payload)?;
167+
let encoded_payload = encode_add_wasm_request(decoded_payload, proposal_id)?;
168+
Ok(encoded_payload)
178169
}
179170

180171
// Most NNS functions don't require any transformation of the payload, and for new NNS
@@ -203,35 +194,54 @@ fn get_request_for_bitcoin_set_config(payload: &[u8]) -> Result<CallCanisterRequ
203194
})
204195
}
205196

206-
impl From<CallCanisterRequest> for SelfDescribingValue {
207-
fn from(request: CallCanisterRequest) -> Self {
208-
let CallCanisterRequest {
209-
canister_id,
210-
method_name,
211-
payload,
212-
} = request;
213-
ValueBuilder::new()
214-
.add_field("canister_id", canister_id)
215-
.add_field("method_name", method_name)
216-
.add_field("payload", payload)
217-
.build()
218-
}
197+
fn decode_subnet_rental_request(payload: &[u8]) -> Result<SubnetRentalRequest, GovernanceError> {
198+
Decode!([decoder_config()]; payload, SubnetRentalRequest).map_err(|_| {
199+
GovernanceError::new_with_message(
200+
ErrorType::InvalidProposal,
201+
"Unable to decode SubnetRentalRequest proposal: {e}",
202+
)
203+
})
219204
}
220205

221-
fn add_proposal_id_to_add_wasm_request(
222-
payload: &[u8],
206+
fn encode_subnet_rental_proposal_payload(
207+
subnet_rental_request: SubnetRentalRequest,
223208
proposal_id: u64,
209+
proposal_creation_time_seconds: u64,
224210
) -> Result<Vec<u8>, GovernanceError> {
225-
let add_wasm_request = match Decode!([decoder_config()]; payload, AddWasmRequest) {
226-
Ok(add_wasm_request) => add_wasm_request,
227-
Err(e) => {
228-
return Err(GovernanceError::new_with_message(
229-
ErrorType::InvalidProposal,
230-
format!("Payload must be a valid AddWasmRequest. Error: {e}"),
231-
));
232-
}
233-
};
211+
let SubnetRentalRequest {
212+
user,
213+
rental_condition_id,
214+
} = subnet_rental_request;
215+
216+
let encoded_payload = Encode!(&SubnetRentalProposalPayload {
217+
user,
218+
rental_condition_id,
219+
proposal_id,
220+
proposal_creation_time_seconds,
221+
})
222+
.map_err(|_| {
223+
GovernanceError::new_with_message(
224+
ErrorType::InvalidProposal,
225+
"Unable to encode SubnetRentalProposalPayload proposal: {e}",
226+
)
227+
})?;
228+
229+
Ok(encoded_payload)
230+
}
234231

232+
fn decode_add_wasm_request(payload: &[u8]) -> Result<AddWasmRequest, GovernanceError> {
233+
Decode!([decoder_config()]; payload, AddWasmRequest).map_err(|_| {
234+
GovernanceError::new_with_message(
235+
ErrorType::InvalidProposal,
236+
"Unable to decode AddWasmRequest proposal: {e}",
237+
)
238+
})
239+
}
240+
241+
fn encode_add_wasm_request(
242+
add_wasm_request: AddWasmRequest,
243+
proposal_id: u64,
244+
) -> Result<Vec<u8>, GovernanceError> {
235245
let wasm = add_wasm_request
236246
.wasm
237247
.ok_or(GovernanceError::new_with_message(
@@ -247,9 +257,74 @@ fn add_proposal_id_to_add_wasm_request(
247257
..add_wasm_request
248258
};
249259

250-
let payload = Encode!(&add_wasm_request).unwrap();
260+
Encode!(&add_wasm_request).map_err(|_| {
261+
GovernanceError::new_with_message(
262+
ErrorType::InvalidProposal,
263+
"Unable to encode AddWasmRequest proposal: {e}",
264+
)
265+
})
266+
}
251267

252-
Ok(payload)
268+
impl From<CallCanisterRequest> for SelfDescribingValue {
269+
fn from(request: CallCanisterRequest) -> Self {
270+
let CallCanisterRequest {
271+
canister_id,
272+
method_name,
273+
payload,
274+
} = request;
275+
ValueBuilder::new()
276+
.add_field("canister_id", canister_id)
277+
.add_field("method_name", method_name)
278+
.add_field("payload", payload)
279+
.build()
280+
}
281+
}
282+
283+
impl From<SubnetRentalRequest> for SelfDescribingValue {
284+
fn from(request: SubnetRentalRequest) -> Self {
285+
let SubnetRentalRequest {
286+
user,
287+
rental_condition_id,
288+
} = request;
289+
ValueBuilder::new()
290+
.add_field("user", user)
291+
.add_field("rental_condition_id", format!("{:?}", rental_condition_id))
292+
.build()
293+
}
294+
}
295+
296+
impl From<AddWasmRequest> for SelfDescribingValue {
297+
fn from(payload: AddWasmRequest) -> Self {
298+
let AddWasmRequest {
299+
wasm,
300+
hash,
301+
skip_update_latest_version,
302+
} = payload;
303+
304+
ValueBuilder::new()
305+
.add_field("wasm", wasm)
306+
.add_field("hash", hash)
307+
.add_field("skip_update_latest_version", skip_update_latest_version)
308+
.build()
309+
}
310+
}
311+
312+
impl From<SnsWasm> for SelfDescribingValue {
313+
fn from(wasm: SnsWasm) -> Self {
314+
let SnsWasm {
315+
wasm,
316+
canister_type,
317+
proposal_id: _,
318+
} = wasm;
319+
320+
let wasm_hash = Sha256::hash(&wasm).to_vec();
321+
let canister_type = SelfDescribingProstEnum::<SnsCanisterType>::new(canister_type);
322+
323+
ValueBuilder::new()
324+
.add_field("wasm_hash", wasm_hash)
325+
.add_field("canister_type", canister_type)
326+
.build()
327+
}
253328
}
254329

255330
#[derive(Debug, Clone, PartialEq)]

rs/nns/governance/src/proposals/execute_nns_function_tests.rs

Lines changed: 82 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,14 +7,16 @@ use crate::{
77
test_utils::{ExpectedCallCanisterMethodCallArguments, MockEnvironment},
88
};
99
use candid::{Decode, Encode};
10-
use ic_base_types::CanisterId;
10+
use ic_base_types::{CanisterId, PrincipalId};
11+
use ic_crypto_sha2::Sha256;
1112
use ic_management_canister_types_private::{CanisterMetadataRequest, CanisterMetadataResponse};
1213
use ic_nns_constants::{BITCOIN_MAINNET_CANISTER_ID, CYCLES_MINTING_CANISTER_ID};
1314
use ic_nns_governance_api::{
1415
SelfDescribingValue,
1516
bitcoin::{BitcoinNetwork, BitcoinSetConfigProposal},
17+
subnet_rental::{RentalConditionId, SubnetRentalRequest},
1618
};
17-
use ic_sns_wasm::pb::v1::{AddWasmRequest, SnsWasm};
19+
use ic_sns_wasm::pb::v1::{AddWasmRequest, SnsCanisterType, SnsWasm};
1820
use maplit::hashmap;
1921
use std::sync::Arc;
2022

@@ -271,6 +273,84 @@ async fn test_to_self_describing_bitcoin_set_config() {
271273
);
272274
}
273275

276+
#[tokio::test]
277+
async fn test_to_self_describing_subnet_rental_request() {
278+
let user = PrincipalId::new_user_test_id(123);
279+
let rental_condition_id = RentalConditionId::App13CH;
280+
281+
let arg = SubnetRentalRequest {
282+
user,
283+
rental_condition_id,
284+
};
285+
let payload = Encode!(&arg).unwrap();
286+
287+
let execute_nns_function = ValidExecuteNnsFunction {
288+
nns_function: ValidNnsFunction::SubnetRentalRequest,
289+
payload,
290+
};
291+
292+
// No canister_metadata call expected - SubnetRentalRequest uses static conversion.
293+
let env = Arc::new(MockEnvironment::new(vec![], 0));
294+
295+
let proposal_action = ValidProposalAction::ExecuteNnsFunction(execute_nns_function);
296+
let result = proposal_action.to_self_describing(env).await.unwrap();
297+
298+
assert_eq!(result.type_name, "Subnet Rental Request");
299+
assert!(
300+
result
301+
.type_description
302+
.contains("A proposal to rent a subnet on the Internet Computer")
303+
);
304+
assert_eq!(
305+
SelfDescribingValue::from(result.value.unwrap()),
306+
SelfDescribingValue::Map(hashmap! {
307+
"user".to_string() => SelfDescribingValue::Text(user.to_string()),
308+
"rental_condition_id".to_string() => SelfDescribingValue::Text("App13CH".to_string()),
309+
})
310+
);
311+
}
312+
313+
#[tokio::test]
314+
async fn test_to_self_describing_add_sns_wasm() {
315+
let wasm_bytes = vec![0, 0x61, 0x73, 0x6D, 1, 0, 0, 0];
316+
let canister_type = SnsCanisterType::Root as i32;
317+
let hash = vec![1, 2, 3, 4];
318+
319+
let arg = AddWasmRequest {
320+
wasm: Some(SnsWasm {
321+
wasm: wasm_bytes.clone(),
322+
canister_type,
323+
proposal_id: None, // Will be set by the NNS function
324+
}),
325+
hash: hash.clone(),
326+
skip_update_latest_version: Some(false),
327+
};
328+
let payload = Encode!(&arg).unwrap();
329+
330+
let execute_nns_function = ValidExecuteNnsFunction {
331+
nns_function: ValidNnsFunction::AddSnsWasm,
332+
payload,
333+
};
334+
335+
// No canister_metadata call expected - AddSnsWasm uses static conversion.
336+
let env = Arc::new(MockEnvironment::new(vec![], 0));
337+
338+
let proposal_action = ValidProposalAction::ExecuteNnsFunction(execute_nns_function);
339+
let result = proposal_action.to_self_describing(env).await.unwrap();
340+
341+
assert_eq!(
342+
SelfDescribingValue::from(result.value.unwrap()),
343+
SelfDescribingValue::Map(hashmap! {
344+
"wasm".to_string() => SelfDescribingValue::Map(hashmap! {
345+
"wasm_hash".to_string() => SelfDescribingValue::from(Sha256::hash(&wasm_bytes).to_vec()),
346+
"canister_type".to_string() => SelfDescribingValue::from("Root"),
347+
}),
348+
"hash".to_string() => SelfDescribingValue::from(hash),
349+
"skip_update_latest_version".to_string() => SelfDescribingValue::from(false),
350+
})
351+
);
352+
}
353+
274354
#[test]
275355
fn test_re_encode_payload_to_target_canister_sets_proposal_id_for_add_wasm() {
276356
let proposal_id = 42;

0 commit comments

Comments
 (0)