diff --git a/README.md b/README.md index b565ec7..b69454a 100644 --- a/README.md +++ b/README.md @@ -29,6 +29,7 @@ cargo clean && cargo cosmwasm-to-quint 4. In order to run the generated tests, you'll need to add some dependencies: ```bash +cargo add cw-multi-test@0.16.2 --dev cargo add itf@0.2.4 --dev cargo add anyhow@1.0.83 --dev cargo add num-bigint@0.4.4 --dev diff --git a/src/lib.rs b/src/lib.rs index 4e2838d..92de14e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -191,15 +191,17 @@ fn translate_items(tcx: TyCtxt, crate_name: &str, items: Vec<&rustc_hir::Item>) .expect("Unable to write file"); // write tests to file - std::fs::create_dir_all("tests").expect("Unable to create directory"); std::fs::write(format!("tests/mbt_{}.rs", crate_name), tests).expect("Unable to write file"); } // This is the main entry point for the plugin. It prints the generated quint code to STDOUT. fn cosmwasm_to_quint(tcx: TyCtxt, _args: &CosmwasmToQuintPluginArgs) { - // create generated directory + // create directories for the output files (if they don't already exist) std::fs::create_dir_all("quint/lib").expect("Unable to create directory"); + std::fs::create_dir_all("tests").expect("Unable to create directory"); + // Read quint lib files. `include_str!` makes it so they are read at + // compilation time, and therefore get embeded in the cosmwasm-to-quint executable let bank = include_str!("./quint-lib-files/bank.qnt"); let basic_spells = include_str!("./quint-lib-files/basicSpells.qnt"); let bounded_uint = include_str!("./quint-lib-files/BoundedUInt.qnt"); @@ -207,6 +209,7 @@ fn cosmwasm_to_quint(tcx: TyCtxt, _args: &CosmwasmToQuintPluginArgs) { let cw_utils = include_str!("./quint-lib-files/cw_utils.qnt"); let messaging = include_str!("./quint-lib-files/messaging.qnt"); + // Write the lib files in runtime std::fs::write("quint/lib/bank.qnt", bank).expect("Unable to write file"); std::fs::write("quint/lib/basicSpells.qnt", basic_spells).expect("Unable to write file"); std::fs::write("quint/lib/BoundedUInt.qnt", bounded_uint).expect("Unable to write file"); diff --git a/src/test_generation/actions.rs b/src/test_generation/actions.rs index 6d4b07e..584d25a 100644 --- a/src/test_generation/actions.rs +++ b/src/test_generation/actions.rs @@ -1,146 +1,108 @@ use crate::boilerplate::init_value_for_type; +use crate::test_generation::boilerplate::*; use crate::types::{fallback_constructor, Context}; use itertools::Itertools; pub fn translate_actions(ctx: Context) -> String { - let msgs = ctx - .message_type_for_action + ctx.message_type_for_action .iter() // Sort items by name so that the generated code is deterministic .sorted_by(|a, b| a.0.cmp(b.0)) .map(|(action, ty)| { if action == "instantiate" { - let msg_struct = ctx - .structs - .get("InstantiateMsg") - .cloned() - .unwrap_or_default(); - let msg_fields = msg_struct - .iter() - .map(|f| { - let body = init_value_for_type(&ctx, f.ty.clone()); - - format!("{}: {}", f.name, body) - }) - .collect_vec(); - let msg = format!("InstantiateMsg {{ {} }}", msg_fields.join(", ")); + let msg = generate_instantiate_msg(ctx.clone()); return translate_init(msg); } - if action == "execute" || action == "instantiate" || action == "reply" { - return "".to_string(); - } - let constructor = ctx - .constructors - .get(ty.as_str()) - .cloned() - .unwrap_or_else(|| fallback_constructor(ty)); - - let nondet_picks = constructor - .fields - .iter() - .map(|f| { - let body = type_conversion( - format!("nondet_picks.message_{}.clone().unwrap()", f.name), - f.ty.clone(), - ); - - format!(" let message_{} = {};", f.name, body) - }) - .collect_vec(); - - let fields = constructor - .fields - .iter() - .map(|f| format!("{}: message_{}", f.name, f.name)) - .collect_vec() - .join(", "); - let msg = format!("{} {{ {} }}", constructor.name.replace('_', "::"), fields); - - translate_action(action, msg, nondet_picks.clone()) - }); - - msgs.clone().join("\n") + + let msg = generate_message(ctx.clone(), ty.clone()); + translate_action(action, msg) + }) + .join("\n") } -fn translate_action(action: &str, msg: String, nondet_picks: Vec) -> String { - let header = format!( +fn translate_action(action_name: &str, msg: String) -> String { + format!( " - \"{}_action\" => {{ - let sender = Addr::unchecked(sender.unwrap()); - let funds = funds_from_trace(amount, denom); + \"{action_name}_action\" => {{ + {EXTRACT_SENDER_AND_FUNDS} +{msg} + {PRINT_MESSAGE_FIELDS} + {EXECUTE_CONTRACT} + {COMPARE_RESULT} + }} +" + ) +} + +fn translate_init(msg: String) -> String { + format!( + " + \"q::init\" => {{ + println!(\"Initializing contract.\"); -", - action - ); + {EXTRACT_SENDER_AND_FUNDS} + {msg} + {PRINT_MESSAGE_FIELDS} + {INITIALIZE_CONTRACT} + {MINT_TOKENS} + }} - let footer = " - println!(\"Message: {:?}\", msg); - println!(\"Sender: {:?}\", sender); - println!(\"Funds: {:?}\", funds); +" + ) +} - let res = app.execute_contract( - sender, - test_state.contract_addr.clone(), - &msg, - &funds, - ); +fn generate_instantiate_msg(ctx: Context) -> String { + let msg_struct = ctx + .structs + .get("InstantiateMsg") + .cloned() + .unwrap_or_default(); - compare_result(s.value.result.clone(), res) - } + let msg_fields = msg_struct + .iter() + .map(|f| { + let body = init_value_for_type(&ctx, f.ty.clone()); -"; + format!("{}: {}", f.name, body) + }) + .collect_vec(); - format!( - "{}{}\n let msg = {};{}", - header, - nondet_picks.clone().join("\n"), - msg, - footer, - ) + format!("let msg = InstantiateMsg {{ {} }};", msg_fields.join(", ")) } -fn translate_init(msg: String) -> String { - let header = " - \"q::init\" => { - println!(\"Initializing contract.\"); +fn generate_message(ctx: Context, ty: String) -> String { + let constructor = ctx + .constructors + .get(ty.as_str()) + .cloned() + .unwrap_or_else(|| fallback_constructor(ty.as_ref())); - let sender = Addr::unchecked(sender.unwrap()); - let funds = funds_from_trace(amount, denom); + let nondet_picks = constructor + .fields + .iter() + .map(|f| { + let body = type_conversion( + format!("nondet_picks.message_{}.clone().unwrap()", f.name), + f.ty.clone(), + ); + + format!(" let message_{} = {};", f.name, body) + }) + .collect_vec() + .join("\n"); + + let fields = constructor + .fields + .iter() + .map(|f| format!("{}: message_{}", f.name, f.name)) + .collect_vec() + .join(", "); -" - .to_string(); - - let footer = " - println!(\"Message: {:?}\", msg); - println!(\"Sender: {:?}\", sender); - println!(\"Funds: {:?}\", funds); - - test_state.contract_addr = app.instantiate_contract( - code_id, - sender, - &msg, - &funds, - \"test\", - None, - ).unwrap(); - - for (addr, coins) in s.value.bank.clone().iter() { - for (denom, amount) in coins.iter() { - app = mint_tokens( - app, - addr.clone(), - denom.to_string(), - Uint128::new(amount.to_u128().unwrap()), - ); - } - } - } - -"; + let message_type = constructor.name.replace('_', "::"); format!( - "{}\n let msg = {};{}", - header, msg, footer, + "{nondet_picks} + let msg = {message_type} {{ {fields} }};" ) } diff --git a/src/test_generation/boilerplate.rs b/src/test_generation/boilerplate.rs index 01156fd..7199d48 100644 --- a/src/test_generation/boilerplate.rs +++ b/src/test_generation/boilerplate.rs @@ -144,3 +144,44 @@ pub const TEST_FOOTER: &str = " } } "; + +pub const PRINT_MESSAGE_FIELDS: &str = "println!(\"Message: {:?}\", msg); + println!(\"Sender: {:?}\", sender); + println!(\"Funds: {:?}\", funds); +"; + +pub const EXECUTE_CONTRACT: &str = "let res = app.execute_contract( + sender, + test_state.contract_addr.clone(), + &msg, + &funds, + ); +"; + +pub const COMPARE_RESULT: &str = "compare_result(s.value.result.clone(), res)"; + +pub const EXTRACT_SENDER_AND_FUNDS: &str = "let sender = Addr::unchecked(sender.unwrap()); + let funds = funds_from_trace(amount, denom); +"; + +pub const INITIALIZE_CONTRACT: &str = "test_state.contract_addr = app.instantiate_contract( + code_id, + sender, + &msg, + &funds, + \"test\", + None, + ).unwrap(); +"; + +pub const MINT_TOKENS: &str = "for (addr, coins) in s.value.bank.clone().iter() { + for (denom, amount) in coins.iter() { + app = mint_tokens( + app, + addr.clone(), + denom.to_string(), + Uint128::new(amount.to_u128().unwrap()), + ); + } + } +"; diff --git a/tests/snapshots/integration_tests__ctf01.snap b/tests/snapshots/integration_tests__ctf01.snap index 995f47d..da85522 100644 --- a/tests/snapshots/integration_tests__ctf01.snap +++ b/tests/snapshots/integration_tests__ctf01.snap @@ -361,14 +361,12 @@ pub mod tests { } - "q::init" => { println!("Initializing contract."); let sender = Addr::unchecked(sender.unwrap()); let funds = funds_from_trace(amount, denom); - let msg = InstantiateMsg { count: 0 }; println!("Message: {:?}", msg); println!("Sender: {:?}", sender); @@ -393,7 +391,8 @@ pub mod tests { ); } } - } + + } @@ -417,7 +416,6 @@ pub mod tests { compare_result(s.value.result.clone(), res) } - _ => panic!("Invalid action taken"), } compare_state(&test_state, &app, &(s.value.clone())); diff --git a/tests/snapshots/integration_tests__ctf02.snap b/tests/snapshots/integration_tests__ctf02.snap index b3cbbf5..8cee177 100644 --- a/tests/snapshots/integration_tests__ctf02.snap +++ b/tests/snapshots/integration_tests__ctf02.snap @@ -386,14 +386,12 @@ pub mod tests { } - "q::init" => { println!("Initializing contract."); let sender = Addr::unchecked(sender.unwrap()); let funds = funds_from_trace(amount, denom); - let msg = InstantiateMsg { }; println!("Message: {:?}", msg); println!("Sender: {:?}", sender); @@ -418,7 +416,8 @@ pub mod tests { ); } } - } + + } @@ -443,7 +442,6 @@ pub mod tests { } - "unstake_action" => { let sender = Addr::unchecked(sender.unwrap()); let funds = funds_from_trace(amount, denom); @@ -465,7 +463,6 @@ pub mod tests { } - "withdraw_action" => { let sender = Addr::unchecked(sender.unwrap()); let funds = funds_from_trace(amount, denom); @@ -486,7 +483,6 @@ pub mod tests { compare_result(s.value.result.clone(), res) } - _ => panic!("Invalid action taken"), } compare_state(&test_state, &app, &(s.value.clone())); diff --git a/tests/snapshots/integration_tests__ctf03.snap b/tests/snapshots/integration_tests__ctf03.snap index 09fb9d7..5af7afc 100644 --- a/tests/snapshots/integration_tests__ctf03.snap +++ b/tests/snapshots/integration_tests__ctf03.snap @@ -630,14 +630,12 @@ pub mod tests { } - "q::init" => { println!("Initializing contract."); let sender = Addr::unchecked(sender.unwrap()); let funds = funds_from_trace(amount, denom); - let msg = InstantiateMsg { }; println!("Message: {:?}", msg); println!("Sender: {:?}", sender); @@ -662,7 +660,8 @@ pub mod tests { ); } } - } + + } @@ -687,7 +686,6 @@ pub mod tests { } - "settle_loan_action" => { let sender = Addr::unchecked(sender.unwrap()); let funds = funds_from_trace(amount, denom); @@ -709,7 +707,6 @@ pub mod tests { } - "transfer_owner_action" => { let sender = Addr::unchecked(sender.unwrap()); let funds = funds_from_trace(amount, denom); @@ -731,7 +728,6 @@ pub mod tests { } - "withdraw_funds_action" => { let sender = Addr::unchecked(sender.unwrap()); let funds = funds_from_trace(amount, denom); @@ -752,7 +748,6 @@ pub mod tests { compare_result(s.value.result.clone(), res) } - _ => panic!("Invalid action taken"), } compare_state(&test_state, &app, &(s.value.clone())); @@ -958,14 +953,12 @@ pub mod tests { } - "q::init" => { println!("Initializing contract."); let sender = Addr::unchecked(sender.unwrap()); let funds = funds_from_trace(amount, denom); - let msg = InstantiateMsg { }; println!("Message: {:?}", msg); println!("Sender: {:?}", sender); @@ -990,7 +983,8 @@ pub mod tests { ); } } - } + + } _ => panic!("Invalid action taken"), @@ -1183,7 +1177,6 @@ pub mod tests { let sender = Addr::unchecked(sender.unwrap()); let funds = funds_from_trace(amount, denom); - let msg = InstantiateMsg { }; println!("Message: {:?}", msg); println!("Sender: {:?}", sender); @@ -1208,7 +1201,8 @@ pub mod tests { ); } } - } + + } @@ -1232,7 +1226,6 @@ pub mod tests { compare_result(s.value.result.clone(), res) } - _ => panic!("Invalid action taken"), } compare_state(&test_state, &app, &(s.value.clone())); diff --git a/tests/snapshots/integration_tests__ctf04.snap b/tests/snapshots/integration_tests__ctf04.snap index e73ad7f..109d364 100644 --- a/tests/snapshots/integration_tests__ctf04.snap +++ b/tests/snapshots/integration_tests__ctf04.snap @@ -362,14 +362,12 @@ pub mod tests { } - "q::init" => { println!("Initializing contract."); let sender = Addr::unchecked(sender.unwrap()); let funds = funds_from_trace(amount, denom); - let msg = InstantiateMsg { offset: 0 }; println!("Message: {:?}", msg); println!("Sender: {:?}", sender); @@ -394,7 +392,8 @@ pub mod tests { ); } } - } + + } @@ -418,7 +417,6 @@ pub mod tests { compare_result(s.value.result.clone(), res) } - _ => panic!("Invalid action taken"), } compare_state(&test_state, &app, &(s.value.clone())); diff --git a/tests/snapshots/integration_tests__ctf05.snap b/tests/snapshots/integration_tests__ctf05.snap index ca92e5b..e9e254b 100644 --- a/tests/snapshots/integration_tests__ctf05.snap +++ b/tests/snapshots/integration_tests__ctf05.snap @@ -415,7 +415,6 @@ pub mod tests { } - "deposit_action" => { let sender = Addr::unchecked(sender.unwrap()); let funds = funds_from_trace(amount, denom); @@ -437,7 +436,6 @@ pub mod tests { } - "drop_owner_action" => { let sender = Addr::unchecked(sender.unwrap()); let funds = funds_from_trace(amount, denom); @@ -459,14 +457,12 @@ pub mod tests { } - "q::init" => { println!("Initializing contract."); let sender = Addr::unchecked(sender.unwrap()); let funds = funds_from_trace(amount, denom); - let msg = InstantiateMsg { owner: "" }; println!("Message: {:?}", msg); println!("Sender: {:?}", sender); @@ -491,7 +487,8 @@ pub mod tests { ); } } - } + + } @@ -516,7 +513,6 @@ pub mod tests { } - "propose_owner_action" => { let sender = Addr::unchecked(sender.unwrap()); let funds = funds_from_trace(amount, denom); @@ -538,7 +534,6 @@ pub mod tests { } - "withdraw_action" => { let sender = Addr::unchecked(sender.unwrap()); let funds = funds_from_trace(amount, denom); @@ -559,7 +554,6 @@ pub mod tests { compare_result(s.value.result.clone(), res) } - _ => panic!("Invalid action taken"), } compare_state(&test_state, &app, &(s.value.clone())); diff --git a/tests/snapshots/integration_tests__ctf06.snap b/tests/snapshots/integration_tests__ctf06.snap index ddf3a31..96f6721 100644 --- a/tests/snapshots/integration_tests__ctf06.snap +++ b/tests/snapshots/integration_tests__ctf06.snap @@ -378,7 +378,6 @@ pub mod tests { let sender = Addr::unchecked(sender.unwrap()); let funds = funds_from_trace(amount, denom); - let msg = InstantiateMsg { token: "", owner: "", window: 0 }; println!("Message: {:?}", msg); println!("Sender: {:?}", sender); @@ -403,7 +402,8 @@ pub mod tests { ); } } - } + + } @@ -428,7 +428,6 @@ pub mod tests { } - "propose_action" => { let sender = Addr::unchecked(sender.unwrap()); let funds = funds_from_trace(amount, denom); @@ -450,7 +449,6 @@ pub mod tests { } - "receive_cw20_action" => { let sender = Addr::unchecked(sender.unwrap()); let funds = funds_from_trace(amount, denom); @@ -472,7 +470,6 @@ pub mod tests { } - "resolve_proposal_action" => { let sender = Addr::unchecked(sender.unwrap()); let funds = funds_from_trace(amount, denom); @@ -493,7 +490,6 @@ pub mod tests { compare_result(s.value.result.clone(), res) } - _ => panic!("Invalid action taken"), } compare_state(&test_state, &app, &(s.value.clone())); diff --git a/tests/snapshots/integration_tests__ctf07.snap b/tests/snapshots/integration_tests__ctf07.snap index 1e4711a..e9ac002 100644 --- a/tests/snapshots/integration_tests__ctf07.snap +++ b/tests/snapshots/integration_tests__ctf07.snap @@ -394,14 +394,12 @@ pub mod tests { } - "q::init" => { println!("Initializing contract."); let sender = Addr::unchecked(sender.unwrap()); let funds = funds_from_trace(amount, denom); - let msg = InstantiateMsg { owner: "", threshold: 0 }; println!("Message: {:?}", msg); println!("Sender: {:?}", sender); @@ -426,7 +424,8 @@ pub mod tests { ); } } - } + + } @@ -451,7 +450,6 @@ pub mod tests { } - "update_config_action" => { let sender = Addr::unchecked(sender.unwrap()); let funds = funds_from_trace(amount, denom); @@ -473,7 +471,6 @@ pub mod tests { } - "withdraw_action" => { let sender = Addr::unchecked(sender.unwrap()); let funds = funds_from_trace(amount, denom); @@ -494,7 +491,6 @@ pub mod tests { compare_result(s.value.result.clone(), res) } - _ => panic!("Invalid action taken"), } compare_state(&test_state, &app, &(s.value.clone())); diff --git a/tests/snapshots/integration_tests__ctf08.snap b/tests/snapshots/integration_tests__ctf08.snap index 015ab90..d43a98f 100644 --- a/tests/snapshots/integration_tests__ctf08.snap +++ b/tests/snapshots/integration_tests__ctf08.snap @@ -472,7 +472,6 @@ pub mod tests { } - "exec_buy_action" => { let sender = Addr::unchecked(sender.unwrap()); let funds = funds_from_trace(amount, denom); @@ -494,7 +493,6 @@ pub mod tests { } - "exec_cancel_sale_action" => { let sender = Addr::unchecked(sender.unwrap()); let funds = funds_from_trace(amount, denom); @@ -516,7 +514,6 @@ pub mod tests { } - "exec_cancel_trade_action" => { let sender = Addr::unchecked(sender.unwrap()); let funds = funds_from_trace(amount, denom); @@ -538,7 +535,6 @@ pub mod tests { } - "exec_new_sale_action" => { let sender = Addr::unchecked(sender.unwrap()); let funds = funds_from_trace(amount, denom); @@ -562,7 +558,6 @@ pub mod tests { } - "exec_new_trade_action" => { let sender = Addr::unchecked(sender.unwrap()); let funds = funds_from_trace(amount, denom); @@ -585,14 +580,12 @@ pub mod tests { } - "q::init" => { println!("Initializing contract."); let sender = Addr::unchecked(sender.unwrap()); let funds = funds_from_trace(amount, denom); - let msg = InstantiateMsg { nft_address: "" }; println!("Message: {:?}", msg); println!("Sender: {:?}", sender); @@ -617,7 +610,8 @@ pub mod tests { ); } } - } + + } _ => panic!("Invalid action taken"), diff --git a/tests/snapshots/integration_tests__ctf09.snap b/tests/snapshots/integration_tests__ctf09.snap index 4487302..72a1dbd 100644 --- a/tests/snapshots/integration_tests__ctf09.snap +++ b/tests/snapshots/integration_tests__ctf09.snap @@ -391,7 +391,6 @@ pub mod tests { } - "deposit_action" => { let sender = Addr::unchecked(sender.unwrap()); let funds = funds_from_trace(amount, denom); @@ -413,7 +412,6 @@ pub mod tests { } - "increase_reward_action" => { let sender = Addr::unchecked(sender.unwrap()); let funds = funds_from_trace(amount, denom); @@ -435,14 +433,12 @@ pub mod tests { } - "q::init" => { println!("Initializing contract."); let sender = Addr::unchecked(sender.unwrap()); let funds = funds_from_trace(amount, denom); - let msg = InstantiateMsg { }; println!("Message: {:?}", msg); println!("Sender: {:?}", sender); @@ -467,7 +463,8 @@ pub mod tests { ); } } - } + + } @@ -491,7 +488,6 @@ pub mod tests { compare_result(s.value.result.clone(), res) } - _ => panic!("Invalid action taken"), } compare_state(&test_state, &app, &(s.value.clone())); diff --git a/tests/snapshots/integration_tests__ctf10.snap b/tests/snapshots/integration_tests__ctf10.snap index afaf3a6..89e1bf7 100644 --- a/tests/snapshots/integration_tests__ctf10.snap +++ b/tests/snapshots/integration_tests__ctf10.snap @@ -334,7 +334,6 @@ pub mod tests { let sender = Addr::unchecked(sender.unwrap()); let funds = funds_from_trace(amount, denom); - let msg = InstantiateMsg { cw721_code_id: 0, mint_per_user: 0, whitelisted_users: [] }; println!("Message: {:?}", msg); println!("Sender: {:?}", sender); @@ -359,7 +358,8 @@ pub mod tests { ); } } - } + + } @@ -383,7 +383,6 @@ pub mod tests { compare_result(s.value.result.clone(), res) } - _ => panic!("Invalid action taken"), } compare_state(&test_state, &app, &(s.value.clone()));