1use anyhow::{Context, Result};
2use solana_client::rpc_client::RpcClient;
3use solana_commitment_config::CommitmentConfig;
4use solana_sdk::instruction::Instruction as SolanaInstruction;
5use solana_sdk::{
6 pubkey::Pubkey,
7 signature::{Signature, Signer},
8 transaction::Transaction,
9};
10
11use solify_common::types::{IdlData as CommonIdlData, TestMetadata as CommonTestMetadata};
12use solify_common::ArgumentType as C;
13use types::ArgumentType as T;
14use std::str::FromStr;
15
16#[path = "clients/rust/src/generated/mod.rs"]
17pub mod generated;
18
19pub use generated::programs::SOLIFY_ID;
20pub use generated::{accounts, errors, instructions, types};
21
22pub struct SolifyClient {
23 rpc: RpcClient,
24 commitment: CommitmentConfig,
25}
26
27impl SolifyClient {
28 pub fn new(rpc_url: impl AsRef<str>) -> Result<Self> {
29 Self::new_with_commitment(rpc_url, CommitmentConfig::confirmed())
30 }
31
32 pub fn new_with_commitment(
33 rpc_url: impl AsRef<str>,
34 commitment: CommitmentConfig,
35 ) -> Result<Self> {
36 let rpc = RpcClient::new_with_commitment(rpc_url.as_ref().to_string(), commitment);
37 Ok(Self { rpc, commitment })
38 }
39
40 pub fn from_rpc_client(rpc: RpcClient, commitment: CommitmentConfig) -> Self {
41 Self { rpc, commitment }
42 }
43
44 pub fn rpc(&self) -> &RpcClient {
45 &self.rpc
46 }
47
48 pub fn commitment(&self) -> CommitmentConfig {
49 self.commitment
50 }
51
52 pub fn store_idl_data<S: Signer>(
53 &self,
54 authority: &S,
55 program_id: Pubkey,
56 idl_data: &CommonIdlData,
57 ) -> Result<Signature> {
58 let generated_idl = convert_idl_data(idl_data)?;
59 let (idl_storage, _) = derive_idl_storage_address(&program_id, &authority.pubkey());
60
61 let accounts = instructions::StoreIdlData {
62 idl_storage,
63 authority: authority.pubkey(),
64 system_program: system_program_id(),
65 };
66 let args = instructions::StoreIdlDataInstructionArgs {
67 idl_data: generated_idl,
68 program_id,
69 };
70 let instruction = accounts.instruction(args);
71
72 self.send_instruction(authority, &[instruction])
73 }
74
75 pub fn update_idl_data<S: Signer>(
76 &self,
77 authority: &S,
78 program_id: Pubkey,
79 idl_data: &CommonIdlData,
80 ) -> Result<Signature> {
81 let generated_idl = convert_idl_data(idl_data)?;
82 let (idl_storage, _) = derive_idl_storage_address(&program_id, &authority.pubkey());
83
84 let accounts = instructions::UpdateIdlData {
85 idl_storage,
86 authority: authority.pubkey(),
87 system_program: system_program_id(),
88 };
89 let args = instructions::UpdateIdlDataInstructionArgs {
90 idl_data: generated_idl,
91 program_id,
92 };
93 let instruction = accounts.instruction(args);
94
95 self.send_instruction(authority, &[instruction])
96 }
97
98 pub fn generate_metadata<S: Signer>(
99 &self,
100 authority: &S,
101 program_id: Pubkey,
102 execution_order: Vec<String>,
103 paraphrase: &str,
104 program_name: impl Into<String>,
105 ) -> Result<Signature> {
106 let (idl_storage, _) = derive_idl_storage_address(&program_id, &authority.pubkey());
107 let (test_metadata_config, _) =
108 derive_test_metadata_config_address(&program_id, &authority.pubkey(), paraphrase);
109
110 let accounts = instructions::GenerateMetadata {
111 test_metadata_config,
112 idl_storage,
113 authority: authority.pubkey(),
114 system_program: system_program_id(),
115 };
116 let args = instructions::GenerateMetadataInstructionArgs {
117 execution_order,
118 program_id,
119 program_name: program_name.into(),
120 paraphrase: paraphrase.to_string(),
121 };
122 let instruction = accounts.instruction(args);
123
124 self.send_instruction(authority, &[instruction])
125 }
126
127
128
129 pub fn fetch_idl_storage(
130 &self,
131 authority: Pubkey,
132 program_id: Pubkey,
133 ) -> Result<Option<IdlStorageAccount>> {
134 let (address, _) = derive_idl_storage_address(&program_id, &authority);
135 let response = self
136 .rpc
137 .get_account_with_commitment(&address, self.commitment)
138 .context("Failed to fetch IDL storage account")?;
139
140 if let Some(account) = response.value {
141 let decoded = accounts::idl_storage::IdlStorage::from_bytes(&account.data)
142 .context("Failed to decode IDL storage account data")?;
143 let idl_data = convert_idl_data_back(&decoded.idl_data);
144
145 Ok(Some(IdlStorageAccount {
146 address,
147 authority: decoded.authority,
148 program_id: decoded.program_id,
149 idl_data,
150 timestamp: decoded.timestamp,
151 }))
152 } else {
153 Ok(None)
154 }
155 }
156
157 pub fn fetch_test_metadata(
158 &self,
159 authority: Pubkey,
160 program_id: Pubkey,
161 paraphrase: &str,
162 ) -> Result<Option<TestMetadataAccount>> {
163 let (address, _) = derive_test_metadata_config_address(&program_id, &authority, ¶phrase);
164 let response = self
165 .rpc
166 .get_account_with_commitment(&address, self.commitment)
167 .context("Failed to fetch test metadata account")?;
168
169 if let Some(account) = response.value {
170
171 let decoded = accounts::test_metadata_config::TestMetadataConfig::from_bytes(&account.data)
172 .with_context(|| {
173 format!(
174 "Failed to decode TestMetadataConfig due to serialization format mismatch. \
175 On-chain uses Anchor's fixed-size strings (max_len), client uses Borsh variable-size. \
176 This happens because the generated client code doesn't respect Anchor's max_len format. \
177 Account data length: {} bytes. \
178 SOLUTION: Regenerate the client code with proper Anchor support, or remove max_len from on-chain types.",
179 account.data.len()
180 )
181 })?;
182 let test_metadata = convert_test_metadata_back(&decoded.test_metadata)?;
183
184 Ok(Some(TestMetadataAccount {
185 address,
186 authority: decoded.authority,
187 program_id: decoded.program_id,
188 program_name: decoded.program_name,
189 test_metadata,
190 timestamp: decoded.timestamp,
191 }))
192 } else {
193 Ok(None)
194 }
195 }
196
197 fn send_instruction<S: Signer>(
198 &self,
199 authority: &S,
200 instructions: &[SolanaInstruction],
201 ) -> Result<Signature> {
202 let recent_blockhash = self
203 .rpc
204 .get_latest_blockhash()
205 .context("Failed to fetch latest blockhash")?;
206
207 let transaction = Transaction::new_signed_with_payer(
208 instructions,
209 Some(&authority.pubkey()),
210 &[authority],
211 recent_blockhash,
212 );
213
214 let simulation_result = self.rpc.simulate_transaction(&transaction);
216 if let Ok(simulation) = simulation_result {
217 if let Some(err) = simulation.value.err {
218 return Err(anyhow::anyhow!(
219 "Transaction simulation failed: {:?}. Logs: {:?}",
220 err,
221 simulation.value.logs
222 ));
223 }
224 }
225
226 self.rpc
228 .send_and_confirm_transaction_with_spinner_and_commitment(
229 &transaction,
230 self.commitment,
231 )
232 .map_err(|e| {
233 anyhow::anyhow!(
234 "Failed to send Solify transaction: {}. \
235 This could be due to: insufficient funds, network issues, \
236 or program execution errors. Check your wallet balance and RPC connection.",
237 e
238 )
239 })
240 }
241}
242
243pub fn derive_idl_storage_address(program_id: &Pubkey, authority: &Pubkey) -> (Pubkey, u8) {
244 Pubkey::find_program_address(
245 &[b"idl_storage", program_id.as_ref(), authority.as_ref()],
246 &generated::SOLIFY_ID,
247 )
248}
249
250pub fn derive_test_metadata_config_address(
251 program_id: &Pubkey,
252 authority: &Pubkey,
253 paraphrase: &str,
254) -> (Pubkey, u8) {
255 Pubkey::find_program_address(
256 &[b"tests_metadata", program_id.as_ref(), authority.as_ref(), paraphrase.as_bytes()],
257 &generated::SOLIFY_ID,
258 )
259}
260
261#[derive(Debug, Clone)]
262pub struct IdlStorageAccount {
263 pub address: Pubkey,
264 pub authority: Pubkey,
265 pub program_id: Pubkey,
266 pub idl_data: CommonIdlData,
267 pub timestamp: i64,
268}
269
270
271#[derive(Debug, Clone)]
272pub struct TestMetadataAccount {
273 pub address: Pubkey,
274 pub authority: Pubkey,
275 pub program_id: Pubkey,
276 pub program_name: String,
277 pub test_metadata: CommonTestMetadata,
278 pub timestamp: i64,
279}
280
281
282pub fn convert_idl_data(common: &solify_common::IdlData) -> Result<types::IdlData> {
283 Ok(types::IdlData {
284 name: common.name.clone(),
285 version: common.version.clone(),
286 instructions: common
287 .instructions
288 .iter()
289 .map(convert_idl_instruction)
290 .collect::<Result<Vec<_>>>()?,
291 accounts: common
292 .accounts
293 .iter()
294 .map(convert_idl_account)
295 .collect::<Result<Vec<_>>>()?,
296 types: common
297 .types
298 .iter()
299 .map(convert_idl_typedef)
300 .collect::<Result<Vec<_>>>()?,
301 errors: common
302 .errors
303 .iter()
304 .map(convert_idl_error)
305 .collect::<Result<Vec<_>>>()?,
306 constants: common
307 .constants
308 .iter()
309 .map(convert_idl_constant)
310 .collect::<Result<Vec<_>>>()?,
311 events: common
312 .events
313 .iter()
314 .map(convert_idl_event)
315 .collect::<Result<Vec<_>>>()?,
316 })
317}
318
319fn convert_idl_instruction(src: &solify_common::IdlInstruction) -> Result<types::IdlInstruction> {
320 Ok(types::IdlInstruction {
321 name: src.name.clone(),
322 accounts: src
323 .accounts
324 .iter()
325 .map(convert_idl_account_item)
326 .collect::<Result<Vec<_>>>()?,
327 args: src
328 .args
329 .iter()
330 .map(convert_idl_field)
331 .collect::<Result<Vec<_>>>()?,
332 docs: src.docs.clone(),
333 })
334}
335
336fn convert_idl_account_item(src: &solify_common::IdlAccountItem) -> Result<types::IdlAccountItem> {
337 Ok(types::IdlAccountItem {
338 name: src.name.clone(),
339 is_mut: src.is_mut,
340 is_signer: src.is_signer,
341 is_optional: src.is_optional,
342 docs: src.docs.clone(),
343 pda: match &src.pda {
344 Some(p) => Some(convert_idl_pda(p)?),
345 None => None,
346 },
347 })
348}
349
350fn convert_idl_pda(src: &solify_common::IdlPda) -> Result<types::IdlPda> {
351 Ok(types::IdlPda {
352 seeds: src
353 .seeds
354 .iter()
355 .map(convert_idl_seed)
356 .collect::<Result<Vec<_>>>()?,
357 program: if src.program.is_empty() {
359 None
360 } else {
361 Some(src.program.clone())
362 },
363 })
364}
365
366fn convert_idl_seed(src: &solify_common::IdlSeed) -> Result<types::IdlSeed> {
367 Ok(types::IdlSeed {
368 kind: src.kind.clone(),
369 path: src.path.clone(),
370 value: src.value.clone(),
371 })
372}
373
374fn convert_idl_account(src: &solify_common::IdlAccount) -> Result<types::IdlAccount> {
375 Ok(types::IdlAccount {
376 name: src.name.clone(),
377 fields: src
378 .fields
379 .iter()
380 .map(convert_idl_field)
381 .collect::<Result<Vec<_>>>()?,
382 })
383}
384
385fn convert_idl_field(src: &solify_common::IdlField) -> Result<types::IdlField> {
386 Ok(types::IdlField {
387 name: src.name.clone(),
388 field_type: src.field_type.clone(),
389 })
390}
391
392fn convert_idl_typedef(src: &solify_common::IdlTypeDef) -> Result<types::IdlTypeDef> {
393 Ok(types::IdlTypeDef {
394 name: src.name.clone(),
395 kind: src.kind.clone(),
396 fields: src.fields.clone(),
397 })
398}
399
400fn convert_idl_error(src: &solify_common::IdlError) -> Result<types::IdlError> {
401 Ok(types::IdlError {
402 code: src.code,
403 name: src.name.clone(),
404 msg: src.msg.clone(),
405 })
406}
407
408fn convert_idl_constant(src: &solify_common::IdlConstant) -> Result<types::IdlConstant> {
409 Ok(types::IdlConstant {
410 name: src.name.clone(),
411 constant_type: src.constant_type.clone(),
412 value: src.value.clone(),
413 })
414}
415
416fn convert_idl_event(src: &solify_common::IdlEvent) -> Result<types::IdlEvent> {
417 Ok(types::IdlEvent {
418 name: src.name.clone(),
419 discriminator: src.discriminator.clone(),
420 fields: src
421 .fields
422 .iter()
423 .map(convert_idl_field)
424 .collect::<Result<Vec<_>>>()?,
425 })
426}
427
428fn convert_idl_data_back(generated: &types::IdlData) -> CommonIdlData {
430 CommonIdlData {
431 name: generated.name.clone(),
432 version: generated.version.clone(),
433 instructions: generated.instructions.iter().map(convert_idl_instruction_back).collect(),
434 accounts: generated.accounts.iter().map(convert_idl_account_back).collect(),
435 types: generated.types.iter().map(convert_idl_type_def_back).collect(),
436 errors: generated.errors.iter().map(convert_idl_error_back).collect(),
437 constants: generated.constants.iter().map(convert_idl_constant_back).collect(),
438 events: generated.events.iter().map(convert_idl_event_back).collect(),
439 }
440}
441
442fn convert_idl_instruction_back(generated: &types::IdlInstruction) -> solify_common::IdlInstruction {
443 solify_common::IdlInstruction {
444 name: generated.name.clone(),
445 accounts: generated.accounts.iter().map(convert_idl_account_item_back).collect(),
446 args: generated.args.iter().map(convert_idl_field_back).collect(),
447 docs: generated.docs.clone(),
448 }
449}
450
451fn convert_idl_account_item_back(generated: &types::IdlAccountItem) -> solify_common::IdlAccountItem {
452 solify_common::IdlAccountItem {
453 name: generated.name.clone(),
454 is_mut: generated.is_mut,
455 is_signer: generated.is_signer,
456 is_optional: generated.is_optional,
457 docs: generated.docs.clone(),
458 pda: generated.pda.as_ref().map(convert_idl_pda_back),
459 }
460}
461
462fn convert_idl_pda_back(generated: &types::IdlPda) -> solify_common::IdlPda {
463 solify_common::IdlPda {
464 seeds: generated.seeds.iter().map(convert_idl_seed_back).collect(),
465 program: generated.program.clone().unwrap_or_default(),
466 }
467}
468
469fn convert_idl_seed_back(generated: &types::IdlSeed) -> solify_common::IdlSeed {
470 solify_common::IdlSeed {
471 kind: generated.kind.clone(),
472 path: generated.path.clone(),
473 value: generated.value.clone(),
474 }
475}
476
477fn convert_idl_field_back(generated: &types::IdlField) -> solify_common::IdlField {
478 solify_common::IdlField {
479 name: generated.name.clone(),
480 field_type: generated.field_type.clone(),
481 }
482}
483
484fn convert_idl_account_back(generated: &types::IdlAccount) -> solify_common::IdlAccount {
485 solify_common::IdlAccount {
486 name: generated.name.clone(),
487 fields: generated.fields.iter().map(convert_idl_field_back).collect(),
488 }
489}
490
491fn convert_idl_type_def_back(generated: &types::IdlTypeDef) -> solify_common::IdlTypeDef {
492 solify_common::IdlTypeDef {
493 name: generated.name.clone(),
494 kind: generated.kind.clone(),
495 fields: generated.fields.clone(),
496 }
497}
498
499fn convert_idl_error_back(generated: &types::IdlError) -> solify_common::IdlError {
500 solify_common::IdlError {
501 code: generated.code,
502 name: generated.name.clone(),
503 msg: generated.msg.clone(),
504 }
505}
506
507fn convert_idl_constant_back(generated: &types::IdlConstant) -> solify_common::IdlConstant {
508 solify_common::IdlConstant {
509 name: generated.name.clone(),
510 constant_type: generated.constant_type.clone(),
511 value: generated.value.clone(),
512 }
513}
514
515fn convert_idl_event_back(generated: &types::IdlEvent) -> solify_common::IdlEvent {
516 solify_common::IdlEvent {
517 name: generated.name.clone(),
518 discriminator: generated.discriminator.clone(),
519 fields: generated.fields.iter().map(convert_idl_field_back).collect(),
520 }
521}
522
523pub fn convert_test_metadata(src: &CommonTestMetadata) -> Result<types::TestMetadata> {
526 Ok(types::TestMetadata {
527 instruction_order: src.instruction_order.clone(),
528 account_dependencies: src
529 .account_dependencies
530 .iter()
531 .map(convert_account_dependency)
532 .collect::<Result<Vec<_>>>()?,
533 pda_init_sequence: src
534 .pda_init_sequence
535 .iter()
536 .map(convert_pda_init)
537 .collect::<Result<Vec<_>>>()?,
538 setup_requirements: src
539 .setup_requirements
540 .iter()
541 .map(convert_setup_requirement)
542 .collect::<Result<Vec<_>>>()?,
543 test_cases: src
544 .test_cases
545 .iter()
546 .map(convert_instruction_test_cases)
547 .collect::<Result<Vec<_>>>()?,
548 })
549}
550
551fn convert_account_dependency(src: &solify_common::AccountDependency) -> Result<types::AccountDependency> {
552 Ok(types::AccountDependency {
553 account_name: src.account_name.clone(),
554 depends_on: src.depends_on.clone(),
555 is_pda: src.is_pda,
556 is_signer: src.is_signer,
557 is_mut: src.is_mut,
558 must_be_initialized: src.must_be_initialized,
559 initialization_order: src.initialization_order,
560 })
561}
562
563fn convert_pda_init(src: &solify_common::PdaInit) -> Result<types::PdaInit> {
564 let program_id = Pubkey::from_str(&src.program_id)
566 .with_context(|| format!("Failed to parse program id '{}'", src.program_id))?;
567
568 Ok(types::PdaInit {
569 account_name: src.account_name.clone(),
570 seeds: src
571 .seeds
572 .iter()
573 .map(convert_seed_component)
574 .collect::<Result<Vec<_>>>()?,
575 program_id,
576 space: src.space,
577 })
578}
579
580fn convert_seed_component(src: &solify_common::SeedComponent) -> Result<types::SeedComponent> {
581 Ok(types::SeedComponent {
582 seed_type: match src.seed_type {
583 solify_common::SeedType::Static => types::SeedType::Static,
584 solify_common::SeedType::AccountKey => types::SeedType::AccountKey,
585 solify_common::SeedType::Argument => types::SeedType::Argument,
586 },
587 value: src.value.clone(),
588 })
589}
590
591fn convert_setup_requirement(src: &solify_common::SetupRequirement) -> Result<types::SetupRequirement> {
592 Ok(types::SetupRequirement {
593 requirement_type: match src.requirement_type {
594 solify_common::SetupType::CreateKeypair => types::SetupType::CreateKeypair,
595 solify_common::SetupType::FundAccount => types::SetupType::FundAccount,
596 solify_common::SetupType::InitializePda => types::SetupType::InitializePda,
597 solify_common::SetupType::MintTokens => types::SetupType::MintTokens,
598 solify_common::SetupType::CreateAta => types::SetupType::CreateAta,
599 },
600 description: src.description.clone(),
601 dependencies: src.dependencies.clone(),
602 })
603}
604
605fn convert_instruction_test_cases(src: &solify_common::InstructionTestCases) -> Result<types::InstructionTestCases> {
606 Ok(types::InstructionTestCases {
607 instruction_name: src.instruction_name.clone(),
608 arguments: src
609 .arguments
610 .iter()
611 .map(convert_argument_info)
612 .collect::<Result<Vec<_>>>()?,
613 positive_cases: src
614 .positive_cases
615 .iter()
616 .map(convert_test_case)
617 .collect::<Result<Vec<_>>>()?,
618 negative_cases: src
619 .negative_cases
620 .iter()
621 .map(convert_test_case)
622 .collect::<Result<Vec<_>>>()?,
623 })
624}
625
626fn convert_argument_info(src: &solify_common::ArgumentInfo) -> Result<types::ArgumentInfo> {
627 Ok(types::ArgumentInfo {
628 name: src.name.clone(),
629 arg_type: convert_argument_type(&src.arg_type)?,
630 constraints: src.constraints.clone().into_iter().map(convert_constraint).collect::<Result<Vec<_>>>()?,
631 is_optional: src.is_optional,
632 })
633}
634
635fn convert_argument_type(src: &solify_common::ArgumentType) -> Result<types::ArgumentType> {
636 fn arg_type_name(t: &C) -> Result<String> {
638 match t {
639 C::U8 => Ok("u8".to_string()),
640 C::U16 => Ok("u16".to_string()),
641 C::U32 => Ok("u32".to_string()),
642 C::U64 => Ok("u64".to_string()),
643 C::U128 => Ok("u128".to_string()),
644 C::I8 => Ok("i8".to_string()),
645 C::I16 => Ok("i16".to_string()),
646 C::I32 => Ok("i32".to_string()),
647 C::I64 => Ok("i64".to_string()),
648 C::I128 => Ok("i128".to_string()),
649 C::Bool => Ok("bool".to_string()),
650 C::String { .. } => Ok("String".to_string()),
651 C::Pubkey => Ok("Pubkey".to_string()),
652 C::Vec { inner_type, .. } => {
653 let inner = arg_type_name(inner_type)?;
655 Ok(format!("Vec<{}>", inner))
656 }
657 C::Option { inner_type } => {
658 let inner = arg_type_name(inner_type)?;
659 Ok(format!("Option<{}>", inner))
660 }
661 C::Struct { name } => Ok(name.clone()),
662 C::Enum { name, .. } => Ok(name.clone()),
663 }
664 }
665
666 let out = match src {
667 C::U8 => T::U8,
668 C::U16 => T::U16,
669 C::U32 => T::U32,
670 C::U64 => T::U64,
671 C::U128 => T::U128,
672 C::I8 => T::I8,
673 C::I16 => T::I16,
674 C::I32 => T::I32,
675 C::I64 => T::I64,
676 C::I128 => T::I128,
677 C::Bool => T::Bool,
678 C::String { max_length } => T::String { max_length: *max_length },
679 C::Pubkey => T::Pubkey,
680 C::Vec { inner_type, max_length } => {
681 let inner_name = arg_type_name(inner_type)?;
683 T::VecType {
684 inner_type_name: inner_name,
685 max_length: *max_length,
686 }
687 }
688 C::Option { inner_type } => {
689 let inner_name = arg_type_name(inner_type)?;
690 T::OptionType {
691 inner_type_name: inner_name,
692 }
693 }
694 C::Struct { name } => {
695 anyhow::bail!("Struct types are not supported in generated ArgumentType: {}", name);
697 }
698 C::Enum { name, .. } => {
699 anyhow::bail!("Enum types are not supported in generated ArgumentType: {}", name);
701 }
702 };
703
704 Ok(out)
705}
706
707fn convert_constraint(src: solify_common::ArgumentConstraint) -> Result<types::ArgumentConstraint> {
708 use solify_common::ArgumentConstraint as C;
709 use types::ArgumentConstraint as T;
710
711 let out = match src {
712 C::Min { value } => T::Min { value },
713 C::Max { value } => T::Max { value },
714 C::Range { min, max } => T::Range { min, max },
715 C::NonZero => T::NonZero,
716 C::MaxLength { value } => T::MaxLength { value },
717 C::MinLength { value } => T::MinLength { value },
718 C::Custom { .. } => {
719 return Err(anyhow::anyhow!("Custom constraint mapping not implemented"))
722 }
723 };
724
725 Ok(out)
726}
727
728fn convert_test_case(src: &solify_common::TestCase) -> Result<types::TestCase> {
729 Ok(types::TestCase {
730 test_type: match src.test_type {
731 solify_common::TestCaseType::Positive => types::TestCaseType::Positive,
732 solify_common::TestCaseType::NegativeBoundary => types::TestCaseType::NegativeBoundary,
733 solify_common::TestCaseType::NegativeType => types::TestCaseType::NegativeType,
734 solify_common::TestCaseType::NegativeConstraint => types::TestCaseType::NegativeConstraint,
735 solify_common::TestCaseType::NegativeNull => types::TestCaseType::NegativeNull,
736 solify_common::TestCaseType::NegativeOverflow => types::TestCaseType::NegativeOverflow,
737 },
738 description: src.description.clone(),
739 argument_values: src
740 .argument_values
741 .iter()
742 .map(convert_test_argument_value)
743 .collect::<Result<Vec<_>>>()?,
744 expected_outcome: convert_expected_outcome(&src.expected_outcome)?,
745 })
746}
747
748fn convert_test_argument_value(src: &solify_common::TestArgumentValue) -> Result<types::TestArgumentValue> {
749 Ok(types::TestArgumentValue {
750 argument_name: src.argument_name.clone(),
751 value_type: match &src.value_type {
752 solify_common::TestValueType::Valid { description } => types::TestValueType::Valid { description: description.clone() },
753 solify_common::TestValueType::Invalid { description, reason } => types::TestValueType::Invalid { description: description.clone(), reason: reason.clone() },
754 },
755 })
756}
757
758fn convert_expected_outcome(src: &solify_common::ExpectedOutcome) -> Result<types::ExpectedOutcome> {
759 Ok(match src {
760 solify_common::ExpectedOutcome::Success { state_changes } => types::ExpectedOutcome::Success { state_changes: state_changes.clone() },
761 solify_common::ExpectedOutcome::Failure { error_code, error_message } => types::ExpectedOutcome::Failure { error_code: error_code.clone(), error_message: error_message.clone() },
762 })
763}
764
765fn convert_test_metadata_back(src: &types::TestMetadata) -> Result<CommonTestMetadata> {
767 Ok(CommonTestMetadata {
768 instruction_order: src.instruction_order.clone(),
769 account_dependencies: src
770 .account_dependencies
771 .iter()
772 .map(convert_account_dependency_back)
773 .collect(),
774 pda_init_sequence: src
775 .pda_init_sequence
776 .iter()
777 .map(convert_pda_init_back)
778 .collect::<Result<Vec<_>>>()?,
779 setup_requirements: src
780 .setup_requirements
781 .iter()
782 .map(convert_setup_requirement_back)
783 .collect(),
784 test_cases: src
785 .test_cases
786 .iter()
787 .map(convert_instruction_test_cases_back)
788 .collect::<Result<Vec<_>>>()?,
789 })
790}
791
792fn convert_account_dependency_back(src: &types::AccountDependency) -> solify_common::AccountDependency {
793 solify_common::AccountDependency {
794 account_name: src.account_name.clone(),
795 depends_on: src.depends_on.clone(),
796 is_pda: src.is_pda,
797 is_signer: src.is_signer,
798 is_mut: src.is_mut,
799 must_be_initialized: src.must_be_initialized,
800 initialization_order: src.initialization_order,
801 }
802}
803
804fn convert_pda_init_back(src: &types::PdaInit) -> Result<solify_common::PdaInit> {
805 Ok(solify_common::PdaInit {
806 account_name: src.account_name.clone(),
807 seeds: src
808 .seeds
809 .iter()
810 .map(convert_seed_component_back)
811 .collect(),
812 program_id: src.program_id.to_string(),
813 space: src.space,
814 })
815}
816
817fn convert_seed_component_back(src: &types::SeedComponent) -> solify_common::SeedComponent {
818 solify_common::SeedComponent {
819 seed_type: match src.seed_type {
820 types::SeedType::Static => solify_common::SeedType::Static,
821 types::SeedType::AccountKey => solify_common::SeedType::AccountKey,
822 types::SeedType::Argument => solify_common::SeedType::Argument,
823 },
824 value: src.value.clone(),
825 }
826}
827
828fn convert_setup_requirement_back(src: &types::SetupRequirement) -> solify_common::SetupRequirement {
829 solify_common::SetupRequirement {
830 requirement_type: match src.requirement_type {
831 types::SetupType::CreateKeypair => solify_common::SetupType::CreateKeypair,
832 types::SetupType::FundAccount => solify_common::SetupType::FundAccount,
833 types::SetupType::InitializePda => solify_common::SetupType::InitializePda,
834 types::SetupType::MintTokens => solify_common::SetupType::MintTokens,
835 types::SetupType::CreateAta => solify_common::SetupType::CreateAta,
836 },
837 description: src.description.clone(),
838 dependencies: src.dependencies.clone(),
839 }
840}
841
842fn convert_instruction_test_cases_back(src: &types::InstructionTestCases) -> Result<solify_common::InstructionTestCases> {
843 Ok(solify_common::InstructionTestCases {
844 instruction_name: src.instruction_name.clone(),
845 arguments: src
846 .arguments
847 .iter()
848 .map(convert_argument_info_back)
849 .collect::<Result<Vec<_>>>()?,
850 positive_cases: src
851 .positive_cases
852 .iter()
853 .map(convert_test_case_back)
854 .collect::<Result<Vec<_>>>()?,
855 negative_cases: src
856 .negative_cases
857 .iter()
858 .map(convert_test_case_back)
859 .collect::<Result<Vec<_>>>()?,
860 })
861}
862
863fn convert_argument_info_back(src: &types::ArgumentInfo) -> Result<solify_common::ArgumentInfo> {
864 Ok(solify_common::ArgumentInfo {
865 name: src.name.clone(),
866 arg_type: convert_argument_type_back(&src.arg_type)?,
867 constraints: src.constraints.iter().map(convert_constraint_back).collect(),
868 is_optional: src.is_optional,
869 })
870}
871
872fn convert_argument_type_back(src: &types::ArgumentType) -> Result<solify_common::ArgumentType> {
873 use types::ArgumentType as T;
874 use solify_common::ArgumentType as C;
875
876 let out = match src {
877 T::U8 => C::U8,
878 T::U16 => C::U16,
879 T::U32 => C::U32,
880 T::U64 => C::U64,
881 T::U128 => C::U128,
882 T::I8 => C::I8,
883 T::I16 => C::I16,
884 T::I32 => C::I32,
885 T::I64 => C::I64,
886 T::I128 => C::I128,
887 T::Bool => C::Bool,
888 T::String { max_length } => C::String { max_length: *max_length },
889 T::Pubkey => C::Pubkey,
890 T::VecType { inner_type_name, max_length } => {
891 let inner_type = parse_argument_type_from_name(inner_type_name)?;
893 C::Vec {
894 inner_type: Box::new(inner_type),
895 max_length: *max_length,
896 }
897 }
898 T::OptionType { inner_type_name } => {
899 let inner_type = parse_argument_type_from_name(inner_type_name)?;
900 C::Option {
901 inner_type: Box::new(inner_type),
902 }
903 }
904 };
905 Ok(out)
906}
907
908fn parse_argument_type_from_name(name: &str) -> Result<solify_common::ArgumentType> {
909 match name {
911 "u8" => Ok(solify_common::ArgumentType::U8),
912 "u16" => Ok(solify_common::ArgumentType::U16),
913 "u32" => Ok(solify_common::ArgumentType::U32),
914 "u64" => Ok(solify_common::ArgumentType::U64),
915 "u128" => Ok(solify_common::ArgumentType::U128),
916 "i8" => Ok(solify_common::ArgumentType::I8),
917 "i16" => Ok(solify_common::ArgumentType::I16),
918 "i32" => Ok(solify_common::ArgumentType::I32),
919 "i64" => Ok(solify_common::ArgumentType::I64),
920 "i128" => Ok(solify_common::ArgumentType::I128),
921 "bool" => Ok(solify_common::ArgumentType::Bool),
922 "String" => Ok(solify_common::ArgumentType::String { max_length: None }),
923 "Pubkey" => Ok(solify_common::ArgumentType::Pubkey),
924 _ => {
925 if let Some(inner) = name.strip_prefix("Vec<").and_then(|s| s.strip_suffix('>')) {
927 let inner_type = parse_argument_type_from_name(inner)?;
928 Ok(solify_common::ArgumentType::Vec {
929 inner_type: Box::new(inner_type),
930 max_length: None,
931 })
932 } else if let Some(inner) = name.strip_prefix("Option<").and_then(|s| s.strip_suffix('>')) {
933 let inner_type = parse_argument_type_from_name(inner)?;
934 Ok(solify_common::ArgumentType::Option {
935 inner_type: Box::new(inner_type),
936 })
937 } else {
938 Ok(solify_common::ArgumentType::Struct { name: name.to_string() })
940 }
941 }
942 }
943}
944
945fn convert_constraint_back(src: &types::ArgumentConstraint) -> solify_common::ArgumentConstraint {
946 use types::ArgumentConstraint as T;
947 use solify_common::ArgumentConstraint as C;
948
949 match src {
950 T::Min { value } => C::Min { value: *value },
951 T::Max { value } => C::Max { value: *value },
952 T::Range { min, max } => C::Range { min: *min, max: *max },
953 T::NonZero => C::NonZero,
954 T::MaxLength { value } => C::MaxLength { value: *value },
955 T::MinLength { value } => C::MinLength { value: *value }
956 }
957}
958
959fn convert_test_case_back(src: &types::TestCase) -> Result<solify_common::TestCase> {
960 Ok(solify_common::TestCase {
961 test_type: match src.test_type {
962 types::TestCaseType::Positive => solify_common::TestCaseType::Positive,
963 types::TestCaseType::NegativeBoundary => solify_common::TestCaseType::NegativeBoundary,
964 types::TestCaseType::NegativeType => solify_common::TestCaseType::NegativeType,
965 types::TestCaseType::NegativeConstraint => solify_common::TestCaseType::NegativeConstraint,
966 types::TestCaseType::NegativeNull => solify_common::TestCaseType::NegativeNull,
967 types::TestCaseType::NegativeOverflow => solify_common::TestCaseType::NegativeOverflow,
968 },
969 description: src.description.clone(),
970 argument_values: src.argument_values.iter().map(convert_test_argument_value_back).collect(),
971 expected_outcome: convert_expected_outcome_back(&src.expected_outcome),
972 })
973}
974
975fn convert_test_argument_value_back(src: &types::TestArgumentValue) -> solify_common::TestArgumentValue {
976 solify_common::TestArgumentValue {
977 argument_name: src.argument_name.clone(),
978 value_type: match &src.value_type {
979 types::TestValueType::Valid { description } => {
980 solify_common::TestValueType::Valid { description: description.clone() }
981 }
982 types::TestValueType::Invalid { description, reason } => {
983 solify_common::TestValueType::Invalid {
984 description: description.clone(),
985 reason: reason.clone(),
986 }
987 }
988 },
989 }
990}
991
992fn convert_expected_outcome_back(src: &types::ExpectedOutcome) -> solify_common::ExpectedOutcome {
993 match src {
994 types::ExpectedOutcome::Success { state_changes } => {
995 solify_common::ExpectedOutcome::Success {
996 state_changes: state_changes.clone(),
997 }
998 }
999 types::ExpectedOutcome::Failure { error_code, error_message } => {
1000 solify_common::ExpectedOutcome::Failure {
1001 error_code: error_code.clone(),
1002 error_message: error_message.clone(),
1003 }
1004 }
1005 }
1006}
1007
1008#[inline]
1009fn system_program_id() -> Pubkey {
1010 Pubkey::from_str("11111111111111111111111111111111").unwrap()
1011}