From 1d78861c6afc70bb4c987e28599b006ebc1289df Mon Sep 17 00:00:00 2001 From: Joe Monem Date: Wed, 4 Dec 2024 08:00:23 +0200 Subject: [PATCH 01/27] feat: optional config for splitter send --- .../andromeda-splitter/src/contract.rs | 15 +++-- .../finance/andromeda-splitter/src/mock.rs | 14 +++-- .../andromeda-splitter/src/testing/tests.rs | 56 +++++++++++++++++-- ibc-tests/tests/crowdfund.rs | 2 +- packages/andromeda-finance/src/splitter.rs | 2 +- tests-integration/tests/auction_app.rs | 2 +- tests-integration/tests/crowdfund_app.rs | 2 +- tests-integration/tests/kernel.rs | 2 +- tests-integration/tests/kernel_orch.rs | 2 +- tests-integration/tests/marketplace_app.rs | 2 +- tests-integration/tests/splitter.rs | 2 +- 11 files changed, 80 insertions(+), 21 deletions(-) diff --git a/contracts/finance/andromeda-splitter/src/contract.rs b/contracts/finance/andromeda-splitter/src/contract.rs index db3e9c9f5..e3d215732 100644 --- a/contracts/finance/andromeda-splitter/src/contract.rs +++ b/contracts/finance/andromeda-splitter/src/contract.rs @@ -105,7 +105,7 @@ pub fn handle_execute(mut ctx: ExecuteContext, msg: ExecuteMsg) -> Result execute_update_recipients(ctx, recipients), ExecuteMsg::UpdateLock { lock_time } => execute_update_lock(ctx, lock_time), - ExecuteMsg::Send {} => execute_send(ctx), + ExecuteMsg::Send { config } => execute_send(ctx, config), _ => ADOContract::default().execute(ctx, msg), }?; Ok(res @@ -114,7 +114,10 @@ pub fn handle_execute(mut ctx: ExecuteContext, msg: ExecuteMsg) -> Result Result { +fn execute_send( + ctx: ExecuteContext, + config: Option>, +) -> Result { let ExecuteContext { deps, info, .. } = ctx; ensure!( !info.funds.is_empty(), @@ -131,7 +134,11 @@ fn execute_send(ctx: ExecuteContext) -> Result { ); } - let splitter = SPLITTER.load(deps.storage)?; + let splitter = if let Some(config) = config { + config + } else { + SPLITTER.load(deps.storage)?.recipients + }; let mut msgs: Vec = Vec::new(); let mut amp_funds: Vec = Vec::new(); @@ -148,7 +155,7 @@ fn execute_send(ctx: ExecuteContext) -> Result { let mut pkt = AMPPkt::from_ctx(ctx.amp_ctx, ctx.env.contract.address.to_string()); - for recipient_addr in &splitter.recipients { + for recipient_addr in &splitter { let recipient_percent = recipient_addr.percent; let mut vec_coin: Vec = Vec::new(); for (i, coin) in info.funds.clone().iter().enumerate() { diff --git a/contracts/finance/andromeda-splitter/src/mock.rs b/contracts/finance/andromeda-splitter/src/mock.rs index 22ef84e96..fdf96b9b1 100644 --- a/contracts/finance/andromeda-splitter/src/mock.rs +++ b/contracts/finance/andromeda-splitter/src/mock.rs @@ -28,8 +28,14 @@ impl MockSplitter { Self(res.unwrap()) } - pub fn execute_send(&self, app: &mut MockApp, sender: Addr, funds: &[Coin]) -> ExecuteResult { - let msg = mock_splitter_send_msg(); + pub fn execute_send( + &self, + app: &mut MockApp, + sender: Addr, + funds: &[Coin], + config: Option>, + ) -> ExecuteResult { + let msg = mock_splitter_send_msg(config); self.execute(app, &msg, sender, funds) } @@ -54,6 +60,6 @@ pub fn mock_splitter_instantiate_msg( } } -pub fn mock_splitter_send_msg() -> ExecuteMsg { - ExecuteMsg::Send {} +pub fn mock_splitter_send_msg(config: Option>) -> ExecuteMsg { + ExecuteMsg::Send { config } } diff --git a/contracts/finance/andromeda-splitter/src/testing/tests.rs b/contracts/finance/andromeda-splitter/src/testing/tests.rs index aa13c3658..85fd50d55 100644 --- a/contracts/finance/andromeda-splitter/src/testing/tests.rs +++ b/contracts/finance/andromeda-splitter/src/testing/tests.rs @@ -271,8 +271,17 @@ fn test_execute_send() { let recip_address2 = "address2".to_string(); let recip_percent2 = 20; // 20% + let recip_address3 = "address3".to_string(); + let recip_percent3 = 50; // 50% + let recip1 = Recipient::from_string(recip_address1); let recip2 = Recipient::from_string(recip_address2); + let recip3 = Recipient::from_string(recip_address3); + + let config_recipient = vec![AddressPercent { + recipient: recip3.clone(), + percent: Decimal::percent(recip_percent3), + }]; let recipient = vec![ AddressPercent { @@ -284,7 +293,7 @@ fn test_execute_send() { percent: Decimal::percent(recip_percent2), }, ]; - let msg = ExecuteMsg::Send {}; + let msg = ExecuteMsg::Send { config: None }; let amp_msg_1 = recip1 .generate_amp_msg(&deps.as_ref(), Some(vec![Coin::new(1000, "uluna")])) @@ -312,7 +321,7 @@ fn test_execute_send() { SPLITTER.save(deps.as_mut().storage, &splitter).unwrap(); - let res = execute(deps.as_mut(), env, info, msg).unwrap(); + let res = execute(deps.as_mut(), env.clone(), info.clone(), msg).unwrap(); let expected_res = Response::new() .add_submessages(vec![ @@ -329,6 +338,43 @@ fn test_execute_send() { .add_submessage(generate_economics_message(OWNER, "Send")); assert_eq!(res, expected_res); + + // Test send with config + let msg = ExecuteMsg::Send { + config: Some(config_recipient), + }; + let res = execute(deps.as_mut(), env, info, msg).unwrap(); + let amp_msg_1 = recip3 + .generate_amp_msg(&deps.as_ref(), Some(vec![Coin::new(5000, "uluna")])) + .unwrap(); + + let amp_pkt = AMPPkt::new( + MOCK_CONTRACT_ADDR.to_string(), + MOCK_CONTRACT_ADDR.to_string(), + vec![amp_msg_1], + ); + let amp_msg = amp_pkt + .to_sub_msg( + MOCK_KERNEL_CONTRACT, + Some(vec![Coin::new(5000, "uluna")]), + 1, + ) + .unwrap(); + let expected_res = Response::new() + .add_submessages(vec![ + SubMsg::new( + // refunds remainder to sender + CosmosMsg::Bank(BankMsg::Send { + to_address: OWNER.to_string(), + amount: vec![Coin::new(5000, "uluna")], // 10000 * 0.5 remainder + }), + ), + amp_msg, + ]) + .add_attributes(vec![attr("action", "send"), attr("sender", "creator")]) + .add_submessage(generate_economics_message(OWNER, "Send")); + + assert_eq!(res, expected_res); } #[test] @@ -359,7 +405,7 @@ fn test_execute_send_ado_recipient() { percent: Decimal::percent(recip_percent2), }, ]; - let msg = ExecuteMsg::Send {}; + let msg = ExecuteMsg::Send { config: None }; let amp_msg_1 = recip1 .generate_amp_msg(&deps.as_ref(), Some(vec![Coin::new(1000, "uluna")])) @@ -436,7 +482,7 @@ fn test_handle_packet_exit_with_error_true() { "cosmos2contract", vec![AMPMsg::new( recip_address1, - to_json_binary(&ExecuteMsg::Send {}).unwrap(), + to_json_binary(&ExecuteMsg::Send { config: None }).unwrap(), Some(vec![Coin::new(0, "uluna")]), )], ); @@ -514,7 +560,7 @@ fn test_execute_send_error() { percent: Decimal::percent(recip_percent2), }, ]; - let msg = ExecuteMsg::Send {}; + let msg = ExecuteMsg::Send { config: None }; let splitter = Splitter { recipients: recipient, diff --git a/ibc-tests/tests/crowdfund.rs b/ibc-tests/tests/crowdfund.rs index aab75f930..2406198a6 100644 --- a/ibc-tests/tests/crowdfund.rs +++ b/ibc-tests/tests/crowdfund.rs @@ -227,7 +227,7 @@ fn setup( let withdrawal_recipient = Recipient::new( format!("./{}", splitter_component.name), - Some(to_json_binary(&splitter::ExecuteMsg::Send {}).unwrap()), + Some(to_json_binary(&splitter::ExecuteMsg::Send { config: None }).unwrap()), ); let campaign_config = CampaignConfig { diff --git a/packages/andromeda-finance/src/splitter.rs b/packages/andromeda-finance/src/splitter.rs index 8ead2bb6b..58ba202e3 100644 --- a/packages/andromeda-finance/src/splitter.rs +++ b/packages/andromeda-finance/src/splitter.rs @@ -53,7 +53,7 @@ pub enum ExecuteMsg { /// Used to lock/unlock the contract allowing the config to be updated. UpdateLock { lock_time: Expiry }, /// Divides any attached funds to the message amongst the recipients list. - Send {}, + Send { config: Option> }, } #[andr_query] diff --git a/tests-integration/tests/auction_app.rs b/tests-integration/tests/auction_app.rs index b6e76e29f..0f533dea9 100644 --- a/tests-integration/tests/auction_app.rs +++ b/tests-integration/tests/auction_app.rs @@ -338,7 +338,7 @@ fn test_auction_app_recipient() { None, None, None, - Some(Recipient::from_string("./splitter").with_msg(mock_splitter_send_msg())), + Some(Recipient::from_string("./splitter").with_msg(mock_splitter_send_msg(None))), ); cw721 .execute_send_nft( diff --git a/tests-integration/tests/crowdfund_app.rs b/tests-integration/tests/crowdfund_app.rs index 1fcad7920..4caecd510 100644 --- a/tests-integration/tests/crowdfund_app.rs +++ b/tests-integration/tests/crowdfund_app.rs @@ -840,6 +840,6 @@ fn mock_recipient_with_invalid_msg(addr: &str) -> Recipient { fn mock_recipient_with_valid_msg(addr: &str) -> Recipient { Recipient::new( addr, - Some(to_json_binary(&mock_splitter_send_msg()).unwrap()), + Some(to_json_binary(&mock_splitter_send_msg(None)).unwrap()), ) } diff --git a/tests-integration/tests/kernel.rs b/tests-integration/tests/kernel.rs index 0d89d9b43..a51f4c83e 100644 --- a/tests-integration/tests/kernel.rs +++ b/tests-integration/tests/kernel.rs @@ -75,7 +75,7 @@ fn kernel() { &mut router, owner.clone(), splitter.addr(), - mock_splitter_send_msg(), + mock_splitter_send_msg(None), vec![coin(100, "uandr")], None, ) diff --git a/tests-integration/tests/kernel_orch.rs b/tests-integration/tests/kernel_orch.rs index 590cf2006..a241d96c5 100644 --- a/tests-integration/tests/kernel_orch.rs +++ b/tests-integration/tests/kernel_orch.rs @@ -1514,7 +1514,7 @@ fn test_kernel_ibc_funds_and_execute_msg() { "ibc://osmosis/{}", splitter_osmosis.address().unwrap() )), - message: to_json_binary(&SplitterExecuteMsg::Send {}).unwrap(), + message: to_json_binary(&SplitterExecuteMsg::Send { config: None }).unwrap(), funds: vec![Coin { denom: "juno".to_string(), amount: Uint128::new(100), diff --git a/tests-integration/tests/marketplace_app.rs b/tests-integration/tests/marketplace_app.rs index e6dec7b6f..b45354456 100644 --- a/tests-integration/tests/marketplace_app.rs +++ b/tests-integration/tests/marketplace_app.rs @@ -386,7 +386,7 @@ fn test_marketplace_app_recipient() { None, Some( Recipient::from_string(format!("./{}", splitter_component.name)) - .with_msg(mock_splitter_send_msg()), + .with_msg(mock_splitter_send_msg(None)), ), ), ) diff --git a/tests-integration/tests/splitter.rs b/tests-integration/tests/splitter.rs index a94867f28..629b2b4e6 100644 --- a/tests-integration/tests/splitter.rs +++ b/tests-integration/tests/splitter.rs @@ -68,7 +68,7 @@ fn test_splitter() { let token = coin(1000, "uandr"); splitter - .execute_send(&mut router, owner.clone(), &[token]) + .execute_send(&mut router, owner.clone(), &[token], None) .unwrap(); let balance_1 = router.wrap().query_balance(recipient_1, "uandr").unwrap(); From 3b4b8eb577317341fc5095caa8420ba03f92b6a3 Mon Sep 17 00:00:00 2001 From: Joe Monem Date: Wed, 4 Dec 2024 08:21:13 +0200 Subject: [PATCH 02/27] feat: optional config for weighted distribution splitter --- .../src/contract.rs | 17 ++- .../src/testing/tests.rs | 134 +++++++++++++----- .../src/weighted_splitter.rs | 2 +- 3 files changed, 110 insertions(+), 43 deletions(-) diff --git a/contracts/finance/andromeda-weighted-distribution-splitter/src/contract.rs b/contracts/finance/andromeda-weighted-distribution-splitter/src/contract.rs index 83cb1e64a..612f9af11 100644 --- a/contracts/finance/andromeda-weighted-distribution-splitter/src/contract.rs +++ b/contracts/finance/andromeda-weighted-distribution-splitter/src/contract.rs @@ -110,7 +110,7 @@ pub fn handle_execute(mut ctx: ExecuteContext, msg: ExecuteMsg) -> Result execute_remove_recipient(ctx, recipient), ExecuteMsg::UpdateLock { lock_time } => execute_update_lock(ctx, lock_time), - ExecuteMsg::Send {} => execute_send(ctx), + ExecuteMsg::Send { config } => execute_send(ctx, config), _ => ADOContract::default().execute(ctx, msg), } @@ -221,7 +221,10 @@ pub fn execute_add_recipient( Ok(Response::default().add_attributes(vec![attr("action", "added_recipient")])) } -fn execute_send(ctx: ExecuteContext) -> Result { +fn execute_send( + ctx: ExecuteContext, + config: Option>, +) -> Result { let ExecuteContext { deps, info, .. } = ctx; // Amount of coins sent should be at least 1 ensure!( @@ -236,20 +239,24 @@ fn execute_send(ctx: ExecuteContext) -> Result { ContractError::ExceedsMaxAllowedCoins {} ); - let splitter = SPLITTER.load(deps.storage)?; + let splitter = if let Some(config) = config { + config + } else { + SPLITTER.load(deps.storage)?.recipients + }; let mut msgs: Vec = Vec::new(); let mut remainder_funds = info.funds.clone(); let mut total_weight = Uint128::zero(); // Calculate the total weight of all recipients - for recipient_addr in &splitter.recipients { + for recipient_addr in &splitter { let recipient_weight = recipient_addr.weight; total_weight = total_weight.checked_add(recipient_weight)?; } // Each recipient recieves the funds * (the recipient's weight / total weight of all recipients) // The remaining funds go to the sender of the function - for recipient_addr in &splitter.recipients { + for recipient_addr in &splitter { let recipient_weight = recipient_addr.weight; let mut vec_coin: Vec = Vec::new(); for (i, coin) in info.funds.iter().enumerate() { diff --git a/contracts/finance/andromeda-weighted-distribution-splitter/src/testing/tests.rs b/contracts/finance/andromeda-weighted-distribution-splitter/src/testing/tests.rs index 6b235c362..b5f792724 100644 --- a/contracts/finance/andromeda-weighted-distribution-splitter/src/testing/tests.rs +++ b/contracts/finance/andromeda-weighted-distribution-splitter/src/testing/tests.rs @@ -22,6 +22,21 @@ const CONTRACT_VERSION: &str = env!("CARGO_PKG_VERSION"); const MOCK_RECIPIENT1: &str = "recipient1"; const MOCK_RECIPIENT2: &str = "recipient2"; +// fn init(deps: DepsMut) -> Response { +// let mock_recipient: Vec = vec![AddressWeight { +// recipient: Recipient::from_string(String::from("some_address")), +// weight: Uint128::new(100), +// }]; +// let msg = InstantiateMsg { +// owner: Some("OWNER".to_owned()), +// kernel_address: MOCK_KERNEL_CONTRACT.to_string(), +// recipients: mock_recipient, +// lock_time: Some(Expiry::FromNow(Milliseconds(86400000))), +// }; + +// let info = mock_info("owner", &[]); +// instantiate(deps, mock_env(), info, msg).unwrap() +// } #[test] fn test_update_app_contract() { let mut deps = mock_dependencies_custom(&[]); @@ -1507,71 +1522,116 @@ fn test_execute_update_recipients_unauthorized() { // fn test_execute_send() { // let mut deps = mock_dependencies_custom(&[]); // let env = mock_env(); +// let _res: Response = init(deps.as_mut()); -// let owner = "creator"; +// let sender_funds_amount = 10000u128; + +// let info = mock_info("OWNER", &[Coin::new(sender_funds_amount, "uluna")]); // let recip_address1 = "address1".to_string(); -// let recip_weight1 = Uint128::new(10); // Weight of 10 +// let recip_percent1 = 10; // 10% // let recip_address2 = "address2".to_string(); -// let recip_weight2 = Uint128::new(20); // Weight of 20 +// let recip_percent2 = 20; // 20% + +// let recip_address3 = "address3".to_string(); +// let recip_percent3 = 50; // 50% + +// let recip1 = Recipient::from_string(recip_address1); +// let recip2 = Recipient::from_string(recip_address2); +// let recip3 = Recipient::from_string(recip_address3); + +// let config_recipient = vec![AddressWeight { +// recipient: recip3.clone(), +// weight: Uint128::new(recip_percent3), +// }]; // let recipient = vec![ // AddressWeight { -// recipient: Recipient::Addr(recip_address1.clone()), -// weight: recip_weight1, +// recipient: recip1.clone(), +// weight: Uint128::new(recip_percent1), // }, // AddressWeight { -// recipient: Recipient::Addr(recip_address2.clone()), -// weight: recip_weight2, +// recipient: recip2.clone(), +// weight: Uint128::new(recip_percent2), // }, // ]; -// let msg = ExecuteMsg::Send {}; +// let msg = ExecuteMsg::Send { config: None }; + +// let amp_msg_1 = recip1 +// .generate_amp_msg(&deps.as_ref(), Some(vec![Coin::new(1000, "uluna")])) +// .unwrap(); +// let amp_msg_2 = recip2 +// .generate_amp_msg(&deps.as_ref(), Some(vec![Coin::new(2000, "uluna")])) +// .unwrap(); +// let amp_pkt = AMPPkt::new( +// MOCK_CONTRACT_ADDR.to_string(), +// MOCK_CONTRACT_ADDR.to_string(), +// vec![amp_msg_1, amp_msg_2], +// ); +// let amp_msg = amp_pkt +// .to_sub_msg( +// MOCK_KERNEL_CONTRACT, +// Some(vec![Coin::new(1000, "uluna"), Coin::new(2000, "uluna")]), +// 1, +// ) +// .unwrap(); // let splitter = Splitter { // recipients: recipient, // lock: Milliseconds::default(), // }; -// let info = mock_info(owner, &[Coin::new(10000_u128, "uluna")]); -// let deps_mut = deps.as_mut(); -// ADOContract::default() -// .instantiate( -// deps_mut.storage, -// mock_env(), -// deps_mut.api, -// info.clone(), -// BaseInstantiateMsg { -// ado_type: "splitter".to_string(), -// ado_version: CONTRACT_VERSION.to_string(), -// -// kernel_address: MOCK_KERNEL_CONTRACT.to_string(), -// owner: None, -// }, -// ) -// .unwrap(); +// SPLITTER.save(deps.as_mut().storage, &splitter).unwrap(); + +// let res = execute(deps.as_mut(), env.clone(), info.clone(), msg).unwrap(); + +// let expected_res = Response::new() +// .add_submessages(vec![ +// SubMsg::new( +// // refunds remainder to sender +// CosmosMsg::Bank(BankMsg::Send { +// to_address: OWNER.to_string(), +// amount: vec![Coin::new(7000, "uluna")], // 10000 * 0.7 remainder +// }), +// ), +// amp_msg, +// ]) +// .add_attributes(vec![attr("action", "send"), attr("sender", "creator")]); -// SPLITTER.save(deps_mut.storage, &splitter).unwrap(); +// assert_eq!(res, expected_res); -// let res = execute(deps_mut, env, info, msg).unwrap(); +// // Test send with config +// let msg = ExecuteMsg::Send { +// config: Some(config_recipient), +// }; +// let res = execute(deps.as_mut(), env, info, msg).unwrap(); +// let amp_msg_1 = recip3 +// .generate_amp_msg(&deps.as_ref(), Some(vec![Coin::new(5000, "uluna")])) +// .unwrap(); +// let amp_pkt = AMPPkt::new( +// MOCK_CONTRACT_ADDR.to_string(), +// MOCK_CONTRACT_ADDR.to_string(), +// vec![amp_msg_1], +// ); +// let amp_msg = amp_pkt +// .to_sub_msg( +// MOCK_KERNEL_CONTRACT, +// Some(vec![Coin::new(5000, "uluna")]), +// 1, +// ) +// .unwrap(); // let expected_res = Response::new() // .add_submessages(vec![ -// SubMsg::new(CosmosMsg::Bank(BankMsg::Send { -// to_address: recip_address1, -// amount: vec![Coin::new(3333, "uluna")], // 10000 * (10/30) -// })), -// SubMsg::new(CosmosMsg::Bank(BankMsg::Send { -// to_address: recip_address2, -// amount: vec![Coin::new(6666, "uluna")], // 10000 * (20/30) -// })), // SubMsg::new( // // refunds remainder to sender // CosmosMsg::Bank(BankMsg::Send { -// to_address: owner.to_string(), -// amount: vec![Coin::new(1, "uluna")], // 10000 - (3333+6666) remainder +// to_address: OWNER.to_string(), +// amount: vec![Coin::new(5000, "uluna")], // 10000 * 0.5 remainder // }), // ), +// amp_msg, // ]) // .add_attributes(vec![attr("action", "send"), attr("sender", "creator")]); diff --git a/packages/andromeda-finance/src/weighted_splitter.rs b/packages/andromeda-finance/src/weighted_splitter.rs index 06cd2c9ef..40b83c0cf 100644 --- a/packages/andromeda-finance/src/weighted_splitter.rs +++ b/packages/andromeda-finance/src/weighted_splitter.rs @@ -44,7 +44,7 @@ pub enum ExecuteMsg { /// Used to lock/unlock the contract allowing the config to be updated. UpdateLock { lock_time: Expiry }, /// Divides any attached funds to the message amongst the recipients list. - Send {}, + Send { config: Option> }, } #[andr_query] From f94254938bc9afb36bcb806860fac1a171640605 Mon Sep 17 00:00:00 2001 From: Joe Monem Date: Wed, 4 Dec 2024 08:43:55 +0200 Subject: [PATCH 03/27] feat: optional config for set amount splitter --- .../src/contract.rs | 15 ++-- .../andromeda-set-amount-splitter/src/mock.rs | 14 ++-- .../src/testing/tests.rs | 68 +++++++++++++++++-- .../src/set_amount_splitter.rs | 2 +- .../tests/set_amount_splitter.rs | 2 +- 5 files changed, 85 insertions(+), 16 deletions(-) diff --git a/contracts/finance/andromeda-set-amount-splitter/src/contract.rs b/contracts/finance/andromeda-set-amount-splitter/src/contract.rs index 1fe168ab5..bbbeeb874 100644 --- a/contracts/finance/andromeda-set-amount-splitter/src/contract.rs +++ b/contracts/finance/andromeda-set-amount-splitter/src/contract.rs @@ -124,7 +124,7 @@ pub fn handle_execute(mut ctx: ExecuteContext, msg: ExecuteMsg) -> Result execute_update_recipients(ctx, recipients), ExecuteMsg::UpdateLock { lock_time } => execute_update_lock(ctx, lock_time), - ExecuteMsg::Send {} => execute_send(ctx), + ExecuteMsg::Send { config } => execute_send(ctx, config), _ => ADOContract::default().execute(ctx, msg), }?; Ok(res @@ -133,7 +133,10 @@ pub fn handle_execute(mut ctx: ExecuteContext, msg: ExecuteMsg) -> Result Result { +fn execute_send( + ctx: ExecuteContext, + config: Option>, +) -> Result { let ExecuteContext { deps, info, .. } = ctx; ensure!( @@ -159,7 +162,11 @@ fn execute_send(ctx: ExecuteContext) -> Result { denom_set.insert(coin.denom); } - let splitter = SPLITTER.load(deps.storage)?; + let splitter = if let Some(config) = config { + config + } else { + SPLITTER.load(deps.storage)?.recipients + }; let mut msgs: Vec = Vec::new(); let mut amp_funds: Vec = Vec::new(); @@ -171,7 +178,7 @@ fn execute_send(ctx: ExecuteContext) -> Result { let mut remainder_funds = coin.amount; let denom = coin.denom; - for recipient in &splitter.recipients { + for recipient in &splitter { // Find the recipient's corresponding denom for the current iteration of the sent funds let recipient_coin = recipient .coins diff --git a/contracts/finance/andromeda-set-amount-splitter/src/mock.rs b/contracts/finance/andromeda-set-amount-splitter/src/mock.rs index 04b3e2cb1..5073ed77b 100644 --- a/contracts/finance/andromeda-set-amount-splitter/src/mock.rs +++ b/contracts/finance/andromeda-set-amount-splitter/src/mock.rs @@ -29,8 +29,14 @@ impl MockSetAmountSplitter { Self(res.unwrap()) } - pub fn execute_send(&self, app: &mut MockApp, sender: Addr, funds: &[Coin]) -> ExecuteResult { - let msg = mock_set_amount_splitter_send_msg(); + pub fn execute_send( + &self, + app: &mut MockApp, + sender: Addr, + funds: &[Coin], + config: Option>, + ) -> ExecuteResult { + let msg = mock_set_amount_splitter_send_msg(config); self.execute(app, &msg, sender, funds) } @@ -55,6 +61,6 @@ pub fn mock_set_amount_splitter_instantiate_msg( } } -pub fn mock_set_amount_splitter_send_msg() -> ExecuteMsg { - ExecuteMsg::Send {} +pub fn mock_set_amount_splitter_send_msg(config: Option>) -> ExecuteMsg { + ExecuteMsg::Send { config } } diff --git a/contracts/finance/andromeda-set-amount-splitter/src/testing/tests.rs b/contracts/finance/andromeda-set-amount-splitter/src/testing/tests.rs index 7f937a744..9c0bad5c3 100644 --- a/contracts/finance/andromeda-set-amount-splitter/src/testing/tests.rs +++ b/contracts/finance/andromeda-set-amount-splitter/src/testing/tests.rs @@ -176,9 +176,15 @@ fn test_execute_send() { let recip_address2 = "address2".to_string(); + let recip_address3 = "address3".to_string(); + let recip1 = Recipient::from_string(recip_address1); let recip2 = Recipient::from_string(recip_address2); - + let recip3 = Recipient::from_string(recip_address3); + let config_recipient = vec![AddressAmount { + recipient: recip3.clone(), + coins: vec![coin(1_u128, "uandr"), coin(30_u128, "usdc")], + }]; let recipient = vec![ AddressAmount { recipient: recip1.clone(), @@ -189,7 +195,7 @@ fn test_execute_send() { coins: vec![coin(1_u128, "uandr"), coin(20_u128, "usdc")], }, ]; - let msg = ExecuteMsg::Send {}; + let msg = ExecuteMsg::Send { config: None }; let amp_msg_1 = recip1 .generate_amp_msg(&deps.as_ref(), Some(vec![Coin::new(1, "uandr")])) @@ -230,7 +236,7 @@ fn test_execute_send() { SPLITTER.save(deps.as_mut().storage, &splitter).unwrap(); - let res = execute(deps.as_mut(), env, info, msg).unwrap(); + let res = execute(deps.as_mut(), env.clone(), info.clone(), msg).unwrap(); let expected_res = Response::new() .add_submessages(vec![ @@ -247,6 +253,56 @@ fn test_execute_send() { .add_submessage(generate_economics_message(OWNER, "Send")); assert_eq!(res, expected_res); + + // Test with config + let msg = ExecuteMsg::Send { + config: Some(config_recipient), + }; + + let amp_msg_1 = recip3 + .generate_amp_msg(&deps.as_ref(), Some(vec![Coin::new(1, "uandr")])) + .unwrap(); + + let amp_msg_2 = recip3 + .generate_amp_msg(&deps.as_ref(), Some(vec![Coin::new(30, "usdc")])) + .unwrap(); + + let amp_pkt = AMPPkt::new( + MOCK_CONTRACT_ADDR.to_string(), + MOCK_CONTRACT_ADDR.to_string(), + vec![amp_msg_1, amp_msg_2], + ); + let amp_msg = amp_pkt + .to_sub_msg( + MOCK_KERNEL_CONTRACT, + Some(vec![Coin::new(1, "uandr"), Coin::new(30, "usdc")]), + 1, + ) + .unwrap(); + + let expected_res = Response::new() + .add_submessages(vec![ + SubMsg::new( + // refunds remainder to sender + CosmosMsg::Bank(BankMsg::Send { + to_address: OWNER.to_string(), + amount: vec![Coin::new(9999, "uandr")], + }), + ), + SubMsg::new( + // refunds remainder to sender + CosmosMsg::Bank(BankMsg::Send { + to_address: OWNER.to_string(), + amount: vec![Coin::new(20, "usdc")], + }), + ), + amp_msg, + ]) + .add_attributes(vec![attr("action", "send"), attr("sender", "creator")]) + .add_submessage(generate_economics_message(OWNER, "Send")); + let res = execute(deps.as_mut(), env, info, msg).unwrap(); + + assert_eq!(res, expected_res); } #[test] @@ -275,7 +331,7 @@ fn test_execute_send_ado_recipient() { coins: coins(1_u128, "uandr"), }, ]; - let msg = ExecuteMsg::Send {}; + let msg = ExecuteMsg::Send { config: None }; let amp_msg_1 = recip1 .generate_amp_msg(&deps.as_ref(), Some(vec![Coin::new(1, "uandr")])) @@ -349,7 +405,7 @@ fn test_handle_packet_exit_with_error_true() { "cosmos2contract", vec![AMPMsg::new( recip_address1, - to_json_binary(&ExecuteMsg::Send {}).unwrap(), + to_json_binary(&ExecuteMsg::Send { config: None }).unwrap(), Some(vec![Coin::new(0, "uandr")]), )], ); @@ -425,7 +481,7 @@ fn test_execute_send_error() { coins: coins(1_u128, "uandr"), }, ]; - let msg = ExecuteMsg::Send {}; + let msg = ExecuteMsg::Send { config: None }; let splitter = Splitter { recipients: recipient, diff --git a/packages/andromeda-finance/src/set_amount_splitter.rs b/packages/andromeda-finance/src/set_amount_splitter.rs index 8c0e5ab66..b2894c085 100644 --- a/packages/andromeda-finance/src/set_amount_splitter.rs +++ b/packages/andromeda-finance/src/set_amount_splitter.rs @@ -56,7 +56,7 @@ pub enum ExecuteMsg { lock_time: Expiry, }, /// Divides any attached funds to the message amongst the recipients list. - Send {}, + Send { config: Option> }, } #[andr_query] diff --git a/tests-integration/tests/set_amount_splitter.rs b/tests-integration/tests/set_amount_splitter.rs index 0c79d7311..0713a206a 100644 --- a/tests-integration/tests/set_amount_splitter.rs +++ b/tests-integration/tests/set_amount_splitter.rs @@ -71,7 +71,7 @@ fn test_splitter() { let token = coin(1000, "uandr"); splitter - .execute_send(&mut router, owner.clone(), &[token]) + .execute_send(&mut router, owner.clone(), &[token], None) .unwrap(); let balance_1 = router.wrap().query_balance(recipient_1, "uandr").unwrap(); From 15cd9885037bf343bc96e4a71e68b5934e2c3cd2 Mon Sep 17 00:00:00 2001 From: Joe Monem Date: Wed, 4 Dec 2024 09:32:47 +0200 Subject: [PATCH 04/27] chore: version bumps --- Cargo.lock | 6 +++--- contracts/finance/andromeda-set-amount-splitter/Cargo.toml | 2 +- contracts/finance/andromeda-splitter/Cargo.toml | 2 +- .../andromeda-weighted-distribution-splitter/Cargo.toml | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 9501f7311..3bcaf3d92 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -780,7 +780,7 @@ dependencies = [ [[package]] name = "andromeda-set-amount-splitter" -version = "1.0.3-beta" +version = "1.1.0-beta" dependencies = [ "andromeda-app", "andromeda-finance", @@ -816,7 +816,7 @@ dependencies = [ [[package]] name = "andromeda-splitter" -version = "2.1.4" +version = "2.2.0" dependencies = [ "andromeda-app", "andromeda-finance", @@ -1008,7 +1008,7 @@ dependencies = [ [[package]] name = "andromeda-weighted-distribution-splitter" -version = "2.0.3-beta" +version = "2.1.0-beta" dependencies = [ "andromeda-app", "andromeda-finance", diff --git a/contracts/finance/andromeda-set-amount-splitter/Cargo.toml b/contracts/finance/andromeda-set-amount-splitter/Cargo.toml index 5a0b53eca..6004088b1 100644 --- a/contracts/finance/andromeda-set-amount-splitter/Cargo.toml +++ b/contracts/finance/andromeda-set-amount-splitter/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "andromeda-set-amount-splitter" -version = "1.0.3-beta" +version = "1.1.0-beta" edition = "2021" rust-version = "1.75.0" diff --git a/contracts/finance/andromeda-splitter/Cargo.toml b/contracts/finance/andromeda-splitter/Cargo.toml index 90fda4275..7771b156b 100644 --- a/contracts/finance/andromeda-splitter/Cargo.toml +++ b/contracts/finance/andromeda-splitter/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "andromeda-splitter" -version = "2.1.4" +version = "2.2.0" edition = "2021" rust-version = "1.75.0" diff --git a/contracts/finance/andromeda-weighted-distribution-splitter/Cargo.toml b/contracts/finance/andromeda-weighted-distribution-splitter/Cargo.toml index b3dc507d6..8f6967a6e 100644 --- a/contracts/finance/andromeda-weighted-distribution-splitter/Cargo.toml +++ b/contracts/finance/andromeda-weighted-distribution-splitter/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "andromeda-weighted-distribution-splitter" -version = "2.0.3-beta" +version = "2.1.0-beta" edition = "2021" rust-version = "1.75.0" From 54ff73ef71995f2f75b68ae1ac8a947f27ef1495 Mon Sep 17 00:00:00 2001 From: Joe Monem Date: Wed, 4 Dec 2024 09:46:16 +0200 Subject: [PATCH 05/27] test: send for weighted distribution splitter --- .../src/testing/mock_querier.rs | 91 +++++++ .../src/testing/mod.rs | 1 + .../src/testing/tests.rs | 222 ++++++++---------- 3 files changed, 193 insertions(+), 121 deletions(-) create mode 100644 contracts/finance/andromeda-weighted-distribution-splitter/src/testing/mock_querier.rs diff --git a/contracts/finance/andromeda-weighted-distribution-splitter/src/testing/mock_querier.rs b/contracts/finance/andromeda-weighted-distribution-splitter/src/testing/mock_querier.rs new file mode 100644 index 000000000..d7d944bad --- /dev/null +++ b/contracts/finance/andromeda-weighted-distribution-splitter/src/testing/mock_querier.rs @@ -0,0 +1,91 @@ +use andromeda_std::ado_base::InstantiateMsg; +use andromeda_std::ado_contract::ADOContract; +use andromeda_std::testing::mock_querier::MockAndromedaQuerier; +use cosmwasm_std::testing::mock_info; +use cosmwasm_std::QuerierWrapper; +use cosmwasm_std::{ + from_json, + testing::{mock_env, MockApi, MockQuerier, MockStorage, MOCK_CONTRACT_ADDR}, + Coin, OwnedDeps, Querier, QuerierResult, QueryRequest, SystemError, SystemResult, WasmQuery, +}; + +pub use andromeda_std::testing::mock_querier::MOCK_KERNEL_CONTRACT; + +/// Alternative to `cosmwasm_std::testing::mock_dependencies` that allows us to respond to custom queries. +/// +/// Automatically assigns a kernel address as MOCK_KERNEL_CONTRACT. +pub fn mock_dependencies_custom( + contract_balance: &[Coin], +) -> OwnedDeps { + let custom_querier: WasmMockQuerier = + WasmMockQuerier::new(MockQuerier::new(&[(MOCK_CONTRACT_ADDR, contract_balance)])); + let storage = MockStorage::default(); + let mut deps = OwnedDeps { + storage, + api: MockApi::default(), + querier: custom_querier, + custom_query_type: std::marker::PhantomData, + }; + ADOContract::default() + .instantiate( + &mut deps.storage, + mock_env(), + &deps.api, + &QuerierWrapper::new(&deps.querier), + mock_info("sender", &[]), + InstantiateMsg { + ado_type: "splitter".to_string(), + ado_version: "test".to_string(), + + kernel_address: MOCK_KERNEL_CONTRACT.to_string(), + owner: None, + }, + ) + .unwrap(); + deps +} + +pub struct WasmMockQuerier { + pub base: MockQuerier, + pub contract_address: String, + pub tokens_left_to_burn: usize, +} + +impl Querier for WasmMockQuerier { + fn raw_query(&self, bin_request: &[u8]) -> QuerierResult { + // MockQuerier doesn't support Custom, so we ignore it completely here + let request: QueryRequest = match from_json(bin_request) { + Ok(v) => v, + Err(e) => { + return SystemResult::Err(SystemError::InvalidRequest { + error: format!("Parsing query request: {e}"), + request: bin_request.into(), + }) + } + }; + self.handle_query(&request) + } +} + +impl WasmMockQuerier { + pub fn handle_query(&self, request: &QueryRequest) -> QuerierResult { + match &request { + QueryRequest::Wasm(WasmQuery::Smart { + contract_addr, + msg: _, + }) => { + let _ = contract_addr.as_str(); + MockAndromedaQuerier::default().handle_query(&self.base, request) + } + _ => MockAndromedaQuerier::default().handle_query(&self.base, request), + } + } + + pub fn new(base: MockQuerier) -> Self { + WasmMockQuerier { + base, + contract_address: mock_env().contract.address.to_string(), + tokens_left_to_burn: 2, + } + } +} diff --git a/contracts/finance/andromeda-weighted-distribution-splitter/src/testing/mod.rs b/contracts/finance/andromeda-weighted-distribution-splitter/src/testing/mod.rs index 14f00389d..a1e507b68 100644 --- a/contracts/finance/andromeda-weighted-distribution-splitter/src/testing/mod.rs +++ b/contracts/finance/andromeda-weighted-distribution-splitter/src/testing/mod.rs @@ -1 +1,2 @@ +mod mock_querier; mod tests; diff --git a/contracts/finance/andromeda-weighted-distribution-splitter/src/testing/tests.rs b/contracts/finance/andromeda-weighted-distribution-splitter/src/testing/tests.rs index b5f792724..09af1bdeb 100644 --- a/contracts/finance/andromeda-weighted-distribution-splitter/src/testing/tests.rs +++ b/contracts/finance/andromeda-weighted-distribution-splitter/src/testing/tests.rs @@ -5,12 +5,12 @@ use andromeda_std::{ ado_base::InstantiateMsg as BaseInstantiateMsg, ado_contract::ADOContract, amp::recipient::Recipient, error::ContractError, }; -use cosmwasm_std::QuerierWrapper; use cosmwasm_std::{ attr, testing::{mock_env, mock_info}, Response, Uint128, }; +use cosmwasm_std::{BankMsg, Coin, CosmosMsg, DepsMut, QuerierWrapper, SubMsg}; use crate::{ contract::{execute, instantiate}, @@ -21,22 +21,23 @@ use cosmwasm_std::testing::mock_dependencies; const CONTRACT_VERSION: &str = env!("CARGO_PKG_VERSION"); const MOCK_RECIPIENT1: &str = "recipient1"; const MOCK_RECIPIENT2: &str = "recipient2"; +pub const OWNER: &str = "creator"; -// fn init(deps: DepsMut) -> Response { -// let mock_recipient: Vec = vec![AddressWeight { -// recipient: Recipient::from_string(String::from("some_address")), -// weight: Uint128::new(100), -// }]; -// let msg = InstantiateMsg { -// owner: Some("OWNER".to_owned()), -// kernel_address: MOCK_KERNEL_CONTRACT.to_string(), -// recipients: mock_recipient, -// lock_time: Some(Expiry::FromNow(Milliseconds(86400000))), -// }; +fn init(deps: DepsMut) -> Response { + let mock_recipient: Vec = vec![AddressWeight { + recipient: Recipient::from_string(String::from("some_address")), + weight: Uint128::new(100), + }]; + let msg = InstantiateMsg { + owner: Some(OWNER.to_owned()), + kernel_address: MOCK_KERNEL_CONTRACT.to_string(), + recipients: mock_recipient, + lock_time: Some(Expiry::FromNow(Milliseconds(86400000))), + }; -// let info = mock_info("owner", &[]); -// instantiate(deps, mock_env(), info, msg).unwrap() -// } + let info = mock_info(OWNER, &[]); + instantiate(deps, mock_env(), info, msg).unwrap() +} #[test] fn test_update_app_contract() { let mut deps = mock_dependencies_custom(&[]); @@ -1518,125 +1519,104 @@ fn test_execute_update_recipients_unauthorized() { assert_eq!(ContractError::Unauthorized {}, res.unwrap_err()); } -// #[test] -// fn test_execute_send() { -// let mut deps = mock_dependencies_custom(&[]); -// let env = mock_env(); -// let _res: Response = init(deps.as_mut()); - -// let sender_funds_amount = 10000u128; +#[test] +fn test_execute_send() { + let mut deps = mock_dependencies_custom(&[]); + let env = mock_env(); + let _res: Response = init(deps.as_mut()); -// let info = mock_info("OWNER", &[Coin::new(sender_funds_amount, "uluna")]); + let sender_funds_amount = 10000u128; -// let recip_address1 = "address1".to_string(); -// let recip_percent1 = 10; // 10% + let info = mock_info(OWNER, &[Coin::new(sender_funds_amount, "uluna")]); -// let recip_address2 = "address2".to_string(); -// let recip_percent2 = 20; // 20% + let recip_address1 = "address1".to_string(); + let recip_weight1 = 10; // 10% -// let recip_address3 = "address3".to_string(); -// let recip_percent3 = 50; // 50% + let recip_address2 = "address2".to_string(); + let recip_weight2 = 20; // 20% -// let recip1 = Recipient::from_string(recip_address1); -// let recip2 = Recipient::from_string(recip_address2); -// let recip3 = Recipient::from_string(recip_address3); + let recip_address3 = "address3".to_string(); + let recip_weight3 = 50; // 50% -// let config_recipient = vec![AddressWeight { -// recipient: recip3.clone(), -// weight: Uint128::new(recip_percent3), -// }]; + let recip1 = Recipient::from_string(recip_address1.clone()); + let recip2 = Recipient::from_string(recip_address2.clone()); + let recip3 = Recipient::from_string(recip_address3.clone()); -// let recipient = vec![ -// AddressWeight { -// recipient: recip1.clone(), -// weight: Uint128::new(recip_percent1), -// }, -// AddressWeight { -// recipient: recip2.clone(), -// weight: Uint128::new(recip_percent2), -// }, -// ]; -// let msg = ExecuteMsg::Send { config: None }; - -// let amp_msg_1 = recip1 -// .generate_amp_msg(&deps.as_ref(), Some(vec![Coin::new(1000, "uluna")])) -// .unwrap(); -// let amp_msg_2 = recip2 -// .generate_amp_msg(&deps.as_ref(), Some(vec![Coin::new(2000, "uluna")])) -// .unwrap(); -// let amp_pkt = AMPPkt::new( -// MOCK_CONTRACT_ADDR.to_string(), -// MOCK_CONTRACT_ADDR.to_string(), -// vec![amp_msg_1, amp_msg_2], -// ); -// let amp_msg = amp_pkt -// .to_sub_msg( -// MOCK_KERNEL_CONTRACT, -// Some(vec![Coin::new(1000, "uluna"), Coin::new(2000, "uluna")]), -// 1, -// ) -// .unwrap(); + let config_recipient = vec![AddressWeight { + recipient: recip3.clone(), + weight: Uint128::new(recip_weight3), + }]; -// let splitter = Splitter { -// recipients: recipient, -// lock: Milliseconds::default(), -// }; + let recipient = vec![ + AddressWeight { + recipient: recip1.clone(), + weight: Uint128::new(recip_weight1), + }, + AddressWeight { + recipient: recip2.clone(), + weight: Uint128::new(recip_weight2), + }, + ]; + let msg = ExecuteMsg::Send { config: None }; -// SPLITTER.save(deps.as_mut().storage, &splitter).unwrap(); + let splitter = Splitter { + recipients: recipient, + lock: Milliseconds::default(), + }; -// let res = execute(deps.as_mut(), env.clone(), info.clone(), msg).unwrap(); - -// let expected_res = Response::new() -// .add_submessages(vec![ -// SubMsg::new( -// // refunds remainder to sender -// CosmosMsg::Bank(BankMsg::Send { -// to_address: OWNER.to_string(), -// amount: vec![Coin::new(7000, "uluna")], // 10000 * 0.7 remainder -// }), -// ), -// amp_msg, -// ]) -// .add_attributes(vec![attr("action", "send"), attr("sender", "creator")]); + SPLITTER.save(deps.as_mut().storage, &splitter).unwrap(); -// assert_eq!(res, expected_res); + let res = execute(deps.as_mut(), env.clone(), info.clone(), msg).unwrap(); -// // Test send with config -// let msg = ExecuteMsg::Send { -// config: Some(config_recipient), -// }; -// let res = execute(deps.as_mut(), env, info, msg).unwrap(); -// let amp_msg_1 = recip3 -// .generate_amp_msg(&deps.as_ref(), Some(vec![Coin::new(5000, "uluna")])) -// .unwrap(); - -// let amp_pkt = AMPPkt::new( -// MOCK_CONTRACT_ADDR.to_string(), -// MOCK_CONTRACT_ADDR.to_string(), -// vec![amp_msg_1], -// ); -// let amp_msg = amp_pkt -// .to_sub_msg( -// MOCK_KERNEL_CONTRACT, -// Some(vec![Coin::new(5000, "uluna")]), -// 1, -// ) -// .unwrap(); -// let expected_res = Response::new() -// .add_submessages(vec![ -// SubMsg::new( -// // refunds remainder to sender -// CosmosMsg::Bank(BankMsg::Send { -// to_address: OWNER.to_string(), -// amount: vec![Coin::new(5000, "uluna")], // 10000 * 0.5 remainder -// }), -// ), -// amp_msg, -// ]) -// .add_attributes(vec![attr("action", "send"), attr("sender", "creator")]); + let expected_res = Response::new() + .add_submessages(vec![ + SubMsg::new( + // refunds remainder to sender + CosmosMsg::Bank(BankMsg::Send { + to_address: recip_address1.clone(), + amount: vec![Coin::new(3333, "uluna")], + }), + ), + SubMsg::new( + // refunds remainder to sender + CosmosMsg::Bank(BankMsg::Send { + to_address: recip_address2.clone(), + amount: vec![Coin::new(6666, "uluna")], + }), + ), + SubMsg::new( + // refunds remainder to sender + CosmosMsg::Bank(BankMsg::Send { + to_address: OWNER.to_string(), + amount: vec![Coin::new(1, "uluna")], + }), + ), + ]) + .add_attributes(vec![attr("action", "send"), attr("sender", "creator")]); + + assert_eq!(res, expected_res); + + // Test send with config + let msg = ExecuteMsg::Send { + config: Some(config_recipient), + }; + let res = execute(deps.as_mut(), env, info, msg).unwrap(); -// assert_eq!(res, expected_res); -// } + let expected_res = Response::new() + .add_submessages(vec![ + SubMsg::new( + // refunds remainder to sender + CosmosMsg::Bank(BankMsg::Send { + to_address: recip_address3, + amount: vec![Coin::new(10000, "uluna")], + }), + ), + // amp_msg, + ]) + .add_attributes(vec![attr("action", "send"), attr("sender", "creator")]); + + assert_eq!(res, expected_res); +} // #[test] // fn test_query_splitter() { From f393e9d0db1511f0e7c6fa89abe9cadbba895430 Mon Sep 17 00:00:00 2001 From: Joe Monem Date: Wed, 4 Dec 2024 15:29:06 +0200 Subject: [PATCH 06/27] feat: default recipient for additional funds in splitter --- .../andromeda-splitter/src/contract.rs | 57 +++++++++++++-- .../finance/andromeda-splitter/src/mock.rs | 13 +++- .../andromeda-splitter/src/testing/tests.rs | 69 ++++++++++++++++++- ibc-tests/tests/crowdfund.rs | 1 + packages/andromeda-finance/src/splitter.rs | 4 ++ tests-integration/tests/auction_app.rs | 1 + tests-integration/tests/crowdfund_app.rs | 9 ++- tests-integration/tests/kernel.rs | 1 + tests-integration/tests/kernel_orch.rs | 2 + tests-integration/tests/marketplace_app.rs | 1 + tests-integration/tests/splitter.rs | 9 ++- 11 files changed, 153 insertions(+), 14 deletions(-) diff --git a/contracts/finance/andromeda-splitter/src/contract.rs b/contracts/finance/andromeda-splitter/src/contract.rs index e3d215732..c78bfd896 100644 --- a/contracts/finance/andromeda-splitter/src/contract.rs +++ b/contracts/finance/andromeda-splitter/src/contract.rs @@ -5,7 +5,7 @@ use andromeda_finance::splitter::{ }; use andromeda_std::{ ado_base::{InstantiateMsg as BaseInstantiateMsg, MigrateMsg}, - amp::messages::AMPPkt, + amp::{messages::AMPPkt, Recipient}, common::{actions::call_action, encode_binary, expiration::Expiry, Milliseconds}, error::ContractError, }; @@ -33,6 +33,7 @@ pub fn instantiate( Splitter { recipients: msg.recipients.clone(), lock: time, + default_recipient: msg.default_recipient.clone(), } } None => { @@ -40,6 +41,7 @@ pub fn instantiate( recipients: msg.recipients.clone(), // If locking isn't desired upon instantiation, it's automatically set to 0 lock: Milliseconds::default(), + default_recipient: msg.default_recipient.clone(), } } }; @@ -105,6 +107,9 @@ pub fn handle_execute(mut ctx: ExecuteContext, msg: ExecuteMsg) -> Result execute_update_recipients(ctx, recipients), ExecuteMsg::UpdateLock { lock_time } => execute_update_lock(ctx, lock_time), + ExecuteMsg::UpdateDefaultRecipient { recipient } => { + execute_default_recipient(ctx, recipient) + } ExecuteMsg::Send { config } => execute_send(ctx, config), _ => ADOContract::default().execute(ctx, msg), }?; @@ -133,11 +138,12 @@ fn execute_send( } ); } + let splitter = SPLITTER.load(deps.storage)?; - let splitter = if let Some(config) = config { + let splitter_recipients = if let Some(config) = config { config } else { - SPLITTER.load(deps.storage)?.recipients + splitter.recipients }; let mut msgs: Vec = Vec::new(); @@ -155,7 +161,7 @@ fn execute_send( let mut pkt = AMPPkt::from_ctx(ctx.amp_ctx, ctx.env.contract.address.to_string()); - for recipient_addr in &splitter { + for recipient_addr in &splitter_recipients { let recipient_percent = recipient_addr.percent; let mut vec_coin: Vec = Vec::new(); for (i, coin) in info.funds.clone().iter().enumerate() { @@ -184,8 +190,15 @@ fn execute_send( // From tests, it looks like owner of smart contract (Andromeda) will recieve the rest of funds. // If so, should be documented if !remainder_funds.is_empty() { + let remainder_recipient = splitter + .default_recipient + .unwrap_or(Recipient::new(info.sender.to_string(), None)); + msgs.push(SubMsg::new(CosmosMsg::Bank(BankMsg::Send { - to_address: info.sender.to_string(), + to_address: remainder_recipient + .address + .get_raw_address(&deps.as_ref())? + .into_string(), amount: remainder_funds, }))); } @@ -270,6 +283,40 @@ fn execute_update_lock(ctx: ExecuteContext, lock_time: Expiry) -> Result Result { + let ExecuteContext { + deps, info, env, .. + } = ctx; + + nonpayable(&info)?; + + ensure!( + ADOContract::default().is_owner_or_operator(deps.storage, info.sender.as_str())?, + ContractError::Unauthorized {} + ); + + let mut splitter = SPLITTER.load(deps.storage)?; + + // Can't call this function while the lock isn't expired + ensure!( + splitter.lock.is_expired(&env.block), + ContractError::ContractLocked {} + ); + + recipient.validate(&deps.as_ref())?; + splitter.default_recipient = Some(recipient.clone()); + + SPLITTER.save(deps.storage, &splitter)?; + + Ok(Response::default().add_attributes(vec![ + attr("action", "update_default_recipient"), + attr("recipient", recipient.address.to_string()), + ])) +} + #[cfg_attr(not(feature = "library"), entry_point)] pub fn migrate(deps: DepsMut, _env: Env, _msg: MigrateMsg) -> Result { ADOContract::default().migrate(deps, CONTRACT_NAME, CONTRACT_VERSION) diff --git a/contracts/finance/andromeda-splitter/src/mock.rs b/contracts/finance/andromeda-splitter/src/mock.rs index fdf96b9b1..06324fc07 100644 --- a/contracts/finance/andromeda-splitter/src/mock.rs +++ b/contracts/finance/andromeda-splitter/src/mock.rs @@ -2,7 +2,7 @@ use crate::contract::{execute, instantiate, query, reply}; use andromeda_finance::splitter::{AddressPercent, ExecuteMsg, InstantiateMsg, QueryMsg}; -use andromeda_std::common::expiration::Expiry; +use andromeda_std::{amp::Recipient, common::expiration::Expiry}; use andromeda_testing::{ mock::MockApp, mock_ado, mock_contract::ExecuteResult, MockADO, MockContract, }; @@ -21,8 +21,15 @@ impl MockSplitter { kernel_address: impl Into, lock_time: Option, owner: Option, + default_recipient: Option, ) -> Self { - let msg = mock_splitter_instantiate_msg(recipients, kernel_address, lock_time, owner); + let msg = mock_splitter_instantiate_msg( + recipients, + kernel_address, + lock_time, + owner, + default_recipient, + ); let res = app.instantiate_contract(code_id, sender, &msg, &[], "Andromeda Splitter", None); Self(res.unwrap()) @@ -51,12 +58,14 @@ pub fn mock_splitter_instantiate_msg( kernel_address: impl Into, lock_time: Option, owner: Option, + default_recipient: Option, ) -> InstantiateMsg { InstantiateMsg { recipients, lock_time, kernel_address: kernel_address.into(), owner, + default_recipient, } } diff --git a/contracts/finance/andromeda-splitter/src/testing/tests.rs b/contracts/finance/andromeda-splitter/src/testing/tests.rs index 85fd50d55..2093922e6 100644 --- a/contracts/finance/andromeda-splitter/src/testing/tests.rs +++ b/contracts/finance/andromeda-splitter/src/testing/tests.rs @@ -35,6 +35,7 @@ fn init(deps: DepsMut) -> Response { kernel_address: MOCK_KERNEL_CONTRACT.to_string(), recipients: mock_recipient, lock_time: Some(Expiry::FromNow(Milliseconds(86400000))), + default_recipient: None, }; let info = mock_info("owner", &[]); @@ -62,6 +63,7 @@ fn test_different_lock_times() { kernel_address: MOCK_KERNEL_CONTRACT.to_string(), recipients: vec![], lock_time: Some(lock_time), + default_recipient: None, }; let info = mock_info(OWNER, &[]); @@ -77,6 +79,7 @@ fn test_different_lock_times() { kernel_address: MOCK_KERNEL_CONTRACT.to_string(), recipients: vec![], lock_time: Some(lock_time), + default_recipient: None, }; let err = instantiate(deps.as_mut(), env.clone(), info.clone(), msg).unwrap_err(); @@ -94,6 +97,7 @@ fn test_different_lock_times() { percent: Decimal::percent(100), }], lock_time: Some(lock_time), + default_recipient: None, }; let info = mock_info(OWNER, &[]); @@ -109,6 +113,7 @@ fn test_different_lock_times() { kernel_address: MOCK_KERNEL_CONTRACT.to_string(), recipients: vec![], lock_time: Some(lock_time), + default_recipient: None, }; let info = mock_info(OWNER, &[]); @@ -123,6 +128,7 @@ fn test_different_lock_times() { kernel_address: MOCK_KERNEL_CONTRACT.to_string(), recipients: vec![], lock_time: Some(lock_time), + default_recipient: None, }; let info = mock_info(OWNER, &[]); @@ -140,6 +146,7 @@ fn test_different_lock_times() { percent: Decimal::percent(100), }], lock_time: Some(lock_time), + default_recipient: None, }; let info = mock_info(OWNER, &[]); @@ -162,6 +169,7 @@ fn test_execute_update_lock() { let splitter = Splitter { recipients: vec![], lock: Milliseconds::from_seconds(current_time - 1), + default_recipient: None, }; SPLITTER.save(deps.as_mut().storage, &splitter).unwrap(); @@ -200,6 +208,7 @@ fn test_execute_update_recipients() { let splitter = Splitter { recipients: vec![], lock: Milliseconds::from_seconds(0), + default_recipient: None, }; SPLITTER.save(deps.as_mut().storage, &splitter).unwrap(); @@ -276,7 +285,7 @@ fn test_execute_send() { let recip1 = Recipient::from_string(recip_address1); let recip2 = Recipient::from_string(recip_address2); - let recip3 = Recipient::from_string(recip_address3); + let recip3 = Recipient::from_string(recip_address3.clone()); let config_recipient = vec![AddressPercent { recipient: recip3.clone(), @@ -315,8 +324,9 @@ fn test_execute_send() { .unwrap(); let splitter = Splitter { - recipients: recipient, + recipients: recipient.clone(), lock: Milliseconds::default(), + default_recipient: None, }; SPLITTER.save(deps.as_mut().storage, &splitter).unwrap(); @@ -343,7 +353,7 @@ fn test_execute_send() { let msg = ExecuteMsg::Send { config: Some(config_recipient), }; - let res = execute(deps.as_mut(), env, info, msg).unwrap(); + let res = execute(deps.as_mut(), env.clone(), info.clone(), msg).unwrap(); let amp_msg_1 = recip3 .generate_amp_msg(&deps.as_ref(), Some(vec![Coin::new(5000, "uluna")])) .unwrap(); @@ -369,6 +379,55 @@ fn test_execute_send() { amount: vec![Coin::new(5000, "uluna")], // 10000 * 0.5 remainder }), ), + amp_msg.clone(), + ]) + .add_attributes(vec![attr("action", "send"), attr("sender", "creator")]) + .add_submessage(generate_economics_message(OWNER, "Send")); + + assert_eq!(res, expected_res); + + // Test send with default recipient + let msg = ExecuteMsg::Send { config: None }; + SPLITTER + .save( + deps.as_mut().storage, + &Splitter { + recipients: recipient, + lock: Milliseconds::default(), + default_recipient: Some(recip3.clone()), + }, + ) + .unwrap(); + + let res = execute(deps.as_mut(), env.clone(), info.clone(), msg).unwrap(); + let amp_msg_1 = recip1 + .generate_amp_msg(&deps.as_ref(), Some(vec![Coin::new(1000, "uluna")])) + .unwrap(); + let amp_msg_2 = recip2 + .generate_amp_msg(&deps.as_ref(), Some(vec![Coin::new(2000, "uluna")])) + .unwrap(); + let amp_pkt = AMPPkt::new( + MOCK_CONTRACT_ADDR.to_string(), + MOCK_CONTRACT_ADDR.to_string(), + vec![amp_msg_1, amp_msg_2], + ); + let amp_msg = amp_pkt + .to_sub_msg( + MOCK_KERNEL_CONTRACT, + Some(vec![Coin::new(1000, "uluna"), Coin::new(2000, "uluna")]), + 1, + ) + .unwrap(); + + let expected_res = Response::new() + .add_submessages(vec![ + SubMsg::new( + // refunds remainder to sender + CosmosMsg::Bank(BankMsg::Send { + to_address: recip_address3, + amount: vec![Coin::new(7000, "uluna")], // 10000 * 0.7 remainder + }), + ), amp_msg, ]) .add_attributes(vec![attr("action", "send"), attr("sender", "creator")]) @@ -429,6 +488,7 @@ fn test_execute_send_ado_recipient() { let splitter = Splitter { recipients: recipient, lock: Milliseconds::default(), + default_recipient: None, }; SPLITTER.save(deps.as_mut().storage, &splitter).unwrap(); @@ -491,6 +551,7 @@ fn test_handle_packet_exit_with_error_true() { let splitter = Splitter { recipients: recipient, lock: Milliseconds::default(), + default_recipient: None, }; SPLITTER.save(deps.as_mut().storage, &splitter).unwrap(); @@ -512,6 +573,7 @@ fn test_query_splitter() { let splitter = Splitter { recipients: vec![], lock: Milliseconds::default(), + default_recipient: None, }; SPLITTER.save(deps.as_mut().storage, &splitter).unwrap(); @@ -565,6 +627,7 @@ fn test_execute_send_error() { let splitter = Splitter { recipients: recipient, lock: Milliseconds::default(), + default_recipient: None, }; SPLITTER.save(deps.as_mut().storage, &splitter).unwrap(); diff --git a/ibc-tests/tests/crowdfund.rs b/ibc-tests/tests/crowdfund.rs index 2406198a6..97b1d372e 100644 --- a/ibc-tests/tests/crowdfund.rs +++ b/ibc-tests/tests/crowdfund.rs @@ -163,6 +163,7 @@ fn setup( lock_time: None, kernel_address: kernel_address.clone(), owner: None, + default_recipient: None, }; let splitter_component = AppComponent::new( diff --git a/packages/andromeda-finance/src/splitter.rs b/packages/andromeda-finance/src/splitter.rs index 58ba202e3..a9f80d53d 100644 --- a/packages/andromeda-finance/src/splitter.rs +++ b/packages/andromeda-finance/src/splitter.rs @@ -28,6 +28,7 @@ pub struct Splitter { pub recipients: Vec, /// The lock's expiration time pub lock: MillisecondsExpiration, + pub default_recipient: Option, } #[andr_instantiate] @@ -37,6 +38,7 @@ pub struct InstantiateMsg { /// sent the amount sent will be divided amongst these recipients depending on their assigned percentage. pub recipients: Vec, pub lock_time: Option, + pub default_recipient: Option, } impl InstantiateMsg { @@ -52,6 +54,8 @@ pub enum ExecuteMsg { UpdateRecipients { recipients: Vec }, /// Used to lock/unlock the contract allowing the config to be updated. UpdateLock { lock_time: Expiry }, + /// Update the default recipient. Only executable by the contract owner when the contract is not locked. + UpdateDefaultRecipient { recipient: Recipient }, /// Divides any attached funds to the message amongst the recipients list. Send { config: Option> }, } diff --git a/tests-integration/tests/auction_app.rs b/tests-integration/tests/auction_app.rs index 0f533dea9..c277ad103 100644 --- a/tests-integration/tests/auction_app.rs +++ b/tests-integration/tests/auction_app.rs @@ -280,6 +280,7 @@ fn test_auction_app_recipient() { andr.kernel.addr(), None, None, + None, ); let splitter_component = AppComponent::new( "splitter", diff --git a/tests-integration/tests/crowdfund_app.rs b/tests-integration/tests/crowdfund_app.rs index 4caecd510..e9cd015b2 100644 --- a/tests-integration/tests/crowdfund_app.rs +++ b/tests-integration/tests/crowdfund_app.rs @@ -92,8 +92,13 @@ fn setup( percent: Decimal::from_str("0.8").unwrap(), }, ]; - let splitter_init_msg = - mock_splitter_instantiate_msg(splitter_recipients, andr.kernel.addr().clone(), None, None); + let splitter_init_msg = mock_splitter_instantiate_msg( + splitter_recipients, + andr.kernel.addr().clone(), + None, + None, + None, + ); let splitter_component = AppComponent::new( "splitter".to_string(), "splitter".to_string(), diff --git a/tests-integration/tests/kernel.rs b/tests-integration/tests/kernel.rs index a51f4c83e..7215c2b91 100644 --- a/tests-integration/tests/kernel.rs +++ b/tests-integration/tests/kernel.rs @@ -33,6 +33,7 @@ fn kernel() { andr.kernel.addr().clone(), None, None, + None, ); let res = andr diff --git a/tests-integration/tests/kernel_orch.rs b/tests-integration/tests/kernel_orch.rs index a241d96c5..4a5171423 100644 --- a/tests-integration/tests/kernel_orch.rs +++ b/tests-integration/tests/kernel_orch.rs @@ -1500,6 +1500,7 @@ fn test_kernel_ibc_funds_and_execute_msg() { lock_time: None, kernel_address: kernel_osmosis.address().unwrap().into_string(), owner: None, + default_recipient: None, }, None, None, @@ -2143,6 +2144,7 @@ fn test_kernel_ibc_funds_and_execute_msg_unhappy() { lock_time: None, kernel_address: kernel_osmosis.address().unwrap().into_string(), owner: None, + default_recipient: None, }, None, None, diff --git a/tests-integration/tests/marketplace_app.rs b/tests-integration/tests/marketplace_app.rs index b45354456..b3c568831 100644 --- a/tests-integration/tests/marketplace_app.rs +++ b/tests-integration/tests/marketplace_app.rs @@ -326,6 +326,7 @@ fn test_marketplace_app_recipient() { andr.kernel.addr(), None, None, + None, ); let splitter_component = AppComponent::new( "splitter", diff --git a/tests-integration/tests/splitter.rs b/tests-integration/tests/splitter.rs index 629b2b4e6..9786b9a73 100644 --- a/tests-integration/tests/splitter.rs +++ b/tests-integration/tests/splitter.rs @@ -44,8 +44,13 @@ fn test_splitter() { }, ]; - let splitter_init_msg = - mock_splitter_instantiate_msg(splitter_recipients, andr.kernel.addr().clone(), None, None); + let splitter_init_msg = mock_splitter_instantiate_msg( + splitter_recipients, + andr.kernel.addr().clone(), + None, + None, + None, + ); let splitter_app_component = AppComponent { name: "splitter".to_string(), component_type: ComponentType::new(splitter_init_msg), From 726601c0d9030d90c3a26c519f1a9c074e48ca43 Mon Sep 17 00:00:00 2001 From: Joe Monem Date: Wed, 4 Dec 2024 18:33:35 +0200 Subject: [PATCH 07/27] feat: default recipient for additional funds in weighted distribution splitter --- .../src/contract.rs | 22 ++++++++++---- .../src/testing/tests.rs | 30 +++++++++++++++++++ packages/andromeda-finance/src/splitter.rs | 1 + .../src/weighted_splitter.rs | 3 ++ 4 files changed, 50 insertions(+), 6 deletions(-) diff --git a/contracts/finance/andromeda-weighted-distribution-splitter/src/contract.rs b/contracts/finance/andromeda-weighted-distribution-splitter/src/contract.rs index 612f9af11..dc8dad92c 100644 --- a/contracts/finance/andromeda-weighted-distribution-splitter/src/contract.rs +++ b/contracts/finance/andromeda-weighted-distribution-splitter/src/contract.rs @@ -46,6 +46,7 @@ pub fn instantiate( Splitter { recipients: msg.recipients, lock: time, + default_recipient: msg.default_recipient, } } None => { @@ -53,6 +54,7 @@ pub fn instantiate( recipients: msg.recipients, // If locking isn't desired upon instantiation, it's automatically set to 0 lock: Milliseconds::default(), + default_recipient: msg.default_recipient, } } }; @@ -215,6 +217,7 @@ pub fn execute_add_recipient( let new_splitter = Splitter { recipients: splitter.recipients, lock: splitter.lock, + default_recipient: splitter.default_recipient, }; SPLITTER.save(deps.storage, &new_splitter)?; @@ -238,25 +241,25 @@ fn execute_send( info.funds.len() < 5, ContractError::ExceedsMaxAllowedCoins {} ); - - let splitter = if let Some(config) = config { + let splitter = SPLITTER.load(deps.storage)?; + let splitter_recipients = if let Some(config) = config { config } else { - SPLITTER.load(deps.storage)?.recipients + splitter.recipients }; let mut msgs: Vec = Vec::new(); let mut remainder_funds = info.funds.clone(); let mut total_weight = Uint128::zero(); // Calculate the total weight of all recipients - for recipient_addr in &splitter { + for recipient_addr in &splitter_recipients { let recipient_weight = recipient_addr.weight; total_weight = total_weight.checked_add(recipient_weight)?; } // Each recipient recieves the funds * (the recipient's weight / total weight of all recipients) // The remaining funds go to the sender of the function - for recipient_addr in &splitter { + for recipient_addr in &splitter_recipients { let recipient_weight = recipient_addr.weight; let mut vec_coin: Vec = Vec::new(); for (i, coin) in info.funds.iter().enumerate() { @@ -275,8 +278,14 @@ fn execute_send( remainder_funds.retain(|x| x.amount > Uint128::zero()); if !remainder_funds.is_empty() { + let remainder_recipient = splitter + .default_recipient + .unwrap_or(Recipient::new(info.sender.to_string(), None)); msgs.push(SubMsg::new(CosmosMsg::Bank(BankMsg::Send { - to_address: info.sender.to_string(), + to_address: remainder_recipient + .address + .get_raw_address(&deps.as_ref())? + .into_string(), amount: remainder_funds, }))); } @@ -400,6 +409,7 @@ fn execute_remove_recipient( let new_splitter = Splitter { recipients: splitter.recipients, lock: splitter.lock, + default_recipient: splitter.default_recipient, }; SPLITTER.save(deps.storage, &new_splitter)?; }; diff --git a/contracts/finance/andromeda-weighted-distribution-splitter/src/testing/tests.rs b/contracts/finance/andromeda-weighted-distribution-splitter/src/testing/tests.rs index 09af1bdeb..99f7cb150 100644 --- a/contracts/finance/andromeda-weighted-distribution-splitter/src/testing/tests.rs +++ b/contracts/finance/andromeda-weighted-distribution-splitter/src/testing/tests.rs @@ -33,6 +33,7 @@ fn init(deps: DepsMut) -> Response { kernel_address: MOCK_KERNEL_CONTRACT.to_string(), recipients: mock_recipient, lock_time: Some(Expiry::FromNow(Milliseconds(86400000))), + default_recipient: None, }; let info = mock_info(OWNER, &[]); @@ -57,6 +58,7 @@ fn test_update_app_contract() { lock_time: None, kernel_address: MOCK_KERNEL_CONTRACT.to_string(), owner: None, + default_recipient: None, }; let _res = instantiate(deps.as_mut(), mock_env(), info.clone(), msg).unwrap(); @@ -126,6 +128,7 @@ fn test_instantiate() { lock_time: None, kernel_address: MOCK_KERNEL_CONTRACT.to_string(), owner: None, + default_recipient: None, }; let res = instantiate(deps.as_mut(), env, info, msg).unwrap(); assert_eq!(0, res.messages.len()); @@ -146,6 +149,7 @@ fn test_execute_update_lock() { let splitter = Splitter { recipients: vec![], lock: Milliseconds(current_time - 1), + default_recipient: None, }; SPLITTER.save(deps.as_mut().storage, &splitter).unwrap(); @@ -203,6 +207,7 @@ fn test_execute_update_lock_too_short() { let splitter = Splitter { recipients: vec![], lock: Milliseconds(current_time - 1), + default_recipient: None, }; SPLITTER.save(deps.as_mut().storage, &splitter).unwrap(); @@ -247,6 +252,7 @@ fn test_execute_update_lock_too_long() { let splitter = Splitter { recipients: vec![], lock: Milliseconds(current_time - 1), + default_recipient: None, }; SPLITTER.save(deps.as_mut().storage, &splitter).unwrap(); @@ -292,6 +298,7 @@ fn test_execute_update_lock_already_locked() { let splitter = Splitter { recipients: vec![], lock: Milliseconds::default().plus_seconds(current_time + 10_000), + default_recipient: None, }; SPLITTER.save(deps.as_mut().storage, &splitter).unwrap(); @@ -336,6 +343,7 @@ fn test_execute_update_lock_unauthorized() { let splitter = Splitter { recipients: vec![], lock: new_lock, + default_recipient: None, }; SPLITTER.save(deps.as_mut().storage, &splitter).unwrap(); @@ -414,6 +422,7 @@ fn test_execute_remove_recipient() { let splitter = Splitter { recipients: recipient, lock: Milliseconds::default(), + default_recipient: None, }; SPLITTER.save(deps.as_mut().storage, &splitter).unwrap(); @@ -439,6 +448,7 @@ fn test_execute_remove_recipient() { }, ], lock: Milliseconds::default(), + default_recipient: None, }; assert_eq!(expected_splitter, splitter); assert_eq!( @@ -506,6 +516,7 @@ fn test_execute_remove_recipient_not_on_list() { let splitter = Splitter { recipients: recipient, lock: Milliseconds::default(), + default_recipient: None, }; SPLITTER.save(deps.as_mut().storage, &splitter).unwrap(); @@ -569,6 +580,7 @@ fn test_execute_remove_recipient_contract_locked() { let splitter = Splitter { recipients: recipient.clone(), lock: Milliseconds::default(), + default_recipient: None, }; SPLITTER.save(deps.as_mut().storage, &splitter).unwrap(); @@ -579,6 +591,7 @@ fn test_execute_remove_recipient_contract_locked() { let splitter = Splitter { recipients: recipient, lock: Milliseconds::default().plus_seconds(current_time + 10_000), + default_recipient: None, }; SPLITTER.save(deps.as_mut().storage, &splitter).unwrap(); @@ -692,6 +705,7 @@ fn test_update_recipient_weight() { let splitter = Splitter { recipients: recipient.clone(), lock: Milliseconds::default(), + default_recipient: None, }; SPLITTER.save(deps.as_mut().storage, &splitter).unwrap(); @@ -702,6 +716,7 @@ fn test_update_recipient_weight() { let splitter = Splitter { recipients: recipient, lock: Milliseconds::default(), + default_recipient: None, }; SPLITTER.save(deps.as_mut().storage, &splitter).unwrap(); @@ -733,6 +748,7 @@ fn test_update_recipient_weight() { }, ], lock: Milliseconds::default(), + default_recipient: None, }; assert_eq!(expected_splitter, splitter); } @@ -785,6 +801,7 @@ fn test_update_recipient_weight_locked_contract() { let splitter = Splitter { recipients: recipient.clone(), lock: Milliseconds(current_time - 1), + default_recipient: None, }; SPLITTER.save(deps.as_mut().storage, &splitter).unwrap(); @@ -795,6 +812,7 @@ fn test_update_recipient_weight_locked_contract() { let splitter = Splitter { recipients: recipient, lock: Milliseconds::default().plus_seconds(current_time + 1), + default_recipient: None, }; SPLITTER.save(deps.as_mut().storage, &splitter).unwrap(); @@ -862,6 +880,7 @@ fn test_update_recipient_weight_user_not_found() { let splitter = Splitter { recipients: recipient, lock: Milliseconds::default(), + default_recipient: None, }; SPLITTER.save(deps.as_mut().storage, &splitter).unwrap(); @@ -934,6 +953,7 @@ fn test_update_recipient_weight_invalid_weight() { let splitter = Splitter { recipients: recipient, lock: Milliseconds::default(), + default_recipient: None, }; SPLITTER.save(deps.as_mut().storage, &splitter).unwrap(); @@ -998,6 +1018,7 @@ fn test_execute_add_recipient() { let splitter = Splitter { recipients: recipient, lock: Milliseconds::default(), + default_recipient: None, }; SPLITTER.save(deps.as_mut().storage, &splitter).unwrap(); @@ -1040,6 +1061,7 @@ fn test_execute_add_recipient() { }, ], lock: Milliseconds::default(), + default_recipient: None, }; assert_eq!(expected_splitter, splitter); @@ -1103,6 +1125,7 @@ fn test_execute_add_recipient_duplicate_recipient() { let splitter = Splitter { recipients: recipient, lock: Milliseconds::default(), + default_recipient: None, }; SPLITTER.save(deps.as_mut().storage, &splitter).unwrap(); @@ -1181,6 +1204,7 @@ fn test_execute_add_recipient_invalid_weight() { let splitter = Splitter { recipients: recipient, lock: Milliseconds::default(), + default_recipient: None, }; SPLITTER.save(deps.as_mut().storage, &splitter).unwrap(); @@ -1247,6 +1271,7 @@ fn test_execute_add_recipient_locked_contract() { let splitter = Splitter { recipients: recipient, lock: Milliseconds::default().plus_seconds(current_time + 1), + default_recipient: None, }; SPLITTER.save(deps.as_mut().storage, &splitter).unwrap(); @@ -1311,6 +1336,7 @@ fn test_execute_update_recipients() { let splitter = Splitter { recipients: vec![], lock: Milliseconds::default(), + default_recipient: None, }; SPLITTER.save(deps.as_mut().storage, &splitter).unwrap(); @@ -1382,6 +1408,7 @@ fn test_execute_update_recipients_invalid_weight() { let splitter = Splitter { recipients: vec![], lock: Milliseconds::default(), + default_recipient: None, }; SPLITTER.save(deps.as_mut().storage, &splitter).unwrap(); @@ -1437,6 +1464,7 @@ fn test_execute_update_recipients_contract_locked() { let splitter = Splitter { recipients: vec![], lock: Milliseconds::default().plus_seconds(current_time + 10), + default_recipient: None, }; SPLITTER.save(deps.as_mut().storage, &splitter).unwrap(); @@ -1490,6 +1518,7 @@ fn test_execute_update_recipients_unauthorized() { let splitter = Splitter { recipients: vec![], lock: Milliseconds::default(), + default_recipient: None, }; SPLITTER.save(deps.as_mut().storage, &splitter).unwrap(); @@ -1562,6 +1591,7 @@ fn test_execute_send() { let splitter = Splitter { recipients: recipient, lock: Milliseconds::default(), + default_recipient: None, }; SPLITTER.save(deps.as_mut().storage, &splitter).unwrap(); diff --git a/packages/andromeda-finance/src/splitter.rs b/packages/andromeda-finance/src/splitter.rs index a9f80d53d..9b2d32f7d 100644 --- a/packages/andromeda-finance/src/splitter.rs +++ b/packages/andromeda-finance/src/splitter.rs @@ -28,6 +28,7 @@ pub struct Splitter { pub recipients: Vec, /// The lock's expiration time pub lock: MillisecondsExpiration, + /// The address that will receive any surplus funds, defaults to the message sender. pub default_recipient: Option, } diff --git a/packages/andromeda-finance/src/weighted_splitter.rs b/packages/andromeda-finance/src/weighted_splitter.rs index 40b83c0cf..c2d6472d5 100644 --- a/packages/andromeda-finance/src/weighted_splitter.rs +++ b/packages/andromeda-finance/src/weighted_splitter.rs @@ -19,6 +19,8 @@ pub struct Splitter { pub recipients: Vec, /// Whether or not the contract is currently locked. This restricts updating any config related fields. pub lock: MillisecondsExpiration, + /// The address that will receive any surplus funds, defaults to the message sender. + pub default_recipient: Option, } #[andr_instantiate] @@ -28,6 +30,7 @@ pub struct InstantiateMsg { /// sent the amount sent will be divided amongst these recipients depending on their assigned weight. pub recipients: Vec, pub lock_time: Option, + pub default_recipient: Option, } #[andr_exec] From 6c7ef649e733af24c847dc62346fb3e92ada1114 Mon Sep 17 00:00:00 2001 From: Joe Monem Date: Wed, 4 Dec 2024 18:34:47 +0200 Subject: [PATCH 08/27] chore: changelog entry --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index ba2a70e9e..dde925de9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -27,6 +27,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Added Validator Staking ADO [(#330)](https://github.com/andromedaprotocol/andromeda-core/pull/330) - Added Restake and Redelegate to Validator Staking [(#622)](https://github.com/andromedaprotocol/andromeda-core/pull/622) - Added andromeda-math and andromeda-account packages[(#672)](https://github.com/andromedaprotocol/andromeda-core/pull/672) +- Added optional config for Send in Splitter contracts [(#686)](https://github.com/andromedaprotocol/andromeda-core/pull/686) ### Changed From 6436ada216f571080d2e4c4b2b1c38eebe1e8a9c Mon Sep 17 00:00:00 2001 From: Joe Monem Date: Wed, 4 Dec 2024 18:43:30 +0200 Subject: [PATCH 09/27] test: add config test case in splitter integration test --- tests-integration/tests/splitter.rs | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/tests-integration/tests/splitter.rs b/tests-integration/tests/splitter.rs index 629b2b4e6..98203d29e 100644 --- a/tests-integration/tests/splitter.rs +++ b/tests-integration/tests/splitter.rs @@ -18,7 +18,7 @@ fn test_splitter() { let mut router = mock_app(None); let andr = MockAndromedaBuilder::new(&mut router, "admin") .with_wallets(vec![ - ("owner", vec![coin(1000, "uandr")]), + ("owner", vec![coin(10000, "uandr")]), ("recipient1", vec![]), ("recipient2", vec![]), ]) @@ -68,7 +68,7 @@ fn test_splitter() { let token = coin(1000, "uandr"); splitter - .execute_send(&mut router, owner.clone(), &[token], None) + .execute_send(&mut router, owner.clone(), &[token.clone()], None) .unwrap(); let balance_1 = router.wrap().query_balance(recipient_1, "uandr").unwrap(); @@ -76,4 +76,21 @@ fn test_splitter() { assert_eq!(balance_1.amount, Uint128::from(200u128)); assert_eq!(balance_2.amount, Uint128::from(800u128)); + + // Test with config + let custom_recipients = vec![AddressPercent { + recipient: Recipient::from_string(recipient_1.to_string()), + percent: Decimal::from_str("0.5").unwrap(), + }]; + + splitter + .execute_send( + &mut router, + owner.clone(), + &[token], + Some(custom_recipients), + ) + .unwrap(); + let balance_1 = router.wrap().query_balance(recipient_1, "uandr").unwrap(); + assert_eq!(balance_1.amount, Uint128::from(200u128 + 500u128)); } From a26bedf6388f6cf9e09638431a835f4306b9ba91 Mon Sep 17 00:00:00 2001 From: Joe Monem Date: Wed, 4 Dec 2024 18:52:10 +0200 Subject: [PATCH 10/27] ref: validate config in splitter --- .../finance/andromeda-set-amount-splitter/src/contract.rs | 7 ++++--- contracts/finance/andromeda-splitter/src/contract.rs | 7 ++++--- .../src/contract.rs | 2 ++ 3 files changed, 10 insertions(+), 6 deletions(-) diff --git a/contracts/finance/andromeda-set-amount-splitter/src/contract.rs b/contracts/finance/andromeda-set-amount-splitter/src/contract.rs index bbbeeb874..b88a0ca00 100644 --- a/contracts/finance/andromeda-set-amount-splitter/src/contract.rs +++ b/contracts/finance/andromeda-set-amount-splitter/src/contract.rs @@ -162,10 +162,11 @@ fn execute_send( denom_set.insert(coin.denom); } - let splitter = if let Some(config) = config { + let splitter = if let Some(ref config) = config { + validate_recipient_list(deps.as_ref(), config.clone())?; config } else { - SPLITTER.load(deps.storage)?.recipients + &SPLITTER.load(deps.storage)?.recipients }; let mut msgs: Vec = Vec::new(); @@ -178,7 +179,7 @@ fn execute_send( let mut remainder_funds = coin.amount; let denom = coin.denom; - for recipient in &splitter { + for recipient in splitter { // Find the recipient's corresponding denom for the current iteration of the sent funds let recipient_coin = recipient .coins diff --git a/contracts/finance/andromeda-splitter/src/contract.rs b/contracts/finance/andromeda-splitter/src/contract.rs index e3d215732..c63cf7a86 100644 --- a/contracts/finance/andromeda-splitter/src/contract.rs +++ b/contracts/finance/andromeda-splitter/src/contract.rs @@ -134,10 +134,11 @@ fn execute_send( ); } - let splitter = if let Some(config) = config { + let splitter = if let Some(ref config) = config { + validate_recipient_list(deps.as_ref(), config.clone())?; config } else { - SPLITTER.load(deps.storage)?.recipients + &SPLITTER.load(deps.storage)?.recipients }; let mut msgs: Vec = Vec::new(); @@ -155,7 +156,7 @@ fn execute_send( let mut pkt = AMPPkt::from_ctx(ctx.amp_ctx, ctx.env.contract.address.to_string()); - for recipient_addr in &splitter { + for recipient_addr in splitter { let recipient_percent = recipient_addr.percent; let mut vec_coin: Vec = Vec::new(); for (i, coin) in info.funds.clone().iter().enumerate() { diff --git a/contracts/finance/andromeda-weighted-distribution-splitter/src/contract.rs b/contracts/finance/andromeda-weighted-distribution-splitter/src/contract.rs index 612f9af11..7617c2ba3 100644 --- a/contracts/finance/andromeda-weighted-distribution-splitter/src/contract.rs +++ b/contracts/finance/andromeda-weighted-distribution-splitter/src/contract.rs @@ -240,6 +240,8 @@ fn execute_send( ); let splitter = if let Some(config) = config { + // Max 100 recipients + ensure!(config.len() <= 100, ContractError::ReachedRecipientLimit {}); config } else { SPLITTER.load(deps.storage)?.recipients From 39bed131cbe80ebfcfd5328cc95d800574782d92 Mon Sep 17 00:00:00 2001 From: Joe Monem Date: Wed, 4 Dec 2024 19:01:36 +0200 Subject: [PATCH 11/27] fix: clippy --- .../finance/andromeda-set-amount-splitter/src/contract.rs | 6 +++--- contracts/finance/andromeda-splitter/src/contract.rs | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/contracts/finance/andromeda-set-amount-splitter/src/contract.rs b/contracts/finance/andromeda-set-amount-splitter/src/contract.rs index b88a0ca00..d0b3a517c 100644 --- a/contracts/finance/andromeda-set-amount-splitter/src/contract.rs +++ b/contracts/finance/andromeda-set-amount-splitter/src/contract.rs @@ -162,11 +162,11 @@ fn execute_send( denom_set.insert(coin.denom); } - let splitter = if let Some(ref config) = config { + let splitter = if let Some(config) = config { validate_recipient_list(deps.as_ref(), config.clone())?; config } else { - &SPLITTER.load(deps.storage)?.recipients + SPLITTER.load(deps.storage)?.recipients }; let mut msgs: Vec = Vec::new(); @@ -179,7 +179,7 @@ fn execute_send( let mut remainder_funds = coin.amount; let denom = coin.denom; - for recipient in splitter { + for recipient in splitter.clone() { // Find the recipient's corresponding denom for the current iteration of the sent funds let recipient_coin = recipient .coins diff --git a/contracts/finance/andromeda-splitter/src/contract.rs b/contracts/finance/andromeda-splitter/src/contract.rs index c63cf7a86..a46e91042 100644 --- a/contracts/finance/andromeda-splitter/src/contract.rs +++ b/contracts/finance/andromeda-splitter/src/contract.rs @@ -134,11 +134,11 @@ fn execute_send( ); } - let splitter = if let Some(ref config) = config { + let splitter = if let Some(config) = config { validate_recipient_list(deps.as_ref(), config.clone())?; config } else { - &SPLITTER.load(deps.storage)?.recipients + SPLITTER.load(deps.storage)?.recipients }; let mut msgs: Vec = Vec::new(); From 2c9bd092b717b6332fbf2e521f64f4278cb8a86c Mon Sep 17 00:00:00 2001 From: Joe Monem Date: Thu, 5 Dec 2024 08:19:41 +0200 Subject: [PATCH 12/27] fix: allow removing default recipient, adjust default recipient in weighted distribution splitter --- .../andromeda-splitter/src/contract.rs | 17 +++++-- .../src/contract.rs | 47 ++++++++++++++++++- packages/andromeda-finance/src/splitter.rs | 2 +- .../src/weighted_splitter.rs | 2 + 4 files changed, 62 insertions(+), 6 deletions(-) diff --git a/contracts/finance/andromeda-splitter/src/contract.rs b/contracts/finance/andromeda-splitter/src/contract.rs index c78bfd896..9bc767cdd 100644 --- a/contracts/finance/andromeda-splitter/src/contract.rs +++ b/contracts/finance/andromeda-splitter/src/contract.rs @@ -285,7 +285,7 @@ fn execute_update_lock(ctx: ExecuteContext, lock_time: Expiry) -> Result, ) -> Result { let ExecuteContext { deps, info, env, .. @@ -306,14 +306,23 @@ fn execute_default_recipient( ContractError::ContractLocked {} ); - recipient.validate(&deps.as_ref())?; - splitter.default_recipient = Some(recipient.clone()); + if let Some(ref recipient) = recipient { + recipient.validate(&deps.as_ref())?; + } + splitter.default_recipient = recipient; SPLITTER.save(deps.storage, &splitter)?; Ok(Response::default().add_attributes(vec![ attr("action", "update_default_recipient"), - attr("recipient", recipient.address.to_string()), + attr( + "recipient", + splitter + .default_recipient + .map_or("no default recipient".to_string(), |r| { + r.address.to_string() + }), + ), ])) } diff --git a/contracts/finance/andromeda-weighted-distribution-splitter/src/contract.rs b/contracts/finance/andromeda-weighted-distribution-splitter/src/contract.rs index dc8dad92c..cb5fda787 100644 --- a/contracts/finance/andromeda-weighted-distribution-splitter/src/contract.rs +++ b/contracts/finance/andromeda-weighted-distribution-splitter/src/contract.rs @@ -111,7 +111,9 @@ pub fn handle_execute(mut ctx: ExecuteContext, msg: ExecuteMsg) -> Result execute_add_recipient(ctx, recipient), ExecuteMsg::RemoveRecipient { recipient } => execute_remove_recipient(ctx, recipient), ExecuteMsg::UpdateLock { lock_time } => execute_update_lock(ctx, lock_time), - + ExecuteMsg::UpdateDefaultRecipient { recipient } => { + execute_default_recipient(ctx, recipient) + } ExecuteMsg::Send { config } => execute_send(ctx, config), _ => ADOContract::default().execute(ctx, msg), @@ -165,6 +167,49 @@ pub fn execute_update_recipient_weight( Ok(Response::default().add_attribute("action", "updated_recipient_weight")) } +fn execute_default_recipient( + ctx: ExecuteContext, + recipient: Option, +) -> Result { + let ExecuteContext { + deps, info, env, .. + } = ctx; + + nonpayable(&info)?; + + ensure!( + ADOContract::default().is_owner_or_operator(deps.storage, info.sender.as_str())?, + ContractError::Unauthorized {} + ); + + let mut splitter = SPLITTER.load(deps.storage)?; + + // Can't call this function while the lock isn't expired + ensure!( + splitter.lock.is_expired(&env.block), + ContractError::ContractLocked {} + ); + + if let Some(ref recipient) = recipient { + recipient.validate(&deps.as_ref())?; + } + splitter.default_recipient = recipient; + + SPLITTER.save(deps.storage, &splitter)?; + + Ok(Response::default().add_attributes(vec![ + attr("action", "update_default_recipient"), + attr( + "recipient", + splitter + .default_recipient + .map_or("no default recipient".to_string(), |r| { + r.address.to_string() + }), + ), + ])) +} + pub fn execute_add_recipient( ctx: ExecuteContext, recipient: AddressWeight, diff --git a/packages/andromeda-finance/src/splitter.rs b/packages/andromeda-finance/src/splitter.rs index 9b2d32f7d..95bca32b3 100644 --- a/packages/andromeda-finance/src/splitter.rs +++ b/packages/andromeda-finance/src/splitter.rs @@ -56,7 +56,7 @@ pub enum ExecuteMsg { /// Used to lock/unlock the contract allowing the config to be updated. UpdateLock { lock_time: Expiry }, /// Update the default recipient. Only executable by the contract owner when the contract is not locked. - UpdateDefaultRecipient { recipient: Recipient }, + UpdateDefaultRecipient { recipient: Option }, /// Divides any attached funds to the message amongst the recipients list. Send { config: Option> }, } diff --git a/packages/andromeda-finance/src/weighted_splitter.rs b/packages/andromeda-finance/src/weighted_splitter.rs index c2d6472d5..56a78f72f 100644 --- a/packages/andromeda-finance/src/weighted_splitter.rs +++ b/packages/andromeda-finance/src/weighted_splitter.rs @@ -40,6 +40,8 @@ pub enum ExecuteMsg { UpdateRecipients { recipients: Vec }, /// Update a specific recipient's weight. Only executable by the contract owner when the contract is not locked. UpdateRecipientWeight { recipient: AddressWeight }, + /// Update the default recipient. Only executable by the contract owner when the contract is not locked. + UpdateDefaultRecipient { recipient: Option }, /// Add a single recipient to the recipient list. Only executable by the contract owner when the contract is not locked. AddRecipient { recipient: AddressWeight }, /// Remove a single recipient from the recipient list. Only executable by the contract owner when the contract is not locked. From e63c574e35c09e7cf4248922784ca78e219f1fb0 Mon Sep 17 00:00:00 2001 From: Joe Monem Date: Thu, 5 Dec 2024 09:24:34 +0200 Subject: [PATCH 13/27] feat: default recipient for set amount splitter --- .../src/contract.rs | 67 +++++++++++++++++-- .../andromeda-set-amount-splitter/src/mock.rs | 14 +++- .../src/testing/tests.rs | 8 +++ .../src/set_amount_splitter.rs | 6 ++ .../tests/set_amount_splitter.rs | 1 + 5 files changed, 88 insertions(+), 8 deletions(-) diff --git a/contracts/finance/andromeda-set-amount-splitter/src/contract.rs b/contracts/finance/andromeda-set-amount-splitter/src/contract.rs index bbbeeb874..24f40b20c 100644 --- a/contracts/finance/andromeda-set-amount-splitter/src/contract.rs +++ b/contracts/finance/andromeda-set-amount-splitter/src/contract.rs @@ -10,7 +10,7 @@ use andromeda_finance::{ }; use andromeda_std::{ ado_base::{InstantiateMsg as BaseInstantiateMsg, MigrateMsg}, - amp::messages::AMPPkt, + amp::{messages::AMPPkt, Recipient}, common::{actions::call_action, encode_binary, expiration::Expiry, Milliseconds}, error::ContractError, }; @@ -52,6 +52,7 @@ pub fn instantiate( Splitter { recipients: msg.recipients.clone(), lock: lock_time.get_time(&env.block), + default_recipient: msg.default_recipient.clone(), } } None => { @@ -59,6 +60,7 @@ pub fn instantiate( recipients: msg.recipients.clone(), // If locking isn't desired upon instantiation, it's automatically set to 0 lock: Milliseconds::default(), + default_recipient: msg.default_recipient.clone(), } } }; @@ -124,6 +126,9 @@ pub fn handle_execute(mut ctx: ExecuteContext, msg: ExecuteMsg) -> Result execute_update_recipients(ctx, recipients), ExecuteMsg::UpdateLock { lock_time } => execute_update_lock(ctx, lock_time), + ExecuteMsg::UpdateDefaultRecipient { recipient } => { + execute_default_recipient(ctx, recipient) + } ExecuteMsg::Send { config } => execute_send(ctx, config), _ => ADOContract::default().execute(ctx, msg), }?; @@ -133,6 +138,49 @@ pub fn handle_execute(mut ctx: ExecuteContext, msg: ExecuteMsg) -> Result, +) -> Result { + let ExecuteContext { + deps, info, env, .. + } = ctx; + + nonpayable(&info)?; + + ensure!( + ADOContract::default().is_owner_or_operator(deps.storage, info.sender.as_str())?, + ContractError::Unauthorized {} + ); + + let mut splitter = SPLITTER.load(deps.storage)?; + + // Can't call this function while the lock isn't expired + ensure!( + splitter.lock.is_expired(&env.block), + ContractError::ContractLocked {} + ); + + if let Some(ref recipient) = recipient { + recipient.validate(&deps.as_ref())?; + } + splitter.default_recipient = recipient; + + SPLITTER.save(deps.storage, &splitter)?; + + Ok(Response::default().add_attributes(vec![ + attr("action", "update_default_recipient"), + attr( + "recipient", + splitter + .default_recipient + .map_or("no default recipient".to_string(), |r| { + r.address.to_string() + }), + ), + ])) +} + fn execute_send( ctx: ExecuteContext, config: Option>, @@ -162,10 +210,12 @@ fn execute_send( denom_set.insert(coin.denom); } - let splitter = if let Some(config) = config { + let splitter = SPLITTER.load(deps.storage)?; + + let splitter_recipients = if let Some(config) = config { config } else { - SPLITTER.load(deps.storage)?.recipients + splitter.recipients }; let mut msgs: Vec = Vec::new(); @@ -178,7 +228,7 @@ fn execute_send( let mut remainder_funds = coin.amount; let denom = coin.denom; - for recipient in &splitter { + for recipient in &splitter_recipients { // Find the recipient's corresponding denom for the current iteration of the sent funds let recipient_coin = recipient .coins @@ -207,8 +257,15 @@ fn execute_send( // Refund message for sender if !remainder_funds.is_zero() { + let remainder_recipient = splitter + .default_recipient + .clone() + .unwrap_or(Recipient::new(info.sender.to_string(), None)); let msg = SubMsg::new(CosmosMsg::Bank(BankMsg::Send { - to_address: info.sender.clone().into_string(), + to_address: remainder_recipient + .address + .get_raw_address(&deps.as_ref())? + .into_string(), amount: coins(remainder_funds.u128(), denom), })); msgs.push(msg); diff --git a/contracts/finance/andromeda-set-amount-splitter/src/mock.rs b/contracts/finance/andromeda-set-amount-splitter/src/mock.rs index 5073ed77b..1464acd70 100644 --- a/contracts/finance/andromeda-set-amount-splitter/src/mock.rs +++ b/contracts/finance/andromeda-set-amount-splitter/src/mock.rs @@ -2,7 +2,7 @@ use crate::contract::{execute, instantiate, query, reply}; use andromeda_finance::set_amount_splitter::{AddressAmount, ExecuteMsg, InstantiateMsg, QueryMsg}; -use andromeda_std::common::expiration::Expiry; +use andromeda_std::{amp::Recipient, common::expiration::Expiry}; use andromeda_testing::{ mock::MockApp, mock_ado, mock_contract::ExecuteResult, MockADO, MockContract, }; @@ -21,9 +21,15 @@ impl MockSetAmountSplitter { kernel_address: impl Into, lock_time: Option, owner: Option, + default_recipient: Option, ) -> Self { - let msg = - mock_set_amount_splitter_instantiate_msg(recipients, kernel_address, lock_time, owner); + let msg = mock_set_amount_splitter_instantiate_msg( + recipients, + kernel_address, + lock_time, + owner, + default_recipient, + ); let res = app.instantiate_contract(code_id, sender, &msg, &[], "Andromeda Splitter", None); Self(res.unwrap()) @@ -52,12 +58,14 @@ pub fn mock_set_amount_splitter_instantiate_msg( kernel_address: impl Into, lock_time: Option, owner: Option, + default_recipient: Option, ) -> InstantiateMsg { InstantiateMsg { recipients, lock_time, kernel_address: kernel_address.into(), owner, + default_recipient, } } diff --git a/contracts/finance/andromeda-set-amount-splitter/src/testing/tests.rs b/contracts/finance/andromeda-set-amount-splitter/src/testing/tests.rs index 9c0bad5c3..bdb3afe79 100644 --- a/contracts/finance/andromeda-set-amount-splitter/src/testing/tests.rs +++ b/contracts/finance/andromeda-set-amount-splitter/src/testing/tests.rs @@ -35,6 +35,7 @@ fn init(deps: DepsMut) -> Response { kernel_address: MOCK_KERNEL_CONTRACT.to_string(), recipients: mock_recipient, lock_time: Some(Expiry::AtTime(Milliseconds::from_seconds(100_000))), + default_recipient: None, }; let info = mock_info("owner", &[]); @@ -63,6 +64,7 @@ fn test_execute_update_lock() { let splitter = Splitter { recipients: vec![], lock: Milliseconds::from_seconds(current_time - 1), + default_recipient: None, }; SPLITTER.save(deps.as_mut().storage, &splitter).unwrap(); @@ -101,6 +103,7 @@ fn test_execute_update_recipients() { let splitter = Splitter { recipients: vec![], lock: Milliseconds::from_seconds(0), + default_recipient: None, }; SPLITTER.save(deps.as_mut().storage, &splitter).unwrap(); @@ -232,6 +235,7 @@ fn test_execute_send() { let splitter = Splitter { recipients: recipient, lock: Milliseconds::default(), + default_recipient: None, }; SPLITTER.save(deps.as_mut().storage, &splitter).unwrap(); @@ -355,6 +359,7 @@ fn test_execute_send_ado_recipient() { let splitter = Splitter { recipients: recipient, lock: Milliseconds::default(), + default_recipient: None, }; SPLITTER.save(deps.as_mut().storage, &splitter).unwrap(); @@ -414,6 +419,7 @@ fn test_handle_packet_exit_with_error_true() { let splitter = Splitter { recipients: recipient, lock: Milliseconds::default(), + default_recipient: None, }; SPLITTER.save(deps.as_mut().storage, &splitter).unwrap(); @@ -435,6 +441,7 @@ fn test_query_splitter() { let splitter = Splitter { recipients: vec![], lock: Milliseconds::default(), + default_recipient: None, }; SPLITTER.save(deps.as_mut().storage, &splitter).unwrap(); @@ -486,6 +493,7 @@ fn test_execute_send_error() { let splitter = Splitter { recipients: recipient, lock: Milliseconds::default(), + default_recipient: None, }; SPLITTER.save(deps.as_mut().storage, &splitter).unwrap(); diff --git a/packages/andromeda-finance/src/set_amount_splitter.rs b/packages/andromeda-finance/src/set_amount_splitter.rs index b2894c085..9e9efe1a2 100644 --- a/packages/andromeda-finance/src/set_amount_splitter.rs +++ b/packages/andromeda-finance/src/set_amount_splitter.rs @@ -28,6 +28,8 @@ pub struct Splitter { pub recipients: Vec, /// The lock's expiration time pub lock: MillisecondsExpiration, + /// The address that will receive any surplus funds, defaults to the message sender. + pub default_recipient: Option, } #[andr_instantiate] @@ -37,6 +39,8 @@ pub struct InstantiateMsg { /// sent the amount sent will be divided amongst these recipients depending on their assigned amount. pub recipients: Vec, pub lock_time: Option, + /// The address that will receive any surplus funds, defaults to the message sender. + pub default_recipient: Option, } impl InstantiateMsg { @@ -55,6 +59,8 @@ pub enum ExecuteMsg { // Milliseconds from current time lock_time: Expiry, }, + /// Update the default recipient. Only executable by the contract owner when the contract is not locked. + UpdateDefaultRecipient { recipient: Option }, /// Divides any attached funds to the message amongst the recipients list. Send { config: Option> }, } diff --git a/tests-integration/tests/set_amount_splitter.rs b/tests-integration/tests/set_amount_splitter.rs index 0713a206a..ac019da59 100644 --- a/tests-integration/tests/set_amount_splitter.rs +++ b/tests-integration/tests/set_amount_splitter.rs @@ -48,6 +48,7 @@ fn test_splitter() { andr.kernel.addr().clone(), None, None, + None, ); let splitter_app_component = AppComponent { name: "splitter".to_string(), From ccedff1cbc3cedadb81d04738d310ec84749fcdc Mon Sep 17 00:00:00 2001 From: Joe Monem Date: Thu, 5 Dec 2024 17:32:52 +0200 Subject: [PATCH 14/27] feat: add cw20 support for splitter + integration tests --- Cargo.lock | 2 + .../finance/andromeda-splitter/Cargo.toml | 1 + .../andromeda-splitter/src/contract.rs | 119 +++++++++- .../andromeda-cw20/src/contract.rs | 1 - packages/andromeda-finance/Cargo.toml | 1 + packages/andromeda-finance/src/splitter.rs | 23 +- tests-integration/tests/splitter.rs | 219 +++++++++++++++++- 7 files changed, 343 insertions(+), 23 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 3bcaf3d92..7d5b57c8b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -489,6 +489,7 @@ dependencies = [ "cw-multi-test", "cw-orch", "cw-utils 1.0.3", + "cw20 1.1.2", "cw3 1.1.2", "cw4", "cw721 0.18.0", @@ -828,6 +829,7 @@ dependencies = [ "cw-orch", "cw-storage-plus 1.2.0", "cw-utils 1.0.3", + "cw20 1.1.2", ] [[package]] diff --git a/contracts/finance/andromeda-splitter/Cargo.toml b/contracts/finance/andromeda-splitter/Cargo.toml index 7771b156b..9b7d84639 100644 --- a/contracts/finance/andromeda-splitter/Cargo.toml +++ b/contracts/finance/andromeda-splitter/Cargo.toml @@ -20,6 +20,7 @@ cosmwasm-std = { workspace = true } cosmwasm-schema = { workspace = true } cw-storage-plus = { workspace = true } cw-utils = { workspace = true } +cw20 = { workspace = true } andromeda-std = { workspace = true } andromeda-finance = { workspace = true } diff --git a/contracts/finance/andromeda-splitter/src/contract.rs b/contracts/finance/andromeda-splitter/src/contract.rs index df1ee7c9d..a26cfbd5b 100644 --- a/contracts/finance/andromeda-splitter/src/contract.rs +++ b/contracts/finance/andromeda-splitter/src/contract.rs @@ -1,6 +1,6 @@ use crate::state::SPLITTER; use andromeda_finance::splitter::{ - validate_expiry_duration, validate_recipient_list, AddressPercent, ExecuteMsg, + validate_expiry_duration, validate_recipient_list, AddressPercent, Cw20HookMsg, ExecuteMsg, GetSplitterConfigResponse, InstantiateMsg, QueryMsg, Splitter, }; use andromeda_std::{ @@ -11,9 +11,10 @@ use andromeda_std::{ }; use andromeda_std::{ado_contract::ADOContract, common::context::ExecuteContext}; use cosmwasm_std::{ - attr, ensure, entry_point, BankMsg, Binary, Coin, CosmosMsg, Deps, DepsMut, Env, MessageInfo, + attr, coin, ensure, entry_point, from_json, Binary, Coin, Deps, DepsMut, Env, MessageInfo, Reply, Response, StdError, SubMsg, Uint128, }; +use cw20::{Cw20Coin, Cw20ReceiveMsg}; use cw_utils::nonpayable; // version info for migration info @@ -110,6 +111,7 @@ pub fn handle_execute(mut ctx: ExecuteContext, msg: ExecuteMsg) -> Result { execute_default_recipient(ctx, recipient) } + ExecuteMsg::Receive(receive_msg) => handle_receive_cw20(ctx, receive_msg), ExecuteMsg::Send { config } => execute_send(ctx, config), _ => ADOContract::default().execute(ctx, msg), }?; @@ -119,6 +121,45 @@ pub fn handle_execute(mut ctx: ExecuteContext, msg: ExecuteMsg) -> Result Result { + // let is_valid_cw20 = ADOContract::default() + // .is_permissioned( + // ctx.deps.branch(), + // ctx.env.clone(), + // SEND_CW20_ACTION, + // ctx.info.sender.clone(), + // ) + // .is_ok(); + + // ensure!( + // is_valid_cw20, + // ContractError::InvalidAsset { + // asset: ctx.info.sender.into_string() + // } + // ); + + let ExecuteContext { ref info, .. } = ctx; + nonpayable(info)?; + + let asset_sent = info.sender.clone().into_string(); + let amount_sent = receive_msg.amount; + // let sender = receive_msg.sender; + + ensure!( + !amount_sent.is_zero(), + ContractError::InvalidFunds { + msg: "Cannot send a 0 amount".to_string() + } + ); + + match from_json(&receive_msg.msg)? { + Cw20HookMsg::Send { config } => execute_send_cw20(ctx, amount_sent, asset_sent, config), + } +} + fn execute_send( ctx: ExecuteContext, config: Option>, @@ -194,14 +235,9 @@ fn execute_send( let remainder_recipient = splitter .default_recipient .unwrap_or(Recipient::new(info.sender.to_string(), None)); - - msgs.push(SubMsg::new(CosmosMsg::Bank(BankMsg::Send { - to_address: remainder_recipient - .address - .get_raw_address(&deps.as_ref())? - .into_string(), - amount: remainder_funds, - }))); + let native_msg = + remainder_recipient.generate_direct_msg(&deps.as_ref(), remainder_funds)?; + msgs.push(native_msg); } let kernel_address = ADOContract::default().get_kernel_address(deps.as_ref().storage)?; @@ -216,6 +252,69 @@ fn execute_send( .add_attribute("sender", info.sender.to_string())) } +fn execute_send_cw20( + ctx: ExecuteContext, + amount: Uint128, + asset: String, + config: Option>, +) -> Result { + let ExecuteContext { deps, info, .. } = ctx; + let splitter = SPLITTER.load(deps.storage)?; + + let splitter_recipients = if let Some(config) = config { + validate_recipient_list(deps.as_ref(), config.clone())?; + config + } else { + splitter.recipients + }; + + let mut msgs: Vec = Vec::new(); + let mut amp_funds: Vec = Vec::new(); + let mut remainder_funds = coin(amount.u128(), asset.clone()); + + for recipient_addr in splitter_recipients { + let recipient_percent = recipient_addr.percent; + let mut vec_coin: Vec = Vec::new(); + let coin = coin(amount.u128(), asset.clone()); + let amount_owed = coin.amount.mul_floor(recipient_percent); + + if !amount_owed.is_zero() { + let mut recip_coin: Coin = coin.clone(); + recip_coin.amount = amount_owed; + remainder_funds.amount = remainder_funds.amount.checked_sub(recip_coin.amount)?; + vec_coin.push(recip_coin.clone()); + amp_funds.push(recip_coin.clone()); + let amp_msg = recipient_addr.recipient.generate_msg_cw20( + &deps.as_ref(), + Cw20Coin { + address: recip_coin.denom.clone(), + amount: recip_coin.amount, + }, + )?; + msgs.push(amp_msg); + } + } + + if !remainder_funds.amount.is_zero() { + let remainder_recipient = splitter + .default_recipient + .unwrap_or(Recipient::new(info.sender.to_string(), None)); + let cw20_msg = remainder_recipient.generate_msg_cw20( + &deps.as_ref(), + Cw20Coin { + address: asset, + amount: remainder_funds.amount, + }, + )?; + msgs.push(cw20_msg); + } + + Ok(Response::new() + .add_submessages(msgs) + .add_attribute("action", "cw20_send") + .add_attribute("sender", info.sender.to_string())) +} + fn execute_update_recipients( ctx: ExecuteContext, recipients: Vec, diff --git a/contracts/fungible-tokens/andromeda-cw20/src/contract.rs b/contracts/fungible-tokens/andromeda-cw20/src/contract.rs index 48818a8bb..517bac9fd 100644 --- a/contracts/fungible-tokens/andromeda-cw20/src/contract.rs +++ b/contracts/fungible-tokens/andromeda-cw20/src/contract.rs @@ -338,7 +338,6 @@ fn handle_send( msg, } }; - let cw20_resp = execute_cw20(deps, env, info, cw20_msg)?; Ok(cw20_resp) } diff --git a/packages/andromeda-finance/Cargo.toml b/packages/andromeda-finance/Cargo.toml index b24996187..c5fe1c76d 100644 --- a/packages/andromeda-finance/Cargo.toml +++ b/packages/andromeda-finance/Cargo.toml @@ -23,6 +23,7 @@ cw3 = { workspace = true } cw4 = { workspace = true } cw721 = { workspace = true } cw721-base = { workspace = true } +cw20 = { workspace = true } schemars = { version = "0.8.10" } andromeda-std = { workspace = true } diff --git a/packages/andromeda-finance/src/splitter.rs b/packages/andromeda-finance/src/splitter.rs index 95bca32b3..2acb108c4 100644 --- a/packages/andromeda-finance/src/splitter.rs +++ b/packages/andromeda-finance/src/splitter.rs @@ -8,6 +8,7 @@ use andromeda_std::{ }; use cosmwasm_schema::{cw_serde, QueryResponses}; use cosmwasm_std::{ensure, BlockInfo, Decimal, Deps}; +use cw20::Cw20ReceiveMsg; #[cw_serde] pub struct AddressPercent { @@ -48,17 +49,31 @@ impl InstantiateMsg { } } +#[cw_serde] +pub enum Cw20HookMsg { + Send { config: Option> }, +} + #[andr_exec] #[cw_serde] pub enum ExecuteMsg { /// Update the recipients list. Only executable by the contract owner when the contract is not locked. - UpdateRecipients { recipients: Vec }, + UpdateRecipients { + recipients: Vec, + }, /// Used to lock/unlock the contract allowing the config to be updated. - UpdateLock { lock_time: Expiry }, + UpdateLock { + lock_time: Expiry, + }, /// Update the default recipient. Only executable by the contract owner when the contract is not locked. - UpdateDefaultRecipient { recipient: Option }, + UpdateDefaultRecipient { + recipient: Option, + }, + Receive(Cw20ReceiveMsg), /// Divides any attached funds to the message amongst the recipients list. - Send { config: Option> }, + Send { + config: Option>, + }, } #[andr_query] diff --git a/tests-integration/tests/splitter.rs b/tests-integration/tests/splitter.rs index 8397fd7b3..44172b281 100644 --- a/tests-integration/tests/splitter.rs +++ b/tests-integration/tests/splitter.rs @@ -1,16 +1,14 @@ use andromeda_app::app::{AppComponent, ComponentType}; use andromeda_app_contract::mock::{mock_andromeda_app, MockAppContract}; - -use andromeda_testing::{mock::mock_app, mock_builder::MockAndromedaBuilder, MockContract}; - -use andromeda_std::amp::Recipient; -use cosmwasm_std::{coin, Decimal, Uint128}; - -use andromeda_finance::splitter::AddressPercent; +use andromeda_cw20::mock::{mock_andromeda_cw20, mock_cw20_instantiate_msg, mock_minter, MockCW20}; +use andromeda_finance::splitter::{AddressPercent, Cw20HookMsg}; use andromeda_splitter::mock::{ mock_andromeda_splitter, mock_splitter_instantiate_msg, MockSplitter, }; - +use andromeda_std::amp::Recipient; +use andromeda_testing::{mock::mock_app, mock_builder::MockAndromedaBuilder, MockContract}; +use cosmwasm_std::{coin, to_json_binary, Decimal, Uint128}; +use cw20::Cw20Coin; use std::str::FromStr; #[test] @@ -99,3 +97,208 @@ fn test_splitter() { let balance_1 = router.wrap().query_balance(recipient_1, "uandr").unwrap(); assert_eq!(balance_1.amount, Uint128::from(200u128 + 500u128)); } + +#[test] +fn test_splitter_cw20() { + let mut router = mock_app(None); + let andr = MockAndromedaBuilder::new(&mut router, "admin") + .with_wallets(vec![ + ("owner", vec![coin(10000, "uandr")]), + ("recipient1", vec![]), + ("recipient2", vec![]), + ]) + .with_contracts(vec![ + ("app-contract", mock_andromeda_app()), + ("splitter", mock_andromeda_splitter()), + ("cw20", mock_andromeda_cw20()), + ]) + .build(&mut router); + let owner = andr.get_wallet("owner"); + let recipient_1 = andr.get_wallet("recipient1"); + let recipient_2 = andr.get_wallet("recipient2"); + + let app_code_id = andr.get_code_id(&mut router, "app-contract"); + + let initial_balances = vec![Cw20Coin { + address: owner.to_string(), + amount: Uint128::new(1000000u128), + }]; + + let cw20_init_msg = mock_cw20_instantiate_msg( + None, + "Test Tokens".to_string(), + "TTT".to_string(), + 6, + initial_balances.clone(), + Some(mock_minter( + owner.to_string(), + Some(Uint128::from(10000000000u128)), + )), + andr.kernel.addr().to_string(), + ); + let cw20_component = AppComponent::new( + "cw20".to_string(), + "cw20".to_string(), + to_json_binary(&cw20_init_msg).unwrap(), + ); + + let splitter_recipients = vec![ + AddressPercent { + recipient: Recipient::from_string(recipient_1.to_string()), + percent: Decimal::from_str("0.2").unwrap(), + }, + AddressPercent { + recipient: Recipient::from_string(recipient_2.to_string()), + percent: Decimal::from_str("0.8").unwrap(), + }, + ]; + + let splitter_init_msg = mock_splitter_instantiate_msg( + splitter_recipients, + andr.kernel.addr().clone(), + None, + None, + None, + ); + let splitter_app_component = AppComponent { + name: "splitter".to_string(), + component_type: ComponentType::new(splitter_init_msg), + ado_type: "splitter".to_string(), + }; + + let app_components = vec![splitter_app_component.clone(), cw20_component.clone()]; + let app = MockAppContract::instantiate( + app_code_id, + owner, + &mut router, + "Splitter App", + app_components, + andr.kernel.addr(), + None, + ); + + let splitter: MockSplitter = + app.query_ado_by_component_name(&router, splitter_app_component.name); + + let cw20: MockCW20 = app.query_ado_by_component_name(&router, cw20_component.name); + + let hook_msg = Cw20HookMsg::Send { config: None }; + + cw20.execute_send( + &mut router, + owner.clone(), + splitter.addr(), + Uint128::new(10), + &hook_msg, + ) + .unwrap(); + + let cw20_balance = cw20.query_balance(&router, recipient_1); + assert_eq!(cw20_balance, Uint128::from(2u128)); + let cw20_balance = cw20.query_balance(&router, recipient_2); + assert_eq!(cw20_balance, Uint128::from(8u128)); +} +#[test] +fn test_splitter_cw20_with_remainder() { + let mut router = mock_app(None); + let andr = MockAndromedaBuilder::new(&mut router, "admin") + .with_wallets(vec![ + ("owner", vec![coin(10000, "uandr")]), + ("recipient1", vec![]), + ("recipient2", vec![]), + ("recipient3", vec![]), + ]) + .with_contracts(vec![ + ("app-contract", mock_andromeda_app()), + ("splitter", mock_andromeda_splitter()), + ("cw20", mock_andromeda_cw20()), + ]) + .build(&mut router); + let owner = andr.get_wallet("owner"); + let recipient_1 = andr.get_wallet("recipient1"); + let recipient_2 = andr.get_wallet("recipient2"); + let recipient_3 = andr.get_wallet("recipient3"); + + let app_code_id = andr.get_code_id(&mut router, "app-contract"); + + let initial_balances = vec![Cw20Coin { + address: owner.to_string(), + amount: Uint128::new(1000000u128), + }]; + + let cw20_init_msg = mock_cw20_instantiate_msg( + None, + "Test Tokens".to_string(), + "TTT".to_string(), + 6, + initial_balances.clone(), + Some(mock_minter( + owner.to_string(), + Some(Uint128::from(10000000000u128)), + )), + andr.kernel.addr().to_string(), + ); + let cw20_component = AppComponent::new( + "cw20".to_string(), + "cw20".to_string(), + to_json_binary(&cw20_init_msg).unwrap(), + ); + + let splitter_recipients = vec![ + AddressPercent { + recipient: Recipient::from_string(recipient_1.to_string()), + percent: Decimal::from_str("0.2").unwrap(), + }, + AddressPercent { + recipient: Recipient::from_string(recipient_2.to_string()), + percent: Decimal::from_str("0.3").unwrap(), + }, + ]; + + let splitter_init_msg = mock_splitter_instantiate_msg( + splitter_recipients, + andr.kernel.addr().clone(), + None, + None, + Some(Recipient::from_string(recipient_3.to_string())), + ); + let splitter_app_component = AppComponent { + name: "splitter".to_string(), + component_type: ComponentType::new(splitter_init_msg), + ado_type: "splitter".to_string(), + }; + + let app_components = vec![splitter_app_component.clone(), cw20_component.clone()]; + let app = MockAppContract::instantiate( + app_code_id, + owner, + &mut router, + "Splitter App", + app_components, + andr.kernel.addr(), + None, + ); + + let splitter: MockSplitter = + app.query_ado_by_component_name(&router, splitter_app_component.name); + + let cw20: MockCW20 = app.query_ado_by_component_name(&router, cw20_component.name); + + let hook_msg = Cw20HookMsg::Send { config: None }; + + cw20.execute_send( + &mut router, + owner.clone(), + splitter.addr(), + Uint128::new(10), + &hook_msg, + ) + .unwrap(); + + let cw20_balance = cw20.query_balance(&router, recipient_1); + assert_eq!(cw20_balance, Uint128::from(2u128)); + let cw20_balance = cw20.query_balance(&router, recipient_2); + assert_eq!(cw20_balance, Uint128::from(3u128)); + let cw20_balance = cw20.query_balance(&router, recipient_3); + assert_eq!(cw20_balance, Uint128::from(5u128)); +} From 17f6ffdfe9c2d63bd74fbe8bf7cb58b37bbdc921 Mon Sep 17 00:00:00 2001 From: Joe Monem Date: Fri, 6 Dec 2024 10:55:43 +0200 Subject: [PATCH 15/27] chore: version bump and changelog entry --- CHANGELOG.md | 1 + Cargo.lock | 2 +- contracts/finance/andromeda-splitter/Cargo.toml | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index dde925de9..131f8a050 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -28,6 +28,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Added Restake and Redelegate to Validator Staking [(#622)](https://github.com/andromedaprotocol/andromeda-core/pull/622) - Added andromeda-math and andromeda-account packages[(#672)](https://github.com/andromedaprotocol/andromeda-core/pull/672) - Added optional config for Send in Splitter contracts [(#686)](https://github.com/andromedaprotocol/andromeda-core/pull/686) +- Added CW20 suppport in Splitter contracts [(#703)](https://github.com/andromedaprotocol/andromeda-core/pull/703) ### Changed diff --git a/Cargo.lock b/Cargo.lock index 7d5b57c8b..5889308de 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -817,7 +817,7 @@ dependencies = [ [[package]] name = "andromeda-splitter" -version = "2.2.0" +version = "3.0.0" dependencies = [ "andromeda-app", "andromeda-finance", diff --git a/contracts/finance/andromeda-splitter/Cargo.toml b/contracts/finance/andromeda-splitter/Cargo.toml index 9b7d84639..22b25555e 100644 --- a/contracts/finance/andromeda-splitter/Cargo.toml +++ b/contracts/finance/andromeda-splitter/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "andromeda-splitter" -version = "2.2.0" +version = "3.0.0" edition = "2021" rust-version = "1.75.0" From 66da425e7a58dd994bfe1d3229198c7dfe27a758 Mon Sep 17 00:00:00 2001 From: Joe Monem <66594578+joemonem@users.noreply.github.com> Date: Fri, 6 Dec 2024 10:57:45 +0200 Subject: [PATCH 16/27] feat: Splitter Default Recipient (#698) --- .../src/contract.rs | 67 ++++++++++++++++-- .../andromeda-set-amount-splitter/src/mock.rs | 14 +++- .../src/testing/tests.rs | 8 +++ .../andromeda-splitter/src/contract.rs | 66 ++++++++++++++++-- .../finance/andromeda-splitter/src/mock.rs | 13 +++- .../andromeda-splitter/src/testing/tests.rs | 69 ++++++++++++++++++- .../src/contract.rs | 69 +++++++++++++++++-- .../src/testing/tests.rs | 30 ++++++++ ibc-tests/tests/crowdfund.rs | 1 + .../src/set_amount_splitter.rs | 6 ++ packages/andromeda-finance/src/splitter.rs | 5 ++ .../src/weighted_splitter.rs | 5 ++ tests-integration/tests/auction_app.rs | 1 + tests-integration/tests/crowdfund_app.rs | 9 ++- tests-integration/tests/kernel.rs | 1 + tests-integration/tests/kernel_orch.rs | 2 + tests-integration/tests/marketplace_app.rs | 1 + .../tests/set_amount_splitter.rs | 1 + tests-integration/tests/splitter.rs | 9 ++- 19 files changed, 347 insertions(+), 30 deletions(-) diff --git a/contracts/finance/andromeda-set-amount-splitter/src/contract.rs b/contracts/finance/andromeda-set-amount-splitter/src/contract.rs index d0b3a517c..ec6f353e4 100644 --- a/contracts/finance/andromeda-set-amount-splitter/src/contract.rs +++ b/contracts/finance/andromeda-set-amount-splitter/src/contract.rs @@ -10,7 +10,7 @@ use andromeda_finance::{ }; use andromeda_std::{ ado_base::{InstantiateMsg as BaseInstantiateMsg, MigrateMsg}, - amp::messages::AMPPkt, + amp::{messages::AMPPkt, Recipient}, common::{actions::call_action, encode_binary, expiration::Expiry, Milliseconds}, error::ContractError, }; @@ -52,6 +52,7 @@ pub fn instantiate( Splitter { recipients: msg.recipients.clone(), lock: lock_time.get_time(&env.block), + default_recipient: msg.default_recipient.clone(), } } None => { @@ -59,6 +60,7 @@ pub fn instantiate( recipients: msg.recipients.clone(), // If locking isn't desired upon instantiation, it's automatically set to 0 lock: Milliseconds::default(), + default_recipient: msg.default_recipient.clone(), } } }; @@ -124,6 +126,9 @@ pub fn handle_execute(mut ctx: ExecuteContext, msg: ExecuteMsg) -> Result execute_update_recipients(ctx, recipients), ExecuteMsg::UpdateLock { lock_time } => execute_update_lock(ctx, lock_time), + ExecuteMsg::UpdateDefaultRecipient { recipient } => { + execute_update_default_recipient(ctx, recipient) + } ExecuteMsg::Send { config } => execute_send(ctx, config), _ => ADOContract::default().execute(ctx, msg), }?; @@ -133,6 +138,49 @@ pub fn handle_execute(mut ctx: ExecuteContext, msg: ExecuteMsg) -> Result, +) -> Result { + let ExecuteContext { + deps, info, env, .. + } = ctx; + + nonpayable(&info)?; + + ensure!( + ADOContract::default().is_owner_or_operator(deps.storage, info.sender.as_str())?, + ContractError::Unauthorized {} + ); + + let mut splitter = SPLITTER.load(deps.storage)?; + + // Can't call this function while the lock isn't expired + ensure!( + splitter.lock.is_expired(&env.block), + ContractError::ContractLocked {} + ); + + if let Some(ref recipient) = recipient { + recipient.validate(&deps.as_ref())?; + } + splitter.default_recipient = recipient; + + SPLITTER.save(deps.storage, &splitter)?; + + Ok(Response::default().add_attributes(vec![ + attr("action", "update_default_recipient"), + attr( + "recipient", + splitter + .default_recipient + .map_or("no default recipient".to_string(), |r| { + r.address.to_string() + }), + ), + ])) +} + fn execute_send( ctx: ExecuteContext, config: Option>, @@ -161,12 +209,12 @@ fn execute_send( ); denom_set.insert(coin.denom); } - - let splitter = if let Some(config) = config { + let splitter = SPLITTER.load(deps.storage)?; + let splitter_recipients = if let Some(config) = config { validate_recipient_list(deps.as_ref(), config.clone())?; config } else { - SPLITTER.load(deps.storage)?.recipients + splitter.recipients }; let mut msgs: Vec = Vec::new(); @@ -179,7 +227,7 @@ fn execute_send( let mut remainder_funds = coin.amount; let denom = coin.denom; - for recipient in splitter.clone() { + for recipient in splitter_recipients.clone() { // Find the recipient's corresponding denom for the current iteration of the sent funds let recipient_coin = recipient .coins @@ -208,8 +256,15 @@ fn execute_send( // Refund message for sender if !remainder_funds.is_zero() { + let remainder_recipient = splitter + .default_recipient + .clone() + .unwrap_or(Recipient::new(info.sender.to_string(), None)); let msg = SubMsg::new(CosmosMsg::Bank(BankMsg::Send { - to_address: info.sender.clone().into_string(), + to_address: remainder_recipient + .address + .get_raw_address(&deps.as_ref())? + .into_string(), amount: coins(remainder_funds.u128(), denom), })); msgs.push(msg); diff --git a/contracts/finance/andromeda-set-amount-splitter/src/mock.rs b/contracts/finance/andromeda-set-amount-splitter/src/mock.rs index 5073ed77b..1464acd70 100644 --- a/contracts/finance/andromeda-set-amount-splitter/src/mock.rs +++ b/contracts/finance/andromeda-set-amount-splitter/src/mock.rs @@ -2,7 +2,7 @@ use crate::contract::{execute, instantiate, query, reply}; use andromeda_finance::set_amount_splitter::{AddressAmount, ExecuteMsg, InstantiateMsg, QueryMsg}; -use andromeda_std::common::expiration::Expiry; +use andromeda_std::{amp::Recipient, common::expiration::Expiry}; use andromeda_testing::{ mock::MockApp, mock_ado, mock_contract::ExecuteResult, MockADO, MockContract, }; @@ -21,9 +21,15 @@ impl MockSetAmountSplitter { kernel_address: impl Into, lock_time: Option, owner: Option, + default_recipient: Option, ) -> Self { - let msg = - mock_set_amount_splitter_instantiate_msg(recipients, kernel_address, lock_time, owner); + let msg = mock_set_amount_splitter_instantiate_msg( + recipients, + kernel_address, + lock_time, + owner, + default_recipient, + ); let res = app.instantiate_contract(code_id, sender, &msg, &[], "Andromeda Splitter", None); Self(res.unwrap()) @@ -52,12 +58,14 @@ pub fn mock_set_amount_splitter_instantiate_msg( kernel_address: impl Into, lock_time: Option, owner: Option, + default_recipient: Option, ) -> InstantiateMsg { InstantiateMsg { recipients, lock_time, kernel_address: kernel_address.into(), owner, + default_recipient, } } diff --git a/contracts/finance/andromeda-set-amount-splitter/src/testing/tests.rs b/contracts/finance/andromeda-set-amount-splitter/src/testing/tests.rs index 9c0bad5c3..bdb3afe79 100644 --- a/contracts/finance/andromeda-set-amount-splitter/src/testing/tests.rs +++ b/contracts/finance/andromeda-set-amount-splitter/src/testing/tests.rs @@ -35,6 +35,7 @@ fn init(deps: DepsMut) -> Response { kernel_address: MOCK_KERNEL_CONTRACT.to_string(), recipients: mock_recipient, lock_time: Some(Expiry::AtTime(Milliseconds::from_seconds(100_000))), + default_recipient: None, }; let info = mock_info("owner", &[]); @@ -63,6 +64,7 @@ fn test_execute_update_lock() { let splitter = Splitter { recipients: vec![], lock: Milliseconds::from_seconds(current_time - 1), + default_recipient: None, }; SPLITTER.save(deps.as_mut().storage, &splitter).unwrap(); @@ -101,6 +103,7 @@ fn test_execute_update_recipients() { let splitter = Splitter { recipients: vec![], lock: Milliseconds::from_seconds(0), + default_recipient: None, }; SPLITTER.save(deps.as_mut().storage, &splitter).unwrap(); @@ -232,6 +235,7 @@ fn test_execute_send() { let splitter = Splitter { recipients: recipient, lock: Milliseconds::default(), + default_recipient: None, }; SPLITTER.save(deps.as_mut().storage, &splitter).unwrap(); @@ -355,6 +359,7 @@ fn test_execute_send_ado_recipient() { let splitter = Splitter { recipients: recipient, lock: Milliseconds::default(), + default_recipient: None, }; SPLITTER.save(deps.as_mut().storage, &splitter).unwrap(); @@ -414,6 +419,7 @@ fn test_handle_packet_exit_with_error_true() { let splitter = Splitter { recipients: recipient, lock: Milliseconds::default(), + default_recipient: None, }; SPLITTER.save(deps.as_mut().storage, &splitter).unwrap(); @@ -435,6 +441,7 @@ fn test_query_splitter() { let splitter = Splitter { recipients: vec![], lock: Milliseconds::default(), + default_recipient: None, }; SPLITTER.save(deps.as_mut().storage, &splitter).unwrap(); @@ -486,6 +493,7 @@ fn test_execute_send_error() { let splitter = Splitter { recipients: recipient, lock: Milliseconds::default(), + default_recipient: None, }; SPLITTER.save(deps.as_mut().storage, &splitter).unwrap(); diff --git a/contracts/finance/andromeda-splitter/src/contract.rs b/contracts/finance/andromeda-splitter/src/contract.rs index a46e91042..76bb7181d 100644 --- a/contracts/finance/andromeda-splitter/src/contract.rs +++ b/contracts/finance/andromeda-splitter/src/contract.rs @@ -5,7 +5,7 @@ use andromeda_finance::splitter::{ }; use andromeda_std::{ ado_base::{InstantiateMsg as BaseInstantiateMsg, MigrateMsg}, - amp::messages::AMPPkt, + amp::{messages::AMPPkt, Recipient}, common::{actions::call_action, encode_binary, expiration::Expiry, Milliseconds}, error::ContractError, }; @@ -33,6 +33,7 @@ pub fn instantiate( Splitter { recipients: msg.recipients.clone(), lock: time, + default_recipient: msg.default_recipient.clone(), } } None => { @@ -40,6 +41,7 @@ pub fn instantiate( recipients: msg.recipients.clone(), // If locking isn't desired upon instantiation, it's automatically set to 0 lock: Milliseconds::default(), + default_recipient: msg.default_recipient.clone(), } } }; @@ -105,6 +107,9 @@ pub fn handle_execute(mut ctx: ExecuteContext, msg: ExecuteMsg) -> Result execute_update_recipients(ctx, recipients), ExecuteMsg::UpdateLock { lock_time } => execute_update_lock(ctx, lock_time), + ExecuteMsg::UpdateDefaultRecipient { recipient } => { + execute_update_default_recipient(ctx, recipient) + } ExecuteMsg::Send { config } => execute_send(ctx, config), _ => ADOContract::default().execute(ctx, msg), }?; @@ -133,12 +138,13 @@ fn execute_send( } ); } + let splitter = SPLITTER.load(deps.storage)?; - let splitter = if let Some(config) = config { + let splitter_recipients = if let Some(config) = config { validate_recipient_list(deps.as_ref(), config.clone())?; config } else { - SPLITTER.load(deps.storage)?.recipients + splitter.recipients }; let mut msgs: Vec = Vec::new(); @@ -156,7 +162,7 @@ fn execute_send( let mut pkt = AMPPkt::from_ctx(ctx.amp_ctx, ctx.env.contract.address.to_string()); - for recipient_addr in splitter { + for recipient_addr in splitter_recipients { let recipient_percent = recipient_addr.percent; let mut vec_coin: Vec = Vec::new(); for (i, coin) in info.funds.clone().iter().enumerate() { @@ -185,8 +191,15 @@ fn execute_send( // From tests, it looks like owner of smart contract (Andromeda) will recieve the rest of funds. // If so, should be documented if !remainder_funds.is_empty() { + let remainder_recipient = splitter + .default_recipient + .unwrap_or(Recipient::new(info.sender.to_string(), None)); + msgs.push(SubMsg::new(CosmosMsg::Bank(BankMsg::Send { - to_address: info.sender.to_string(), + to_address: remainder_recipient + .address + .get_raw_address(&deps.as_ref())? + .into_string(), amount: remainder_funds, }))); } @@ -271,6 +284,49 @@ fn execute_update_lock(ctx: ExecuteContext, lock_time: Expiry) -> Result, +) -> Result { + let ExecuteContext { + deps, info, env, .. + } = ctx; + + nonpayable(&info)?; + + ensure!( + ADOContract::default().is_owner_or_operator(deps.storage, info.sender.as_str())?, + ContractError::Unauthorized {} + ); + + let mut splitter = SPLITTER.load(deps.storage)?; + + // Can't call this function while the lock isn't expired + ensure!( + splitter.lock.is_expired(&env.block), + ContractError::ContractLocked {} + ); + + if let Some(ref recipient) = recipient { + recipient.validate(&deps.as_ref())?; + } + splitter.default_recipient = recipient; + + SPLITTER.save(deps.storage, &splitter)?; + + Ok(Response::default().add_attributes(vec![ + attr("action", "update_default_recipient"), + attr( + "recipient", + splitter + .default_recipient + .map_or("no default recipient".to_string(), |r| { + r.address.to_string() + }), + ), + ])) +} + #[cfg_attr(not(feature = "library"), entry_point)] pub fn migrate(deps: DepsMut, _env: Env, _msg: MigrateMsg) -> Result { ADOContract::default().migrate(deps, CONTRACT_NAME, CONTRACT_VERSION) diff --git a/contracts/finance/andromeda-splitter/src/mock.rs b/contracts/finance/andromeda-splitter/src/mock.rs index fdf96b9b1..06324fc07 100644 --- a/contracts/finance/andromeda-splitter/src/mock.rs +++ b/contracts/finance/andromeda-splitter/src/mock.rs @@ -2,7 +2,7 @@ use crate::contract::{execute, instantiate, query, reply}; use andromeda_finance::splitter::{AddressPercent, ExecuteMsg, InstantiateMsg, QueryMsg}; -use andromeda_std::common::expiration::Expiry; +use andromeda_std::{amp::Recipient, common::expiration::Expiry}; use andromeda_testing::{ mock::MockApp, mock_ado, mock_contract::ExecuteResult, MockADO, MockContract, }; @@ -21,8 +21,15 @@ impl MockSplitter { kernel_address: impl Into, lock_time: Option, owner: Option, + default_recipient: Option, ) -> Self { - let msg = mock_splitter_instantiate_msg(recipients, kernel_address, lock_time, owner); + let msg = mock_splitter_instantiate_msg( + recipients, + kernel_address, + lock_time, + owner, + default_recipient, + ); let res = app.instantiate_contract(code_id, sender, &msg, &[], "Andromeda Splitter", None); Self(res.unwrap()) @@ -51,12 +58,14 @@ pub fn mock_splitter_instantiate_msg( kernel_address: impl Into, lock_time: Option, owner: Option, + default_recipient: Option, ) -> InstantiateMsg { InstantiateMsg { recipients, lock_time, kernel_address: kernel_address.into(), owner, + default_recipient, } } diff --git a/contracts/finance/andromeda-splitter/src/testing/tests.rs b/contracts/finance/andromeda-splitter/src/testing/tests.rs index 85fd50d55..2093922e6 100644 --- a/contracts/finance/andromeda-splitter/src/testing/tests.rs +++ b/contracts/finance/andromeda-splitter/src/testing/tests.rs @@ -35,6 +35,7 @@ fn init(deps: DepsMut) -> Response { kernel_address: MOCK_KERNEL_CONTRACT.to_string(), recipients: mock_recipient, lock_time: Some(Expiry::FromNow(Milliseconds(86400000))), + default_recipient: None, }; let info = mock_info("owner", &[]); @@ -62,6 +63,7 @@ fn test_different_lock_times() { kernel_address: MOCK_KERNEL_CONTRACT.to_string(), recipients: vec![], lock_time: Some(lock_time), + default_recipient: None, }; let info = mock_info(OWNER, &[]); @@ -77,6 +79,7 @@ fn test_different_lock_times() { kernel_address: MOCK_KERNEL_CONTRACT.to_string(), recipients: vec![], lock_time: Some(lock_time), + default_recipient: None, }; let err = instantiate(deps.as_mut(), env.clone(), info.clone(), msg).unwrap_err(); @@ -94,6 +97,7 @@ fn test_different_lock_times() { percent: Decimal::percent(100), }], lock_time: Some(lock_time), + default_recipient: None, }; let info = mock_info(OWNER, &[]); @@ -109,6 +113,7 @@ fn test_different_lock_times() { kernel_address: MOCK_KERNEL_CONTRACT.to_string(), recipients: vec![], lock_time: Some(lock_time), + default_recipient: None, }; let info = mock_info(OWNER, &[]); @@ -123,6 +128,7 @@ fn test_different_lock_times() { kernel_address: MOCK_KERNEL_CONTRACT.to_string(), recipients: vec![], lock_time: Some(lock_time), + default_recipient: None, }; let info = mock_info(OWNER, &[]); @@ -140,6 +146,7 @@ fn test_different_lock_times() { percent: Decimal::percent(100), }], lock_time: Some(lock_time), + default_recipient: None, }; let info = mock_info(OWNER, &[]); @@ -162,6 +169,7 @@ fn test_execute_update_lock() { let splitter = Splitter { recipients: vec![], lock: Milliseconds::from_seconds(current_time - 1), + default_recipient: None, }; SPLITTER.save(deps.as_mut().storage, &splitter).unwrap(); @@ -200,6 +208,7 @@ fn test_execute_update_recipients() { let splitter = Splitter { recipients: vec![], lock: Milliseconds::from_seconds(0), + default_recipient: None, }; SPLITTER.save(deps.as_mut().storage, &splitter).unwrap(); @@ -276,7 +285,7 @@ fn test_execute_send() { let recip1 = Recipient::from_string(recip_address1); let recip2 = Recipient::from_string(recip_address2); - let recip3 = Recipient::from_string(recip_address3); + let recip3 = Recipient::from_string(recip_address3.clone()); let config_recipient = vec![AddressPercent { recipient: recip3.clone(), @@ -315,8 +324,9 @@ fn test_execute_send() { .unwrap(); let splitter = Splitter { - recipients: recipient, + recipients: recipient.clone(), lock: Milliseconds::default(), + default_recipient: None, }; SPLITTER.save(deps.as_mut().storage, &splitter).unwrap(); @@ -343,7 +353,7 @@ fn test_execute_send() { let msg = ExecuteMsg::Send { config: Some(config_recipient), }; - let res = execute(deps.as_mut(), env, info, msg).unwrap(); + let res = execute(deps.as_mut(), env.clone(), info.clone(), msg).unwrap(); let amp_msg_1 = recip3 .generate_amp_msg(&deps.as_ref(), Some(vec![Coin::new(5000, "uluna")])) .unwrap(); @@ -369,6 +379,55 @@ fn test_execute_send() { amount: vec![Coin::new(5000, "uluna")], // 10000 * 0.5 remainder }), ), + amp_msg.clone(), + ]) + .add_attributes(vec![attr("action", "send"), attr("sender", "creator")]) + .add_submessage(generate_economics_message(OWNER, "Send")); + + assert_eq!(res, expected_res); + + // Test send with default recipient + let msg = ExecuteMsg::Send { config: None }; + SPLITTER + .save( + deps.as_mut().storage, + &Splitter { + recipients: recipient, + lock: Milliseconds::default(), + default_recipient: Some(recip3.clone()), + }, + ) + .unwrap(); + + let res = execute(deps.as_mut(), env.clone(), info.clone(), msg).unwrap(); + let amp_msg_1 = recip1 + .generate_amp_msg(&deps.as_ref(), Some(vec![Coin::new(1000, "uluna")])) + .unwrap(); + let amp_msg_2 = recip2 + .generate_amp_msg(&deps.as_ref(), Some(vec![Coin::new(2000, "uluna")])) + .unwrap(); + let amp_pkt = AMPPkt::new( + MOCK_CONTRACT_ADDR.to_string(), + MOCK_CONTRACT_ADDR.to_string(), + vec![amp_msg_1, amp_msg_2], + ); + let amp_msg = amp_pkt + .to_sub_msg( + MOCK_KERNEL_CONTRACT, + Some(vec![Coin::new(1000, "uluna"), Coin::new(2000, "uluna")]), + 1, + ) + .unwrap(); + + let expected_res = Response::new() + .add_submessages(vec![ + SubMsg::new( + // refunds remainder to sender + CosmosMsg::Bank(BankMsg::Send { + to_address: recip_address3, + amount: vec![Coin::new(7000, "uluna")], // 10000 * 0.7 remainder + }), + ), amp_msg, ]) .add_attributes(vec![attr("action", "send"), attr("sender", "creator")]) @@ -429,6 +488,7 @@ fn test_execute_send_ado_recipient() { let splitter = Splitter { recipients: recipient, lock: Milliseconds::default(), + default_recipient: None, }; SPLITTER.save(deps.as_mut().storage, &splitter).unwrap(); @@ -491,6 +551,7 @@ fn test_handle_packet_exit_with_error_true() { let splitter = Splitter { recipients: recipient, lock: Milliseconds::default(), + default_recipient: None, }; SPLITTER.save(deps.as_mut().storage, &splitter).unwrap(); @@ -512,6 +573,7 @@ fn test_query_splitter() { let splitter = Splitter { recipients: vec![], lock: Milliseconds::default(), + default_recipient: None, }; SPLITTER.save(deps.as_mut().storage, &splitter).unwrap(); @@ -565,6 +627,7 @@ fn test_execute_send_error() { let splitter = Splitter { recipients: recipient, lock: Milliseconds::default(), + default_recipient: None, }; SPLITTER.save(deps.as_mut().storage, &splitter).unwrap(); diff --git a/contracts/finance/andromeda-weighted-distribution-splitter/src/contract.rs b/contracts/finance/andromeda-weighted-distribution-splitter/src/contract.rs index 7617c2ba3..8d8eceef9 100644 --- a/contracts/finance/andromeda-weighted-distribution-splitter/src/contract.rs +++ b/contracts/finance/andromeda-weighted-distribution-splitter/src/contract.rs @@ -46,6 +46,7 @@ pub fn instantiate( Splitter { recipients: msg.recipients, lock: time, + default_recipient: msg.default_recipient, } } None => { @@ -53,6 +54,7 @@ pub fn instantiate( recipients: msg.recipients, // If locking isn't desired upon instantiation, it's automatically set to 0 lock: Milliseconds::default(), + default_recipient: msg.default_recipient, } } }; @@ -109,7 +111,9 @@ pub fn handle_execute(mut ctx: ExecuteContext, msg: ExecuteMsg) -> Result execute_add_recipient(ctx, recipient), ExecuteMsg::RemoveRecipient { recipient } => execute_remove_recipient(ctx, recipient), ExecuteMsg::UpdateLock { lock_time } => execute_update_lock(ctx, lock_time), - + ExecuteMsg::UpdateDefaultRecipient { recipient } => { + execute_update_default_recipient(ctx, recipient) + } ExecuteMsg::Send { config } => execute_send(ctx, config), _ => ADOContract::default().execute(ctx, msg), @@ -163,6 +167,49 @@ pub fn execute_update_recipient_weight( Ok(Response::default().add_attribute("action", "updated_recipient_weight")) } +fn execute_update_default_recipient( + ctx: ExecuteContext, + recipient: Option, +) -> Result { + let ExecuteContext { + deps, info, env, .. + } = ctx; + + nonpayable(&info)?; + + ensure!( + ADOContract::default().is_owner_or_operator(deps.storage, info.sender.as_str())?, + ContractError::Unauthorized {} + ); + + let mut splitter = SPLITTER.load(deps.storage)?; + + // Can't call this function while the lock isn't expired + ensure!( + splitter.lock.is_expired(&env.block), + ContractError::ContractLocked {} + ); + + if let Some(ref recipient) = recipient { + recipient.validate(&deps.as_ref())?; + } + splitter.default_recipient = recipient; + + SPLITTER.save(deps.storage, &splitter)?; + + Ok(Response::default().add_attributes(vec![ + attr("action", "update_default_recipient"), + attr( + "recipient", + splitter + .default_recipient + .map_or("no default recipient".to_string(), |r| { + r.address.to_string() + }), + ), + ])) +} + pub fn execute_add_recipient( ctx: ExecuteContext, recipient: AddressWeight, @@ -215,6 +262,7 @@ pub fn execute_add_recipient( let new_splitter = Splitter { recipients: splitter.recipients, lock: splitter.lock, + default_recipient: splitter.default_recipient, }; SPLITTER.save(deps.storage, &new_splitter)?; @@ -238,27 +286,27 @@ fn execute_send( info.funds.len() < 5, ContractError::ExceedsMaxAllowedCoins {} ); - - let splitter = if let Some(config) = config { + let splitter = SPLITTER.load(deps.storage)?; + let splitter_recipients = if let Some(config) = config { // Max 100 recipients ensure!(config.len() <= 100, ContractError::ReachedRecipientLimit {}); config } else { - SPLITTER.load(deps.storage)?.recipients + splitter.recipients }; let mut msgs: Vec = Vec::new(); let mut remainder_funds = info.funds.clone(); let mut total_weight = Uint128::zero(); // Calculate the total weight of all recipients - for recipient_addr in &splitter { + for recipient_addr in &splitter_recipients { let recipient_weight = recipient_addr.weight; total_weight = total_weight.checked_add(recipient_weight)?; } // Each recipient recieves the funds * (the recipient's weight / total weight of all recipients) // The remaining funds go to the sender of the function - for recipient_addr in &splitter { + for recipient_addr in &splitter_recipients { let recipient_weight = recipient_addr.weight; let mut vec_coin: Vec = Vec::new(); for (i, coin) in info.funds.iter().enumerate() { @@ -277,8 +325,14 @@ fn execute_send( remainder_funds.retain(|x| x.amount > Uint128::zero()); if !remainder_funds.is_empty() { + let remainder_recipient = splitter + .default_recipient + .unwrap_or(Recipient::new(info.sender.to_string(), None)); msgs.push(SubMsg::new(CosmosMsg::Bank(BankMsg::Send { - to_address: info.sender.to_string(), + to_address: remainder_recipient + .address + .get_raw_address(&deps.as_ref())? + .into_string(), amount: remainder_funds, }))); } @@ -402,6 +456,7 @@ fn execute_remove_recipient( let new_splitter = Splitter { recipients: splitter.recipients, lock: splitter.lock, + default_recipient: splitter.default_recipient, }; SPLITTER.save(deps.storage, &new_splitter)?; }; diff --git a/contracts/finance/andromeda-weighted-distribution-splitter/src/testing/tests.rs b/contracts/finance/andromeda-weighted-distribution-splitter/src/testing/tests.rs index 09af1bdeb..99f7cb150 100644 --- a/contracts/finance/andromeda-weighted-distribution-splitter/src/testing/tests.rs +++ b/contracts/finance/andromeda-weighted-distribution-splitter/src/testing/tests.rs @@ -33,6 +33,7 @@ fn init(deps: DepsMut) -> Response { kernel_address: MOCK_KERNEL_CONTRACT.to_string(), recipients: mock_recipient, lock_time: Some(Expiry::FromNow(Milliseconds(86400000))), + default_recipient: None, }; let info = mock_info(OWNER, &[]); @@ -57,6 +58,7 @@ fn test_update_app_contract() { lock_time: None, kernel_address: MOCK_KERNEL_CONTRACT.to_string(), owner: None, + default_recipient: None, }; let _res = instantiate(deps.as_mut(), mock_env(), info.clone(), msg).unwrap(); @@ -126,6 +128,7 @@ fn test_instantiate() { lock_time: None, kernel_address: MOCK_KERNEL_CONTRACT.to_string(), owner: None, + default_recipient: None, }; let res = instantiate(deps.as_mut(), env, info, msg).unwrap(); assert_eq!(0, res.messages.len()); @@ -146,6 +149,7 @@ fn test_execute_update_lock() { let splitter = Splitter { recipients: vec![], lock: Milliseconds(current_time - 1), + default_recipient: None, }; SPLITTER.save(deps.as_mut().storage, &splitter).unwrap(); @@ -203,6 +207,7 @@ fn test_execute_update_lock_too_short() { let splitter = Splitter { recipients: vec![], lock: Milliseconds(current_time - 1), + default_recipient: None, }; SPLITTER.save(deps.as_mut().storage, &splitter).unwrap(); @@ -247,6 +252,7 @@ fn test_execute_update_lock_too_long() { let splitter = Splitter { recipients: vec![], lock: Milliseconds(current_time - 1), + default_recipient: None, }; SPLITTER.save(deps.as_mut().storage, &splitter).unwrap(); @@ -292,6 +298,7 @@ fn test_execute_update_lock_already_locked() { let splitter = Splitter { recipients: vec![], lock: Milliseconds::default().plus_seconds(current_time + 10_000), + default_recipient: None, }; SPLITTER.save(deps.as_mut().storage, &splitter).unwrap(); @@ -336,6 +343,7 @@ fn test_execute_update_lock_unauthorized() { let splitter = Splitter { recipients: vec![], lock: new_lock, + default_recipient: None, }; SPLITTER.save(deps.as_mut().storage, &splitter).unwrap(); @@ -414,6 +422,7 @@ fn test_execute_remove_recipient() { let splitter = Splitter { recipients: recipient, lock: Milliseconds::default(), + default_recipient: None, }; SPLITTER.save(deps.as_mut().storage, &splitter).unwrap(); @@ -439,6 +448,7 @@ fn test_execute_remove_recipient() { }, ], lock: Milliseconds::default(), + default_recipient: None, }; assert_eq!(expected_splitter, splitter); assert_eq!( @@ -506,6 +516,7 @@ fn test_execute_remove_recipient_not_on_list() { let splitter = Splitter { recipients: recipient, lock: Milliseconds::default(), + default_recipient: None, }; SPLITTER.save(deps.as_mut().storage, &splitter).unwrap(); @@ -569,6 +580,7 @@ fn test_execute_remove_recipient_contract_locked() { let splitter = Splitter { recipients: recipient.clone(), lock: Milliseconds::default(), + default_recipient: None, }; SPLITTER.save(deps.as_mut().storage, &splitter).unwrap(); @@ -579,6 +591,7 @@ fn test_execute_remove_recipient_contract_locked() { let splitter = Splitter { recipients: recipient, lock: Milliseconds::default().plus_seconds(current_time + 10_000), + default_recipient: None, }; SPLITTER.save(deps.as_mut().storage, &splitter).unwrap(); @@ -692,6 +705,7 @@ fn test_update_recipient_weight() { let splitter = Splitter { recipients: recipient.clone(), lock: Milliseconds::default(), + default_recipient: None, }; SPLITTER.save(deps.as_mut().storage, &splitter).unwrap(); @@ -702,6 +716,7 @@ fn test_update_recipient_weight() { let splitter = Splitter { recipients: recipient, lock: Milliseconds::default(), + default_recipient: None, }; SPLITTER.save(deps.as_mut().storage, &splitter).unwrap(); @@ -733,6 +748,7 @@ fn test_update_recipient_weight() { }, ], lock: Milliseconds::default(), + default_recipient: None, }; assert_eq!(expected_splitter, splitter); } @@ -785,6 +801,7 @@ fn test_update_recipient_weight_locked_contract() { let splitter = Splitter { recipients: recipient.clone(), lock: Milliseconds(current_time - 1), + default_recipient: None, }; SPLITTER.save(deps.as_mut().storage, &splitter).unwrap(); @@ -795,6 +812,7 @@ fn test_update_recipient_weight_locked_contract() { let splitter = Splitter { recipients: recipient, lock: Milliseconds::default().plus_seconds(current_time + 1), + default_recipient: None, }; SPLITTER.save(deps.as_mut().storage, &splitter).unwrap(); @@ -862,6 +880,7 @@ fn test_update_recipient_weight_user_not_found() { let splitter = Splitter { recipients: recipient, lock: Milliseconds::default(), + default_recipient: None, }; SPLITTER.save(deps.as_mut().storage, &splitter).unwrap(); @@ -934,6 +953,7 @@ fn test_update_recipient_weight_invalid_weight() { let splitter = Splitter { recipients: recipient, lock: Milliseconds::default(), + default_recipient: None, }; SPLITTER.save(deps.as_mut().storage, &splitter).unwrap(); @@ -998,6 +1018,7 @@ fn test_execute_add_recipient() { let splitter = Splitter { recipients: recipient, lock: Milliseconds::default(), + default_recipient: None, }; SPLITTER.save(deps.as_mut().storage, &splitter).unwrap(); @@ -1040,6 +1061,7 @@ fn test_execute_add_recipient() { }, ], lock: Milliseconds::default(), + default_recipient: None, }; assert_eq!(expected_splitter, splitter); @@ -1103,6 +1125,7 @@ fn test_execute_add_recipient_duplicate_recipient() { let splitter = Splitter { recipients: recipient, lock: Milliseconds::default(), + default_recipient: None, }; SPLITTER.save(deps.as_mut().storage, &splitter).unwrap(); @@ -1181,6 +1204,7 @@ fn test_execute_add_recipient_invalid_weight() { let splitter = Splitter { recipients: recipient, lock: Milliseconds::default(), + default_recipient: None, }; SPLITTER.save(deps.as_mut().storage, &splitter).unwrap(); @@ -1247,6 +1271,7 @@ fn test_execute_add_recipient_locked_contract() { let splitter = Splitter { recipients: recipient, lock: Milliseconds::default().plus_seconds(current_time + 1), + default_recipient: None, }; SPLITTER.save(deps.as_mut().storage, &splitter).unwrap(); @@ -1311,6 +1336,7 @@ fn test_execute_update_recipients() { let splitter = Splitter { recipients: vec![], lock: Milliseconds::default(), + default_recipient: None, }; SPLITTER.save(deps.as_mut().storage, &splitter).unwrap(); @@ -1382,6 +1408,7 @@ fn test_execute_update_recipients_invalid_weight() { let splitter = Splitter { recipients: vec![], lock: Milliseconds::default(), + default_recipient: None, }; SPLITTER.save(deps.as_mut().storage, &splitter).unwrap(); @@ -1437,6 +1464,7 @@ fn test_execute_update_recipients_contract_locked() { let splitter = Splitter { recipients: vec![], lock: Milliseconds::default().plus_seconds(current_time + 10), + default_recipient: None, }; SPLITTER.save(deps.as_mut().storage, &splitter).unwrap(); @@ -1490,6 +1518,7 @@ fn test_execute_update_recipients_unauthorized() { let splitter = Splitter { recipients: vec![], lock: Milliseconds::default(), + default_recipient: None, }; SPLITTER.save(deps.as_mut().storage, &splitter).unwrap(); @@ -1562,6 +1591,7 @@ fn test_execute_send() { let splitter = Splitter { recipients: recipient, lock: Milliseconds::default(), + default_recipient: None, }; SPLITTER.save(deps.as_mut().storage, &splitter).unwrap(); diff --git a/ibc-tests/tests/crowdfund.rs b/ibc-tests/tests/crowdfund.rs index 2406198a6..97b1d372e 100644 --- a/ibc-tests/tests/crowdfund.rs +++ b/ibc-tests/tests/crowdfund.rs @@ -163,6 +163,7 @@ fn setup( lock_time: None, kernel_address: kernel_address.clone(), owner: None, + default_recipient: None, }; let splitter_component = AppComponent::new( diff --git a/packages/andromeda-finance/src/set_amount_splitter.rs b/packages/andromeda-finance/src/set_amount_splitter.rs index b2894c085..9e9efe1a2 100644 --- a/packages/andromeda-finance/src/set_amount_splitter.rs +++ b/packages/andromeda-finance/src/set_amount_splitter.rs @@ -28,6 +28,8 @@ pub struct Splitter { pub recipients: Vec, /// The lock's expiration time pub lock: MillisecondsExpiration, + /// The address that will receive any surplus funds, defaults to the message sender. + pub default_recipient: Option, } #[andr_instantiate] @@ -37,6 +39,8 @@ pub struct InstantiateMsg { /// sent the amount sent will be divided amongst these recipients depending on their assigned amount. pub recipients: Vec, pub lock_time: Option, + /// The address that will receive any surplus funds, defaults to the message sender. + pub default_recipient: Option, } impl InstantiateMsg { @@ -55,6 +59,8 @@ pub enum ExecuteMsg { // Milliseconds from current time lock_time: Expiry, }, + /// Update the default recipient. Only executable by the contract owner when the contract is not locked. + UpdateDefaultRecipient { recipient: Option }, /// Divides any attached funds to the message amongst the recipients list. Send { config: Option> }, } diff --git a/packages/andromeda-finance/src/splitter.rs b/packages/andromeda-finance/src/splitter.rs index 58ba202e3..95bca32b3 100644 --- a/packages/andromeda-finance/src/splitter.rs +++ b/packages/andromeda-finance/src/splitter.rs @@ -28,6 +28,8 @@ pub struct Splitter { pub recipients: Vec, /// The lock's expiration time pub lock: MillisecondsExpiration, + /// The address that will receive any surplus funds, defaults to the message sender. + pub default_recipient: Option, } #[andr_instantiate] @@ -37,6 +39,7 @@ pub struct InstantiateMsg { /// sent the amount sent will be divided amongst these recipients depending on their assigned percentage. pub recipients: Vec, pub lock_time: Option, + pub default_recipient: Option, } impl InstantiateMsg { @@ -52,6 +55,8 @@ pub enum ExecuteMsg { UpdateRecipients { recipients: Vec }, /// Used to lock/unlock the contract allowing the config to be updated. UpdateLock { lock_time: Expiry }, + /// Update the default recipient. Only executable by the contract owner when the contract is not locked. + UpdateDefaultRecipient { recipient: Option }, /// Divides any attached funds to the message amongst the recipients list. Send { config: Option> }, } diff --git a/packages/andromeda-finance/src/weighted_splitter.rs b/packages/andromeda-finance/src/weighted_splitter.rs index 40b83c0cf..56a78f72f 100644 --- a/packages/andromeda-finance/src/weighted_splitter.rs +++ b/packages/andromeda-finance/src/weighted_splitter.rs @@ -19,6 +19,8 @@ pub struct Splitter { pub recipients: Vec, /// Whether or not the contract is currently locked. This restricts updating any config related fields. pub lock: MillisecondsExpiration, + /// The address that will receive any surplus funds, defaults to the message sender. + pub default_recipient: Option, } #[andr_instantiate] @@ -28,6 +30,7 @@ pub struct InstantiateMsg { /// sent the amount sent will be divided amongst these recipients depending on their assigned weight. pub recipients: Vec, pub lock_time: Option, + pub default_recipient: Option, } #[andr_exec] @@ -37,6 +40,8 @@ pub enum ExecuteMsg { UpdateRecipients { recipients: Vec }, /// Update a specific recipient's weight. Only executable by the contract owner when the contract is not locked. UpdateRecipientWeight { recipient: AddressWeight }, + /// Update the default recipient. Only executable by the contract owner when the contract is not locked. + UpdateDefaultRecipient { recipient: Option }, /// Add a single recipient to the recipient list. Only executable by the contract owner when the contract is not locked. AddRecipient { recipient: AddressWeight }, /// Remove a single recipient from the recipient list. Only executable by the contract owner when the contract is not locked. diff --git a/tests-integration/tests/auction_app.rs b/tests-integration/tests/auction_app.rs index 0f533dea9..c277ad103 100644 --- a/tests-integration/tests/auction_app.rs +++ b/tests-integration/tests/auction_app.rs @@ -280,6 +280,7 @@ fn test_auction_app_recipient() { andr.kernel.addr(), None, None, + None, ); let splitter_component = AppComponent::new( "splitter", diff --git a/tests-integration/tests/crowdfund_app.rs b/tests-integration/tests/crowdfund_app.rs index 4caecd510..e9cd015b2 100644 --- a/tests-integration/tests/crowdfund_app.rs +++ b/tests-integration/tests/crowdfund_app.rs @@ -92,8 +92,13 @@ fn setup( percent: Decimal::from_str("0.8").unwrap(), }, ]; - let splitter_init_msg = - mock_splitter_instantiate_msg(splitter_recipients, andr.kernel.addr().clone(), None, None); + let splitter_init_msg = mock_splitter_instantiate_msg( + splitter_recipients, + andr.kernel.addr().clone(), + None, + None, + None, + ); let splitter_component = AppComponent::new( "splitter".to_string(), "splitter".to_string(), diff --git a/tests-integration/tests/kernel.rs b/tests-integration/tests/kernel.rs index a51f4c83e..7215c2b91 100644 --- a/tests-integration/tests/kernel.rs +++ b/tests-integration/tests/kernel.rs @@ -33,6 +33,7 @@ fn kernel() { andr.kernel.addr().clone(), None, None, + None, ); let res = andr diff --git a/tests-integration/tests/kernel_orch.rs b/tests-integration/tests/kernel_orch.rs index a241d96c5..4a5171423 100644 --- a/tests-integration/tests/kernel_orch.rs +++ b/tests-integration/tests/kernel_orch.rs @@ -1500,6 +1500,7 @@ fn test_kernel_ibc_funds_and_execute_msg() { lock_time: None, kernel_address: kernel_osmosis.address().unwrap().into_string(), owner: None, + default_recipient: None, }, None, None, @@ -2143,6 +2144,7 @@ fn test_kernel_ibc_funds_and_execute_msg_unhappy() { lock_time: None, kernel_address: kernel_osmosis.address().unwrap().into_string(), owner: None, + default_recipient: None, }, None, None, diff --git a/tests-integration/tests/marketplace_app.rs b/tests-integration/tests/marketplace_app.rs index b45354456..b3c568831 100644 --- a/tests-integration/tests/marketplace_app.rs +++ b/tests-integration/tests/marketplace_app.rs @@ -326,6 +326,7 @@ fn test_marketplace_app_recipient() { andr.kernel.addr(), None, None, + None, ); let splitter_component = AppComponent::new( "splitter", diff --git a/tests-integration/tests/set_amount_splitter.rs b/tests-integration/tests/set_amount_splitter.rs index 0713a206a..ac019da59 100644 --- a/tests-integration/tests/set_amount_splitter.rs +++ b/tests-integration/tests/set_amount_splitter.rs @@ -48,6 +48,7 @@ fn test_splitter() { andr.kernel.addr().clone(), None, None, + None, ); let splitter_app_component = AppComponent { name: "splitter".to_string(), diff --git a/tests-integration/tests/splitter.rs b/tests-integration/tests/splitter.rs index 98203d29e..8397fd7b3 100644 --- a/tests-integration/tests/splitter.rs +++ b/tests-integration/tests/splitter.rs @@ -44,8 +44,13 @@ fn test_splitter() { }, ]; - let splitter_init_msg = - mock_splitter_instantiate_msg(splitter_recipients, andr.kernel.addr().clone(), None, None); + let splitter_init_msg = mock_splitter_instantiate_msg( + splitter_recipients, + andr.kernel.addr().clone(), + None, + None, + None, + ); let splitter_app_component = AppComponent { name: "splitter".to_string(), component_type: ComponentType::new(splitter_init_msg), From a8333404eec69e57c19f286a781039d614b78d81 Mon Sep 17 00:00:00 2001 From: Joe Monem Date: Fri, 6 Dec 2024 11:00:41 +0200 Subject: [PATCH 17/27] chore: changelog adjustment --- CHANGELOG.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index dde925de9..7b71877ef 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## Unreleased ### Added +- Added optional config for Send in Splitter contracts [(#686)](https://github.com/andromedaprotocol/andromeda-core/pull/686) ### Changed @@ -27,8 +28,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Added Validator Staking ADO [(#330)](https://github.com/andromedaprotocol/andromeda-core/pull/330) - Added Restake and Redelegate to Validator Staking [(#622)](https://github.com/andromedaprotocol/andromeda-core/pull/622) - Added andromeda-math and andromeda-account packages[(#672)](https://github.com/andromedaprotocol/andromeda-core/pull/672) -- Added optional config for Send in Splitter contracts [(#686)](https://github.com/andromedaprotocol/andromeda-core/pull/686) - ### Changed From bfbfa15797c054d1b5b34645379a0339f6b7eccb Mon Sep 17 00:00:00 2001 From: Joe Monem Date: Fri, 6 Dec 2024 11:13:55 +0200 Subject: [PATCH 18/27] ref: use generate_direct_msg where applicable --- .../andromeda-set-amount-splitter/src/contract.rs | 15 +++++---------- .../src/contract.rs | 14 +++++--------- 2 files changed, 10 insertions(+), 19 deletions(-) diff --git a/contracts/finance/andromeda-set-amount-splitter/src/contract.rs b/contracts/finance/andromeda-set-amount-splitter/src/contract.rs index ec6f353e4..06f56f2f5 100644 --- a/contracts/finance/andromeda-set-amount-splitter/src/contract.rs +++ b/contracts/finance/andromeda-set-amount-splitter/src/contract.rs @@ -16,8 +16,8 @@ use andromeda_std::{ }; use andromeda_std::{ado_contract::ADOContract, common::context::ExecuteContext}; use cosmwasm_std::{ - attr, coins, ensure, entry_point, BankMsg, Binary, Coin, CosmosMsg, Deps, DepsMut, Env, - MessageInfo, Reply, Response, StdError, SubMsg, + attr, coins, ensure, entry_point, Binary, Coin, Deps, DepsMut, Env, MessageInfo, Reply, + Response, StdError, SubMsg, }; use cw_utils::nonpayable; @@ -260,14 +260,9 @@ fn execute_send( .default_recipient .clone() .unwrap_or(Recipient::new(info.sender.to_string(), None)); - let msg = SubMsg::new(CosmosMsg::Bank(BankMsg::Send { - to_address: remainder_recipient - .address - .get_raw_address(&deps.as_ref())? - .into_string(), - amount: coins(remainder_funds.u128(), denom), - })); - msgs.push(msg); + let native_msg = remainder_recipient + .generate_direct_msg(&deps.as_ref(), coins(remainder_funds.u128(), denom))?; + msgs.push(native_msg); } } diff --git a/contracts/finance/andromeda-weighted-distribution-splitter/src/contract.rs b/contracts/finance/andromeda-weighted-distribution-splitter/src/contract.rs index 8d8eceef9..7f16851ef 100644 --- a/contracts/finance/andromeda-weighted-distribution-splitter/src/contract.rs +++ b/contracts/finance/andromeda-weighted-distribution-splitter/src/contract.rs @@ -17,8 +17,8 @@ use andromeda_std::{ error::ContractError, }; use cosmwasm_std::{ - attr, ensure, entry_point, BankMsg, Binary, Coin, CosmosMsg, Deps, DepsMut, Env, MessageInfo, - Reply, Response, StdError, SubMsg, Uint128, + attr, ensure, entry_point, Binary, Coin, Deps, DepsMut, Env, MessageInfo, Reply, Response, + StdError, SubMsg, Uint128, }; use cw_utils::nonpayable; @@ -328,13 +328,9 @@ fn execute_send( let remainder_recipient = splitter .default_recipient .unwrap_or(Recipient::new(info.sender.to_string(), None)); - msgs.push(SubMsg::new(CosmosMsg::Bank(BankMsg::Send { - to_address: remainder_recipient - .address - .get_raw_address(&deps.as_ref())? - .into_string(), - amount: remainder_funds, - }))); + let native_msg = + remainder_recipient.generate_direct_msg(&deps.as_ref(), remainder_funds)?; + msgs.push(native_msg); } // // Generates the SubMsg intended for the kernel From e5b794abc65d1e550612091ceba108b785c3f25a Mon Sep 17 00:00:00 2001 From: Connor Barr Date: Fri, 6 Dec 2024 09:29:18 +0000 Subject: [PATCH 19/27] test: fix tests --- contracts/finance/andromeda-splitter/src/contract.rs | 1 + tests-integration/tests/splitter.rs | 1 + 2 files changed, 2 insertions(+) diff --git a/contracts/finance/andromeda-splitter/src/contract.rs b/contracts/finance/andromeda-splitter/src/contract.rs index cd7f0c43d..66e568334 100644 --- a/contracts/finance/andromeda-splitter/src/contract.rs +++ b/contracts/finance/andromeda-splitter/src/contract.rs @@ -112,6 +112,7 @@ pub fn handle_execute(mut ctx: ExecuteContext, msg: ExecuteMsg) -> Result execute_send(ctx, config), + ExecuteMsg::Receive(receive_msg) => handle_receive_cw20(ctx, receive_msg), _ => ADOContract::default().execute(ctx, msg), }?; Ok(res diff --git a/tests-integration/tests/splitter.rs b/tests-integration/tests/splitter.rs index 44172b281..8ab9d554d 100644 --- a/tests-integration/tests/splitter.rs +++ b/tests-integration/tests/splitter.rs @@ -198,6 +198,7 @@ fn test_splitter_cw20() { let cw20_balance = cw20.query_balance(&router, recipient_2); assert_eq!(cw20_balance, Uint128::from(8u128)); } + #[test] fn test_splitter_cw20_with_remainder() { let mut router = mock_app(None); From c17f02fa0efed7fc18ed823bfc5d683aaf88c537 Mon Sep 17 00:00:00 2001 From: Connor Barr Date: Fri, 6 Dec 2024 09:40:25 +0000 Subject: [PATCH 20/27] chore: fixed beta tag to be "b" --- Cargo.lock | 4 ++-- contracts/finance/andromeda-splitter/Cargo.toml | 2 +- contracts/modules/andromeda-distance/Cargo.toml | 2 +- contracts/os/andromeda-adodb/src/tests.rs | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 2c32deef1..4428e05b8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -454,7 +454,7 @@ dependencies = [ [[package]] name = "andromeda-distance" -version = "0.1.0-beta.1" +version = "0.1.0-b.1" dependencies = [ "andromeda-app", "andromeda-math", @@ -832,7 +832,7 @@ dependencies = [ [[package]] name = "andromeda-splitter" -version = "3.0.0-beta.1" +version = "3.0.0-b.1" dependencies = [ "andromeda-app", "andromeda-finance", diff --git a/contracts/finance/andromeda-splitter/Cargo.toml b/contracts/finance/andromeda-splitter/Cargo.toml index d4518b693..5a87cb54e 100644 --- a/contracts/finance/andromeda-splitter/Cargo.toml +++ b/contracts/finance/andromeda-splitter/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "andromeda-splitter" -version = "3.0.0-beta.1" +version = "3.0.0-b.1" edition = "2021" rust-version = "1.75.0" diff --git a/contracts/modules/andromeda-distance/Cargo.toml b/contracts/modules/andromeda-distance/Cargo.toml index 70e373f31..38a61c528 100644 --- a/contracts/modules/andromeda-distance/Cargo.toml +++ b/contracts/modules/andromeda-distance/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "andromeda-distance" -version = "0.1.0-beta.1" +version = "0.1.0-b.1" edition = "2021" rust-version = "1.75.0" diff --git a/contracts/os/andromeda-adodb/src/tests.rs b/contracts/os/andromeda-adodb/src/tests.rs index d786706f3..a0b729abf 100644 --- a/contracts/os/andromeda-adodb/src/tests.rs +++ b/contracts/os/andromeda-adodb/src/tests.rs @@ -152,7 +152,7 @@ fn test_publish() { } // Test prelease - let ado_version = ADOVersion::from_type("ado_type_with_beta").with_version("0.1.0-beta.1"); + let ado_version = ADOVersion::from_type("ado_type_with_beta").with_version("0.1.0-b.1"); let code_id = 3; let msg = ExecuteMsg::Publish { ado_type: ado_version.get_type(), From 75eef77b1c0396c33e98d212e781a34441d6d862 Mon Sep 17 00:00:00 2001 From: Joe Monem Date: Mon, 9 Dec 2024 12:04:28 +0200 Subject: [PATCH 21/27] feat: cw20 support for set-amount-splitter --- Cargo.lock | 1 + .../andromeda-set-amount-splitter/Cargo.toml | 1 + .../src/contract.rs | 97 ++++++++++++++++++- .../src/set_amount_splitter.rs | 19 +++- 4 files changed, 112 insertions(+), 6 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 4428e05b8..93bbdec12 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -808,6 +808,7 @@ dependencies = [ "cw-orch", "cw-storage-plus 1.2.0", "cw-utils 1.0.3", + "cw20 1.1.2", ] [[package]] diff --git a/contracts/finance/andromeda-set-amount-splitter/Cargo.toml b/contracts/finance/andromeda-set-amount-splitter/Cargo.toml index 6004088b1..e9e37bc6b 100644 --- a/contracts/finance/andromeda-set-amount-splitter/Cargo.toml +++ b/contracts/finance/andromeda-set-amount-splitter/Cargo.toml @@ -20,6 +20,7 @@ cosmwasm-std = { workspace = true } cosmwasm-schema = { workspace = true } cw-storage-plus = { workspace = true } cw-utils = { workspace = true } +cw20 = { workspace = true } andromeda-std = { workspace = true } andromeda-finance = { workspace = true } diff --git a/contracts/finance/andromeda-set-amount-splitter/src/contract.rs b/contracts/finance/andromeda-set-amount-splitter/src/contract.rs index 06f56f2f5..0907a213c 100644 --- a/contracts/finance/andromeda-set-amount-splitter/src/contract.rs +++ b/contracts/finance/andromeda-set-amount-splitter/src/contract.rs @@ -3,7 +3,7 @@ use std::collections::HashSet; use crate::state::SPLITTER; use andromeda_finance::{ set_amount_splitter::{ - validate_recipient_list, AddressAmount, ExecuteMsg, GetSplitterConfigResponse, + validate_recipient_list, AddressAmount, Cw20HookMsg, ExecuteMsg, GetSplitterConfigResponse, InstantiateMsg, QueryMsg, Splitter, }, splitter::validate_expiry_duration, @@ -16,9 +16,10 @@ use andromeda_std::{ }; use andromeda_std::{ado_contract::ADOContract, common::context::ExecuteContext}; use cosmwasm_std::{ - attr, coins, ensure, entry_point, Binary, Coin, Deps, DepsMut, Env, MessageInfo, Reply, - Response, StdError, SubMsg, + attr, coin, coins, ensure, entry_point, from_json, Binary, Coin, Deps, DepsMut, Env, + MessageInfo, Reply, Response, StdError, SubMsg, Uint128, }; +use cw20::{Cw20Coin, Cw20ReceiveMsg}; use cw_utils::nonpayable; // version info for migration info @@ -129,6 +130,7 @@ pub fn handle_execute(mut ctx: ExecuteContext, msg: ExecuteMsg) -> Result { execute_update_default_recipient(ctx, recipient) } + ExecuteMsg::Receive(receive_msg) => handle_receive_cw20(ctx, receive_msg), ExecuteMsg::Send { config } => execute_send(ctx, config), _ => ADOContract::default().execute(ctx, msg), }?; @@ -138,6 +140,95 @@ pub fn handle_execute(mut ctx: ExecuteContext, msg: ExecuteMsg) -> Result Result { + let ExecuteContext { ref info, .. } = ctx; + nonpayable(info)?; + + let asset_sent = info.sender.clone().into_string(); + let amount_sent = receive_msg.amount; + // let sender = receive_msg.sender; + + ensure!( + !amount_sent.is_zero(), + ContractError::InvalidFunds { + msg: "Cannot send a 0 amount".to_string() + } + ); + + match from_json(&receive_msg.msg)? { + Cw20HookMsg::Send { config } => execute_send_cw20(ctx, amount_sent, asset_sent, config), + } +} + +fn execute_send_cw20( + ctx: ExecuteContext, + amount: Uint128, + asset: String, + config: Option>, +) -> Result { + let ExecuteContext { deps, info, .. } = ctx; + + let coin = coin(amount.u128(), asset.clone()); + + let splitter = SPLITTER.load(deps.storage)?; + let splitter_recipients = if let Some(config) = config { + validate_recipient_list(deps.as_ref(), config.clone())?; + config + } else { + splitter.recipients + }; + + let mut msgs: Vec = Vec::new(); + let mut remainder_funds = coin.amount; + + for recipient in splitter_recipients.clone() { + // Find the recipient's corresponding denom for the current iteration of the sent funds + let recipient_coin = recipient + .coins + .clone() + .into_iter() + .find(|coin| coin.denom == asset); + + if let Some(recipient_coin) = recipient_coin { + // Deduct from total amount + remainder_funds = remainder_funds + .checked_sub(recipient_coin.amount) + .map_err(|_| ContractError::InsufficientFunds {})?; + + let recipient_funds = + cosmwasm_std::coin(recipient_coin.amount.u128(), recipient_coin.denom); + + let amp_msg = recipient.recipient.generate_msg_cw20( + &deps.as_ref(), + Cw20Coin { + address: recipient_funds.denom.clone(), + amount: recipient_funds.amount, + }, + )?; + msgs.push(amp_msg); + } + } + + // Refund message for sender + if !remainder_funds.is_zero() { + let remainder_recipient = splitter + .default_recipient + .clone() + .unwrap_or(Recipient::new(info.sender.to_string(), None)); + let native_msg = remainder_recipient + .generate_direct_msg(&deps.as_ref(), coins(remainder_funds.u128(), asset))?; + msgs.push(native_msg); + } + + Ok(Response::new() + .add_submessages(msgs) + .add_attribute("action", "send") + .add_attribute("sender", info.sender.to_string())) +} + fn execute_update_default_recipient( ctx: ExecuteContext, recipient: Option, diff --git a/packages/andromeda-finance/src/set_amount_splitter.rs b/packages/andromeda-finance/src/set_amount_splitter.rs index 9e9efe1a2..2e7ac8b08 100644 --- a/packages/andromeda-finance/src/set_amount_splitter.rs +++ b/packages/andromeda-finance/src/set_amount_splitter.rs @@ -8,6 +8,7 @@ use andromeda_std::{ }; use cosmwasm_schema::{cw_serde, QueryResponses}; use cosmwasm_std::{ensure, Coin, Deps}; +use cw20::Cw20ReceiveMsg; #[cw_serde] pub struct AddressAmount { @@ -49,20 +50,32 @@ impl InstantiateMsg { } } +#[cw_serde] +pub enum Cw20HookMsg { + Send { config: Option> }, +} + #[andr_exec] #[cw_serde] pub enum ExecuteMsg { /// Update the recipients list. Only executable by the contract owner when the contract is not locked. - UpdateRecipients { recipients: Vec }, + UpdateRecipients { + recipients: Vec, + }, /// Used to lock/unlock the contract allowing the config to be updated. UpdateLock { // Milliseconds from current time lock_time: Expiry, }, /// Update the default recipient. Only executable by the contract owner when the contract is not locked. - UpdateDefaultRecipient { recipient: Option }, + UpdateDefaultRecipient { + recipient: Option, + }, + Receive(Cw20ReceiveMsg), /// Divides any attached funds to the message amongst the recipients list. - Send { config: Option> }, + Send { + config: Option>, + }, } #[andr_query] From 158a5a9232971ca523e1b42651ec751ae95fe71f Mon Sep 17 00:00:00 2001 From: Joe Monem Date: Tue, 10 Dec 2024 09:41:53 +0200 Subject: [PATCH 22/27] wip: rstest for set-amount-splitter --- .../andromeda-set-amount-splitter/src/mock.rs | 18 ++ .../tests/set_amount_splitter.rs | 247 +++++++++++++++--- 2 files changed, 226 insertions(+), 39 deletions(-) diff --git a/contracts/finance/andromeda-set-amount-splitter/src/mock.rs b/contracts/finance/andromeda-set-amount-splitter/src/mock.rs index 1464acd70..1d3c3276d 100644 --- a/contracts/finance/andromeda-set-amount-splitter/src/mock.rs +++ b/contracts/finance/andromeda-set-amount-splitter/src/mock.rs @@ -46,6 +46,18 @@ impl MockSetAmountSplitter { self.execute(app, &msg, sender, funds) } + + pub fn execute_update_recipients( + &self, + app: &mut MockApp, + sender: Addr, + funds: &[Coin], + recipients: Vec, + ) -> ExecuteResult { + let msg = mock_set_amount_splitter_update_recipients_msg(recipients); + + self.execute(app, &msg, sender, funds) + } } pub fn mock_andromeda_set_amount_splitter() -> Box> { @@ -72,3 +84,9 @@ pub fn mock_set_amount_splitter_instantiate_msg( pub fn mock_set_amount_splitter_send_msg(config: Option>) -> ExecuteMsg { ExecuteMsg::Send { config } } + +pub fn mock_set_amount_splitter_update_recipients_msg( + recipients: Vec, +) -> ExecuteMsg { + ExecuteMsg::UpdateRecipients { recipients } +} diff --git a/tests-integration/tests/set_amount_splitter.rs b/tests-integration/tests/set_amount_splitter.rs index ac019da59..c30afa374 100644 --- a/tests-integration/tests/set_amount_splitter.rs +++ b/tests-integration/tests/set_amount_splitter.rs @@ -1,48 +1,80 @@ -use andromeda_app::app::{AppComponent, ComponentType}; +use andromeda_app::app::AppComponent; use andromeda_app_contract::mock::{mock_andromeda_app, MockAppContract}; -use andromeda_testing::{mock::mock_app, mock_builder::MockAndromedaBuilder, MockContract}; +use andromeda_cw20::mock::{mock_andromeda_cw20, mock_cw20_instantiate_msg, mock_minter, MockCW20}; +use andromeda_testing::{ + mock::{mock_app, MockApp}, + mock_builder::MockAndromedaBuilder, + MockAndromeda, MockContract, +}; use andromeda_std::amp::Recipient; -use cosmwasm_std::{coin, coins, Uint128}; +use cosmwasm_std::{coin, coins, to_json_binary, Coin, Empty, Uint128}; use andromeda_finance::set_amount_splitter::AddressAmount; use andromeda_set_amount_splitter::mock::{ mock_andromeda_set_amount_splitter, mock_set_amount_splitter_instantiate_msg, MockSetAmountSplitter, }; +use cw20::Cw20Coin; +use cw_multi_test::Contract; +use rstest::{fixture, rstest}; + +struct TestCase { + router: MockApp, + andr: MockAndromeda, + splitter: MockSetAmountSplitter, + cw20: Option, +} + +#[fixture] +fn wallets() -> Vec<(&'static str, Vec)> { + vec![ + ("owner", vec![]), + ("buyer_one", vec![coin(1000000, "uandr")]), + ("recipient1", vec![]), + ("recipient2", vec![]), + ] +} -#[test] -fn test_splitter() { +#[fixture] +fn contracts() -> Vec<(&'static str, Box>)> { + vec![ + ("cw20", mock_andromeda_cw20()), + ("set-amount-splitter", mock_andromeda_set_amount_splitter()), + ("app-contract", mock_andromeda_app()), + ] +} + +#[fixture] +fn setup( + #[default(true)] use_native_token: bool, + wallets: Vec<(&'static str, Vec)>, + contracts: Vec<(&'static str, Box>)>, +) -> TestCase { let mut router = mock_app(None); let andr = MockAndromedaBuilder::new(&mut router, "admin") - .with_wallets(vec![ - ("owner", vec![coin(1000, "uandr")]), - ("recipient1", vec![]), - ("recipient2", vec![]), - ]) - .with_contracts(vec![ - ("app-contract", mock_andromeda_app()), - ("splitter", mock_andromeda_set_amount_splitter()), - ]) + .with_wallets(wallets) + .with_contracts(contracts) .build(&mut router); + let owner = andr.get_wallet("owner"); + let buyer_one = andr.get_wallet("buyer_one"); + + // Prepare Splitter component which can be used as a withdrawal address for some test cases let recipient_1 = andr.get_wallet("recipient1"); let recipient_2 = andr.get_wallet("recipient2"); - let app_code_id = andr.get_code_id(&mut router, "app-contract"); - let splitter_recipients = vec![ AddressAmount { recipient: Recipient::from_string(recipient_1.to_string()), - coins: coins(100_u128, "uandr"), + coins: coins(100, "uandr"), }, AddressAmount { recipient: Recipient::from_string(recipient_2.to_string()), - coins: coins(50_u128, "uandr"), + coins: coins(100, "uandr"), }, ]; - let splitter_init_msg = mock_set_amount_splitter_instantiate_msg( splitter_recipients, andr.kernel.addr().clone(), @@ -50,37 +82,174 @@ fn test_splitter() { None, None, ); - let splitter_app_component = AppComponent { - name: "splitter".to_string(), - component_type: ComponentType::new(splitter_init_msg), - ado_type: "splitter".to_string(), + let splitter_component = AppComponent::new( + "set_amount_splitter".to_string(), + "set_amount_splitter".to_string(), + to_json_binary(&splitter_init_msg).unwrap(), + ); + + let mut app_components = vec![splitter_component.clone()]; + + // Add cw20 components for test cases using cw20 + let cw20_component: Option = match use_native_token { + true => None, + false => { + let initial_balances = vec![Cw20Coin { + address: buyer_one.to_string(), + amount: Uint128::from(1000000u128), + }]; + let cw20_init_msg = mock_cw20_instantiate_msg( + None, + "Test Tokens".to_string(), + "TTT".to_string(), + 6, + initial_balances, + Some(mock_minter( + owner.to_string(), + Some(Uint128::from(1000000u128)), + )), + andr.kernel.addr().to_string(), + ); + let cw20_component = AppComponent::new( + "cw20".to_string(), + "cw20".to_string(), + to_json_binary(&cw20_init_msg).unwrap(), + ); + app_components.push(cw20_component.clone()); + Some(cw20_component) + } }; - let app_components = vec![splitter_app_component.clone()]; let app = MockAppContract::instantiate( - app_code_id, + andr.get_code_id(&mut router, "app-contract"), owner, &mut router, - "Splitter App", - app_components, + "Set Amount Splitter App", + app_components.clone(), andr.kernel.addr(), - None, + Some(owner.to_string()), ); let splitter: MockSetAmountSplitter = - app.query_ado_by_component_name(&router, splitter_app_component.name); + app.query_ado_by_component_name(&router, splitter_component.name); + + let cw20: Option = match use_native_token { + true => None, + false => Some(app.query_ado_by_component_name(&router, cw20_component.unwrap().name)), + }; + + // Update splitter recipients to use cw20 if applicable + if let Some(ref cw20) = cw20 { + let cw20_addr = cw20.addr(); + let splitter_recipients = vec![ + AddressAmount { + recipient: Recipient::from_string(recipient_1.to_string()), + coins: coins(100, cw20_addr.clone()), + }, + AddressAmount { + recipient: Recipient::from_string(recipient_2.to_string()), + coins: coins(100, cw20_addr.clone()), + }, + ]; + + splitter + .execute_update_recipients(&mut router, owner.clone(), &[], splitter_recipients) + .unwrap(); + } + + TestCase { + router, + andr, + splitter, + cw20, + } +} + +#[rstest] +fn test_successful_set_amount_splitter_native(#[with(true)] setup: TestCase) { + let TestCase { + mut router, + andr, + splitter, + .. + } = setup; + + let owner = andr.get_wallet("owner"); - let token = coin(1000, "uandr"); splitter - .execute_send(&mut router, owner.clone(), &[token], None) + .execute_send(&mut router, owner.clone(), &[coin(1000, "uandr")], None) .unwrap(); +} - let balance_1 = router.wrap().query_balance(recipient_1, "uandr").unwrap(); - let balance_2 = router.wrap().query_balance(recipient_2, "uandr").unwrap(); - let balance_owner = router.wrap().query_balance(owner, "uandr").unwrap(); +// #[test] +// fn test_splitter() { +// let mut router = mock_app(None); +// let andr = MockAndromedaBuilder::new(&mut router, "admin") +// .with_wallets(vec![ +// ("owner", vec![coin(1000, "uandr")]), +// ("recipient1", vec![]), +// ("recipient2", vec![]), +// ]) +// .with_contracts(vec![ +// ("app-contract", mock_andromeda_app()), +// ("splitter", mock_andromeda_set_amount_splitter()), +// ]) +// .build(&mut router); +// let owner = andr.get_wallet("owner"); +// let recipient_1 = andr.get_wallet("recipient1"); +// let recipient_2 = andr.get_wallet("recipient2"); - assert_eq!(balance_1.amount, Uint128::from(100u128)); - assert_eq!(balance_2.amount, Uint128::from(50u128)); - // The owner sent 1000 but only 150 was needed. His account should be now worth 850 - assert_eq!(balance_owner.amount, Uint128::from(850u128)); -} +// let app_code_id = andr.get_code_id(&mut router, "app-contract"); + +// let splitter_recipients = vec![ +// AddressAmount { +// recipient: Recipient::from_string(recipient_1.to_string()), +// coins: coins(100_u128, "uandr"), +// }, +// AddressAmount { +// recipient: Recipient::from_string(recipient_2.to_string()), +// coins: coins(50_u128, "uandr"), +// }, +// ]; + +// let splitter_init_msg = mock_set_amount_splitter_instantiate_msg( +// splitter_recipients, +// andr.kernel.addr().clone(), +// None, +// None, +// None, +// ); +// let splitter_app_component = AppComponent { +// name: "splitter".to_string(), +// component_type: ComponentType::new(splitter_init_msg), +// ado_type: "splitter".to_string(), +// }; + +// let app_components = vec![splitter_app_component.clone()]; +// let app = MockAppContract::instantiate( +// app_code_id, +// owner, +// &mut router, +// "Splitter App", +// app_components, +// andr.kernel.addr(), +// None, +// ); + +// let splitter: MockSetAmountSplitter = +// app.query_ado_by_component_name(&router, splitter_app_component.name); + +// let token = coin(1000, "uandr"); +// splitter +// .execute_send(&mut router, owner.clone(), &[token], None) +// .unwrap(); + +// let balance_1 = router.wrap().query_balance(recipient_1, "uandr").unwrap(); +// let balance_2 = router.wrap().query_balance(recipient_2, "uandr").unwrap(); +// let balance_owner = router.wrap().query_balance(owner, "uandr").unwrap(); + +// assert_eq!(balance_1.amount, Uint128::from(100u128)); +// assert_eq!(balance_2.amount, Uint128::from(50u128)); +// // The owner sent 1000 but only 150 was needed. His account should be now worth 850 +// assert_eq!(balance_owner.amount, Uint128::from(850u128)); +// } From bd54220c2843ce2e4b160cc98115e8f76038fc99 Mon Sep 17 00:00:00 2001 From: Joe Monem Date: Tue, 10 Dec 2024 14:34:01 +0200 Subject: [PATCH 23/27] fix + test: refund recipient fixed, rstest integration tests for set_amount_splitter --- .../src/contract.rs | 23 ++- .../andromeda-splitter/src/contract.rs | 13 +- .../tests/set_amount_splitter.rs | 152 +++++++++++++----- 3 files changed, 134 insertions(+), 54 deletions(-) diff --git a/contracts/finance/andromeda-set-amount-splitter/src/contract.rs b/contracts/finance/andromeda-set-amount-splitter/src/contract.rs index 0907a213c..be03d5cbe 100644 --- a/contracts/finance/andromeda-set-amount-splitter/src/contract.rs +++ b/contracts/finance/andromeda-set-amount-splitter/src/contract.rs @@ -149,7 +149,7 @@ pub fn handle_receive_cw20( let asset_sent = info.sender.clone().into_string(); let amount_sent = receive_msg.amount; - // let sender = receive_msg.sender; + let sender = receive_msg.sender; ensure!( !amount_sent.is_zero(), @@ -159,12 +159,15 @@ pub fn handle_receive_cw20( ); match from_json(&receive_msg.msg)? { - Cw20HookMsg::Send { config } => execute_send_cw20(ctx, amount_sent, asset_sent, config), + Cw20HookMsg::Send { config } => { + execute_send_cw20(ctx, sender, amount_sent, asset_sent, config) + } } } fn execute_send_cw20( ctx: ExecuteContext, + sender: String, amount: Uint128, asset: String, config: Option>, @@ -174,6 +177,7 @@ fn execute_send_cw20( let coin = coin(amount.u128(), asset.clone()); let splitter = SPLITTER.load(deps.storage)?; + let splitter_recipients = if let Some(config) = config { validate_recipient_list(deps.as_ref(), config.clone())?; config @@ -212,15 +216,18 @@ fn execute_send_cw20( } } - // Refund message for sender if !remainder_funds.is_zero() { let remainder_recipient = splitter .default_recipient - .clone() - .unwrap_or(Recipient::new(info.sender.to_string(), None)); - let native_msg = remainder_recipient - .generate_direct_msg(&deps.as_ref(), coins(remainder_funds.u128(), asset))?; - msgs.push(native_msg); + .unwrap_or(Recipient::new(sender, None)); + let cw20_msg = remainder_recipient.generate_msg_cw20( + &deps.as_ref(), + Cw20Coin { + address: asset, + amount: remainder_funds, + }, + )?; + msgs.push(cw20_msg); } Ok(Response::new() diff --git a/contracts/finance/andromeda-splitter/src/contract.rs b/contracts/finance/andromeda-splitter/src/contract.rs index 66e568334..139f6b289 100644 --- a/contracts/finance/andromeda-splitter/src/contract.rs +++ b/contracts/finance/andromeda-splitter/src/contract.rs @@ -146,7 +146,7 @@ pub fn handle_receive_cw20( let asset_sent = info.sender.clone().into_string(); let amount_sent = receive_msg.amount; - // let sender = receive_msg.sender; + let sender = receive_msg.sender; ensure!( !amount_sent.is_zero(), @@ -156,7 +156,9 @@ pub fn handle_receive_cw20( ); match from_json(&receive_msg.msg)? { - Cw20HookMsg::Send { config } => execute_send_cw20(ctx, amount_sent, asset_sent, config), + Cw20HookMsg::Send { config } => { + execute_send_cw20(ctx, sender, amount_sent, asset_sent, config) + } } } @@ -254,11 +256,12 @@ fn execute_send( fn execute_send_cw20( ctx: ExecuteContext, + sender: String, amount: Uint128, asset: String, config: Option>, ) -> Result { - let ExecuteContext { deps, info, .. } = ctx; + let ExecuteContext { deps, .. } = ctx; let splitter = SPLITTER.load(deps.storage)?; let splitter_recipients = if let Some(config) = config { @@ -298,7 +301,7 @@ fn execute_send_cw20( if !remainder_funds.amount.is_zero() { let remainder_recipient = splitter .default_recipient - .unwrap_or(Recipient::new(info.sender.to_string(), None)); + .unwrap_or(Recipient::new(sender.clone(), None)); let cw20_msg = remainder_recipient.generate_msg_cw20( &deps.as_ref(), Cw20Coin { @@ -312,7 +315,7 @@ fn execute_send_cw20( Ok(Response::new() .add_submessages(msgs) .add_attribute("action", "cw20_send") - .add_attribute("sender", info.sender.to_string())) + .add_attribute("sender", sender.to_string())) } fn execute_update_recipients( diff --git a/tests-integration/tests/set_amount_splitter.rs b/tests-integration/tests/set_amount_splitter.rs index c30afa374..fd34dc8a1 100644 --- a/tests-integration/tests/set_amount_splitter.rs +++ b/tests-integration/tests/set_amount_splitter.rs @@ -11,7 +11,7 @@ use andromeda_testing::{ use andromeda_std::amp::Recipient; use cosmwasm_std::{coin, coins, to_json_binary, Coin, Empty, Uint128}; -use andromeda_finance::set_amount_splitter::AddressAmount; +use andromeda_finance::set_amount_splitter::{AddressAmount, Cw20HookMsg}; use andromeda_set_amount_splitter::mock::{ mock_andromeda_set_amount_splitter, mock_set_amount_splitter_instantiate_msg, MockSetAmountSplitter, @@ -24,14 +24,13 @@ struct TestCase { router: MockApp, andr: MockAndromeda, splitter: MockSetAmountSplitter, - cw20: Option, + cw20: MockCW20, } #[fixture] fn wallets() -> Vec<(&'static str, Vec)> { vec![ - ("owner", vec![]), - ("buyer_one", vec![coin(1000000, "uandr")]), + ("owner", vec![coin(1000000, "uandr")]), ("recipient1", vec![]), ("recipient2", vec![]), ] @@ -53,13 +52,13 @@ fn setup( contracts: Vec<(&'static str, Box>)>, ) -> TestCase { let mut router = mock_app(None); + let andr = MockAndromedaBuilder::new(&mut router, "admin") .with_wallets(wallets) .with_contracts(contracts) .build(&mut router); let owner = andr.get_wallet("owner"); - let buyer_one = andr.get_wallet("buyer_one"); // Prepare Splitter component which can be used as a withdrawal address for some test cases let recipient_1 = andr.get_wallet("recipient1"); @@ -83,41 +82,39 @@ fn setup( None, ); let splitter_component = AppComponent::new( - "set_amount_splitter".to_string(), - "set_amount_splitter".to_string(), + "set-amount-splitter".to_string(), + "set-amount-splitter".to_string(), to_json_binary(&splitter_init_msg).unwrap(), ); let mut app_components = vec![splitter_component.clone()]; // Add cw20 components for test cases using cw20 - let cw20_component: Option = match use_native_token { - true => None, - false => { - let initial_balances = vec![Cw20Coin { - address: buyer_one.to_string(), - amount: Uint128::from(1000000u128), - }]; - let cw20_init_msg = mock_cw20_instantiate_msg( - None, - "Test Tokens".to_string(), - "TTT".to_string(), - 6, - initial_balances, - Some(mock_minter( - owner.to_string(), - Some(Uint128::from(1000000u128)), - )), - andr.kernel.addr().to_string(), - ); - let cw20_component = AppComponent::new( - "cw20".to_string(), - "cw20".to_string(), - to_json_binary(&cw20_init_msg).unwrap(), - ); - app_components.push(cw20_component.clone()); - Some(cw20_component) - } + let cw20_component: AppComponent = { + let initial_balances = vec![Cw20Coin { + address: owner.to_string(), + amount: Uint128::from(1_000_000u128), + }]; + + let cw20_init_msg = mock_cw20_instantiate_msg( + None, + "Test Tokens".to_string(), + "TTT".to_string(), + 6, + initial_balances, + Some(mock_minter( + owner.to_string(), + Some(Uint128::from(1000000u128)), + )), + andr.kernel.addr().to_string(), + ); + let cw20_component = AppComponent::new( + "cw20".to_string(), + "cw20".to_string(), + to_json_binary(&cw20_init_msg).unwrap(), + ); + app_components.push(cw20_component.clone()); + cw20_component }; let app = MockAppContract::instantiate( @@ -133,13 +130,10 @@ fn setup( let splitter: MockSetAmountSplitter = app.query_ado_by_component_name(&router, splitter_component.name); - let cw20: Option = match use_native_token { - true => None, - false => Some(app.query_ado_by_component_name(&router, cw20_component.unwrap().name)), - }; + let cw20: MockCW20 = app.query_ado_by_component_name(&router, cw20_component.name); // Update splitter recipients to use cw20 if applicable - if let Some(ref cw20) = cw20 { + if !use_native_token { let cw20_addr = cw20.addr(); let splitter_recipients = vec![ AddressAmount { @@ -156,7 +150,6 @@ fn setup( .execute_update_recipients(&mut router, owner.clone(), &[], splitter_recipients) .unwrap(); } - TestCase { router, andr, @@ -166,7 +159,7 @@ fn setup( } #[rstest] -fn test_successful_set_amount_splitter_native(#[with(true)] setup: TestCase) { +fn test_successful_set_amount_splitter_native(setup: TestCase) { let TestCase { mut router, andr, @@ -179,6 +172,83 @@ fn test_successful_set_amount_splitter_native(#[with(true)] setup: TestCase) { splitter .execute_send(&mut router, owner.clone(), &[coin(1000, "uandr")], None) .unwrap(); + + assert_eq!( + router + .wrap() + .query_balance(andr.get_wallet("recipient1"), "uandr") + .unwrap() + .amount, + Uint128::from(100u128) + ); + assert_eq!( + router + .wrap() + .query_balance(andr.get_wallet("recipient2"), "uandr") + .unwrap() + .amount, + Uint128::from(100u128) + ); +} + +#[rstest] +fn test_successful_set_amount_splitter_cw20_with_remainder(#[with(false)] setup: TestCase) { + let TestCase { + mut router, + andr, + splitter, + cw20, + } = setup; + + let owner = andr.get_wallet("owner"); + + let hook_msg = Cw20HookMsg::Send { config: None }; + + cw20.execute_send( + &mut router, + owner.clone(), + splitter.addr(), + Uint128::new(1000), + &hook_msg, + ) + .unwrap(); + + let cw20_balance = cw20.query_balance(&router, andr.get_wallet("recipient1")); + assert_eq!(cw20_balance, Uint128::from(100u128)); + let cw20_balance = cw20.query_balance(&router, andr.get_wallet("recipient2")); + assert_eq!(cw20_balance, Uint128::from(100u128)); + let cw20_balance = cw20.query_balance(&router, owner); + assert_eq!(cw20_balance, Uint128::from(1_000_000u128 - 200u128)); +} + +#[rstest] +fn test_successful_set_amount_splitter_cw20_without_remainder(#[with(false)] setup: TestCase) { + let TestCase { + mut router, + andr, + splitter, + cw20, + } = setup; + + let owner = andr.get_wallet("owner"); + + let hook_msg = Cw20HookMsg::Send { config: None }; + + cw20.execute_send( + &mut router, + owner.clone(), + splitter.addr(), + Uint128::new(200), + &hook_msg, + ) + .unwrap(); + + let cw20_balance = cw20.query_balance(&router, andr.get_wallet("recipient1")); + assert_eq!(cw20_balance, Uint128::from(100u128)); + let cw20_balance = cw20.query_balance(&router, andr.get_wallet("recipient2")); + assert_eq!(cw20_balance, Uint128::from(100u128)); + let cw20_balance = cw20.query_balance(&router, owner); + assert_eq!(cw20_balance, Uint128::from(1_000_000u128 - 200u128)); } // #[test] From f51d97f271d15df80470f85510656f6ac04f4f85 Mon Sep 17 00:00:00 2001 From: Joe Monem Date: Tue, 10 Dec 2024 14:49:13 +0200 Subject: [PATCH 24/27] test: rstest for splitter integration test --- .../finance/andromeda-splitter/src/mock.rs | 16 + .../tests/set_amount_splitter.rs | 73 --- tests-integration/tests/splitter.rs | 419 +++++++++--------- 3 files changed, 224 insertions(+), 284 deletions(-) diff --git a/contracts/finance/andromeda-splitter/src/mock.rs b/contracts/finance/andromeda-splitter/src/mock.rs index 06324fc07..774d7c206 100644 --- a/contracts/finance/andromeda-splitter/src/mock.rs +++ b/contracts/finance/andromeda-splitter/src/mock.rs @@ -46,6 +46,18 @@ impl MockSplitter { self.execute(app, &msg, sender, funds) } + + pub fn execute_update_recipients( + &self, + app: &mut MockApp, + sender: Addr, + funds: &[Coin], + recipients: Vec, + ) -> ExecuteResult { + let msg = mock_splitter_update_recipients_msg(recipients); + + self.execute(app, &msg, sender, funds) + } } pub fn mock_andromeda_splitter() -> Box> { @@ -72,3 +84,7 @@ pub fn mock_splitter_instantiate_msg( pub fn mock_splitter_send_msg(config: Option>) -> ExecuteMsg { ExecuteMsg::Send { config } } + +pub fn mock_splitter_update_recipients_msg(recipients: Vec) -> ExecuteMsg { + ExecuteMsg::UpdateRecipients { recipients } +} diff --git a/tests-integration/tests/set_amount_splitter.rs b/tests-integration/tests/set_amount_splitter.rs index fd34dc8a1..80f222f72 100644 --- a/tests-integration/tests/set_amount_splitter.rs +++ b/tests-integration/tests/set_amount_splitter.rs @@ -250,76 +250,3 @@ fn test_successful_set_amount_splitter_cw20_without_remainder(#[with(false)] set let cw20_balance = cw20.query_balance(&router, owner); assert_eq!(cw20_balance, Uint128::from(1_000_000u128 - 200u128)); } - -// #[test] -// fn test_splitter() { -// let mut router = mock_app(None); -// let andr = MockAndromedaBuilder::new(&mut router, "admin") -// .with_wallets(vec![ -// ("owner", vec![coin(1000, "uandr")]), -// ("recipient1", vec![]), -// ("recipient2", vec![]), -// ]) -// .with_contracts(vec![ -// ("app-contract", mock_andromeda_app()), -// ("splitter", mock_andromeda_set_amount_splitter()), -// ]) -// .build(&mut router); -// let owner = andr.get_wallet("owner"); -// let recipient_1 = andr.get_wallet("recipient1"); -// let recipient_2 = andr.get_wallet("recipient2"); - -// let app_code_id = andr.get_code_id(&mut router, "app-contract"); - -// let splitter_recipients = vec![ -// AddressAmount { -// recipient: Recipient::from_string(recipient_1.to_string()), -// coins: coins(100_u128, "uandr"), -// }, -// AddressAmount { -// recipient: Recipient::from_string(recipient_2.to_string()), -// coins: coins(50_u128, "uandr"), -// }, -// ]; - -// let splitter_init_msg = mock_set_amount_splitter_instantiate_msg( -// splitter_recipients, -// andr.kernel.addr().clone(), -// None, -// None, -// None, -// ); -// let splitter_app_component = AppComponent { -// name: "splitter".to_string(), -// component_type: ComponentType::new(splitter_init_msg), -// ado_type: "splitter".to_string(), -// }; - -// let app_components = vec![splitter_app_component.clone()]; -// let app = MockAppContract::instantiate( -// app_code_id, -// owner, -// &mut router, -// "Splitter App", -// app_components, -// andr.kernel.addr(), -// None, -// ); - -// let splitter: MockSetAmountSplitter = -// app.query_ado_by_component_name(&router, splitter_app_component.name); - -// let token = coin(1000, "uandr"); -// splitter -// .execute_send(&mut router, owner.clone(), &[token], None) -// .unwrap(); - -// let balance_1 = router.wrap().query_balance(recipient_1, "uandr").unwrap(); -// let balance_2 = router.wrap().query_balance(recipient_2, "uandr").unwrap(); -// let balance_owner = router.wrap().query_balance(owner, "uandr").unwrap(); - -// assert_eq!(balance_1.amount, Uint128::from(100u128)); -// assert_eq!(balance_2.amount, Uint128::from(50u128)); -// // The owner sent 1000 but only 150 was needed. His account should be now worth 850 -// assert_eq!(balance_owner.amount, Uint128::from(850u128)); -// } diff --git a/tests-integration/tests/splitter.rs b/tests-integration/tests/splitter.rs index 8ab9d554d..e21404d66 100644 --- a/tests-integration/tests/splitter.rs +++ b/tests-integration/tests/splitter.rs @@ -1,47 +1,77 @@ -use andromeda_app::app::{AppComponent, ComponentType}; +use andromeda_app::app::AppComponent; use andromeda_app_contract::mock::{mock_andromeda_app, MockAppContract}; + use andromeda_cw20::mock::{mock_andromeda_cw20, mock_cw20_instantiate_msg, mock_minter, MockCW20}; +use andromeda_testing::{ + mock::{mock_app, MockApp}, + mock_builder::MockAndromedaBuilder, + MockAndromeda, MockContract, +}; + +use andromeda_std::amp::Recipient; +use cosmwasm_std::{coin, to_json_binary, Coin, Decimal, Empty, Uint128}; + use andromeda_finance::splitter::{AddressPercent, Cw20HookMsg}; use andromeda_splitter::mock::{ mock_andromeda_splitter, mock_splitter_instantiate_msg, MockSplitter, }; -use andromeda_std::amp::Recipient; -use andromeda_testing::{mock::mock_app, mock_builder::MockAndromedaBuilder, MockContract}; -use cosmwasm_std::{coin, to_json_binary, Decimal, Uint128}; use cw20::Cw20Coin; -use std::str::FromStr; +use cw_multi_test::Contract; +use rstest::{fixture, rstest}; + +struct TestCase { + router: MockApp, + andr: MockAndromeda, + splitter: MockSplitter, + cw20: MockCW20, +} -#[test] -fn test_splitter() { +#[fixture] +fn wallets() -> Vec<(&'static str, Vec)> { + vec![ + ("owner", vec![coin(1000000, "uandr")]), + ("recipient1", vec![]), + ("recipient2", vec![]), + ] +} + +#[fixture] +fn contracts() -> Vec<(&'static str, Box>)> { + vec![ + ("cw20", mock_andromeda_cw20()), + ("splitter", mock_andromeda_splitter()), + ("app-contract", mock_andromeda_app()), + ] +} + +#[fixture] +fn setup( + wallets: Vec<(&'static str, Vec)>, + contracts: Vec<(&'static str, Box>)>, +) -> TestCase { let mut router = mock_app(None); + let andr = MockAndromedaBuilder::new(&mut router, "admin") - .with_wallets(vec![ - ("owner", vec![coin(10000, "uandr")]), - ("recipient1", vec![]), - ("recipient2", vec![]), - ]) - .with_contracts(vec![ - ("app-contract", mock_andromeda_app()), - ("splitter", mock_andromeda_splitter()), - ]) + .with_wallets(wallets) + .with_contracts(contracts) .build(&mut router); + let owner = andr.get_wallet("owner"); + + // Prepare Splitter component which can be used as a withdrawal address for some test cases let recipient_1 = andr.get_wallet("recipient1"); let recipient_2 = andr.get_wallet("recipient2"); - let app_code_id = andr.get_code_id(&mut router, "app-contract"); - let splitter_recipients = vec![ AddressPercent { recipient: Recipient::from_string(recipient_1.to_string()), - percent: Decimal::from_str("0.2").unwrap(), + percent: Decimal::from_ratio(Uint128::from(2u128), Uint128::from(10u128)), }, AddressPercent { recipient: Recipient::from_string(recipient_2.to_string()), - percent: Decimal::from_str("0.8").unwrap(), + percent: Decimal::from_ratio(Uint128::from(8u128), Uint128::from(10u128)), }, ]; - let splitter_init_msg = mock_splitter_instantiate_msg( splitter_recipients, andr.kernel.addr().clone(), @@ -49,138 +79,163 @@ fn test_splitter() { None, None, ); - let splitter_app_component = AppComponent { - name: "splitter".to_string(), - component_type: ComponentType::new(splitter_init_msg), - ado_type: "splitter".to_string(), + let splitter_component = AppComponent::new( + "splitter".to_string(), + "splitter".to_string(), + to_json_binary(&splitter_init_msg).unwrap(), + ); + + let mut app_components = vec![splitter_component.clone()]; + + // Add cw20 components for test cases using cw20 + let cw20_component: AppComponent = { + let initial_balances = vec![Cw20Coin { + address: owner.to_string(), + amount: Uint128::from(1_000_000u128), + }]; + + let cw20_init_msg = mock_cw20_instantiate_msg( + None, + "Test Tokens".to_string(), + "TTT".to_string(), + 6, + initial_balances, + Some(mock_minter( + owner.to_string(), + Some(Uint128::from(1000000u128)), + )), + andr.kernel.addr().to_string(), + ); + let cw20_component = AppComponent::new( + "cw20".to_string(), + "cw20".to_string(), + to_json_binary(&cw20_init_msg).unwrap(), + ); + app_components.push(cw20_component.clone()); + cw20_component }; - let app_components = vec![splitter_app_component.clone()]; let app = MockAppContract::instantiate( - app_code_id, + andr.get_code_id(&mut router, "app-contract"), owner, &mut router, - "Splitter App", - app_components, + "Set Amount Splitter App", + app_components.clone(), andr.kernel.addr(), - None, + Some(owner.to_string()), ); - let splitter: MockSplitter = - app.query_ado_by_component_name(&router, splitter_app_component.name); + let splitter: MockSplitter = app.query_ado_by_component_name(&router, splitter_component.name); - let token = coin(1000, "uandr"); - splitter - .execute_send(&mut router, owner.clone(), &[token.clone()], None) - .unwrap(); + let cw20: MockCW20 = app.query_ado_by_component_name(&router, cw20_component.name); - let balance_1 = router.wrap().query_balance(recipient_1, "uandr").unwrap(); - let balance_2 = router.wrap().query_balance(recipient_2, "uandr").unwrap(); + TestCase { + router, + andr, + splitter, + cw20, + } +} - assert_eq!(balance_1.amount, Uint128::from(200u128)); - assert_eq!(balance_2.amount, Uint128::from(800u128)); +#[rstest] +fn test_successful_set_amount_splitter_without_remainder_native(setup: TestCase) { + let TestCase { + mut router, + andr, + splitter, + .. + } = setup; - // Test with config - let custom_recipients = vec![AddressPercent { - recipient: Recipient::from_string(recipient_1.to_string()), - percent: Decimal::from_str("0.5").unwrap(), - }]; + let owner = andr.get_wallet("owner"); splitter - .execute_send( - &mut router, - owner.clone(), - &[token], - Some(custom_recipients), - ) + .execute_send(&mut router, owner.clone(), &[coin(1000, "uandr")], None) .unwrap(); - let balance_1 = router.wrap().query_balance(recipient_1, "uandr").unwrap(); - assert_eq!(balance_1.amount, Uint128::from(200u128 + 500u128)); -} -#[test] -fn test_splitter_cw20() { - let mut router = mock_app(None); - let andr = MockAndromedaBuilder::new(&mut router, "admin") - .with_wallets(vec![ - ("owner", vec![coin(10000, "uandr")]), - ("recipient1", vec![]), - ("recipient2", vec![]), - ]) - .with_contracts(vec![ - ("app-contract", mock_andromeda_app()), - ("splitter", mock_andromeda_splitter()), - ("cw20", mock_andromeda_cw20()), - ]) - .build(&mut router); - let owner = andr.get_wallet("owner"); - let recipient_1 = andr.get_wallet("recipient1"); - let recipient_2 = andr.get_wallet("recipient2"); - - let app_code_id = andr.get_code_id(&mut router, "app-contract"); - - let initial_balances = vec![Cw20Coin { - address: owner.to_string(), - amount: Uint128::new(1000000u128), - }]; - - let cw20_init_msg = mock_cw20_instantiate_msg( - None, - "Test Tokens".to_string(), - "TTT".to_string(), - 6, - initial_balances.clone(), - Some(mock_minter( - owner.to_string(), - Some(Uint128::from(10000000000u128)), - )), - andr.kernel.addr().to_string(), + assert_eq!( + router + .wrap() + .query_balance(andr.get_wallet("recipient1"), "uandr") + .unwrap() + .amount, + Uint128::from(200u128) ); - let cw20_component = AppComponent::new( - "cw20".to_string(), - "cw20".to_string(), - to_json_binary(&cw20_init_msg).unwrap(), + assert_eq!( + router + .wrap() + .query_balance(andr.get_wallet("recipient2"), "uandr") + .unwrap() + .amount, + Uint128::from(800u128) ); +} + +#[rstest] +fn test_successful_set_amount_splitter_with_remainder_native(setup: TestCase) { + let TestCase { + mut router, + andr, + splitter, + .. + } = setup; + + let owner = andr.get_wallet("owner"); let splitter_recipients = vec![ AddressPercent { - recipient: Recipient::from_string(recipient_1.to_string()), - percent: Decimal::from_str("0.2").unwrap(), + recipient: Recipient::from_string(andr.get_wallet("recipient1").to_string()), + percent: Decimal::from_ratio(Uint128::from(1u128), Uint128::from(10u128)), }, AddressPercent { - recipient: Recipient::from_string(recipient_2.to_string()), - percent: Decimal::from_str("0.8").unwrap(), + recipient: Recipient::from_string(andr.get_wallet("recipient2").to_string()), + percent: Decimal::from_ratio(Uint128::from(1u128), Uint128::from(10u128)), }, ]; - let splitter_init_msg = mock_splitter_instantiate_msg( - splitter_recipients, - andr.kernel.addr().clone(), - None, - None, - None, - ); - let splitter_app_component = AppComponent { - name: "splitter".to_string(), - component_type: ComponentType::new(splitter_init_msg), - ado_type: "splitter".to_string(), - }; + splitter + .execute_update_recipients(&mut router, owner.clone(), &[], splitter_recipients) + .unwrap(); - let app_components = vec![splitter_app_component.clone(), cw20_component.clone()]; - let app = MockAppContract::instantiate( - app_code_id, - owner, - &mut router, - "Splitter App", - app_components, - andr.kernel.addr(), - None, + splitter + .execute_send(&mut router, owner.clone(), &[coin(1000, "uandr")], None) + .unwrap(); + + assert_eq!( + router + .wrap() + .query_balance(andr.get_wallet("recipient1"), "uandr") + .unwrap() + .amount, + Uint128::from(100u128) + ); + assert_eq!( + router + .wrap() + .query_balance(andr.get_wallet("recipient2"), "uandr") + .unwrap() + .amount, + Uint128::from(100u128) + ); + assert_eq!( + router + .wrap() + .query_balance(andr.get_wallet("owner"), "uandr") + .unwrap() + .amount, + Uint128::from(1_000_000u128 - 200u128) ); +} - let splitter: MockSplitter = - app.query_ado_by_component_name(&router, splitter_app_component.name); +#[rstest] +fn test_successful_set_amount_splitter_cw20_without_remainder(setup: TestCase) { + let TestCase { + mut router, + andr, + splitter, + cw20, + } = setup; - let cw20: MockCW20 = app.query_ado_by_component_name(&router, cw20_component.name); + let owner = andr.get_wallet("owner"); let hook_msg = Cw20HookMsg::Send { config: None }; @@ -188,102 +243,44 @@ fn test_splitter_cw20() { &mut router, owner.clone(), splitter.addr(), - Uint128::new(10), + Uint128::new(1000), &hook_msg, ) .unwrap(); - let cw20_balance = cw20.query_balance(&router, recipient_1); - assert_eq!(cw20_balance, Uint128::from(2u128)); - let cw20_balance = cw20.query_balance(&router, recipient_2); - assert_eq!(cw20_balance, Uint128::from(8u128)); + let cw20_balance = cw20.query_balance(&router, andr.get_wallet("recipient1")); + assert_eq!(cw20_balance, Uint128::from(200u128)); + let cw20_balance = cw20.query_balance(&router, andr.get_wallet("recipient2")); + assert_eq!(cw20_balance, Uint128::from(800u128)); + let cw20_balance = cw20.query_balance(&router, owner); + assert_eq!(cw20_balance, Uint128::from(1_000_000u128 - 1000u128)); } -#[test] -fn test_splitter_cw20_with_remainder() { - let mut router = mock_app(None); - let andr = MockAndromedaBuilder::new(&mut router, "admin") - .with_wallets(vec![ - ("owner", vec![coin(10000, "uandr")]), - ("recipient1", vec![]), - ("recipient2", vec![]), - ("recipient3", vec![]), - ]) - .with_contracts(vec![ - ("app-contract", mock_andromeda_app()), - ("splitter", mock_andromeda_splitter()), - ("cw20", mock_andromeda_cw20()), - ]) - .build(&mut router); - let owner = andr.get_wallet("owner"); - let recipient_1 = andr.get_wallet("recipient1"); - let recipient_2 = andr.get_wallet("recipient2"); - let recipient_3 = andr.get_wallet("recipient3"); - - let app_code_id = andr.get_code_id(&mut router, "app-contract"); - - let initial_balances = vec![Cw20Coin { - address: owner.to_string(), - amount: Uint128::new(1000000u128), - }]; +#[rstest] +fn test_successful_set_amount_splitter_cw20_with_remainder(setup: TestCase) { + let TestCase { + mut router, + andr, + splitter, + cw20, + } = setup; - let cw20_init_msg = mock_cw20_instantiate_msg( - None, - "Test Tokens".to_string(), - "TTT".to_string(), - 6, - initial_balances.clone(), - Some(mock_minter( - owner.to_string(), - Some(Uint128::from(10000000000u128)), - )), - andr.kernel.addr().to_string(), - ); - let cw20_component = AppComponent::new( - "cw20".to_string(), - "cw20".to_string(), - to_json_binary(&cw20_init_msg).unwrap(), - ); + let owner = andr.get_wallet("owner"); let splitter_recipients = vec![ AddressPercent { - recipient: Recipient::from_string(recipient_1.to_string()), - percent: Decimal::from_str("0.2").unwrap(), + recipient: Recipient::from_string(andr.get_wallet("recipient1").to_string()), + percent: Decimal::from_ratio(Uint128::from(1u128), Uint128::from(10u128)), }, AddressPercent { - recipient: Recipient::from_string(recipient_2.to_string()), - percent: Decimal::from_str("0.3").unwrap(), + recipient: Recipient::from_string(andr.get_wallet("recipient2").to_string()), + percent: Decimal::from_ratio(Uint128::from(1u128), Uint128::from(10u128)), }, ]; - let splitter_init_msg = mock_splitter_instantiate_msg( - splitter_recipients, - andr.kernel.addr().clone(), - None, - None, - Some(Recipient::from_string(recipient_3.to_string())), - ); - let splitter_app_component = AppComponent { - name: "splitter".to_string(), - component_type: ComponentType::new(splitter_init_msg), - ado_type: "splitter".to_string(), - }; - - let app_components = vec![splitter_app_component.clone(), cw20_component.clone()]; - let app = MockAppContract::instantiate( - app_code_id, - owner, - &mut router, - "Splitter App", - app_components, - andr.kernel.addr(), - None, - ); - - let splitter: MockSplitter = - app.query_ado_by_component_name(&router, splitter_app_component.name); - - let cw20: MockCW20 = app.query_ado_by_component_name(&router, cw20_component.name); + splitter + .execute_update_recipients(&mut router, owner.clone(), &[], splitter_recipients) + .unwrap(); let hook_msg = Cw20HookMsg::Send { config: None }; @@ -291,15 +288,15 @@ fn test_splitter_cw20_with_remainder() { &mut router, owner.clone(), splitter.addr(), - Uint128::new(10), + Uint128::new(1000), &hook_msg, ) .unwrap(); - let cw20_balance = cw20.query_balance(&router, recipient_1); - assert_eq!(cw20_balance, Uint128::from(2u128)); - let cw20_balance = cw20.query_balance(&router, recipient_2); - assert_eq!(cw20_balance, Uint128::from(3u128)); - let cw20_balance = cw20.query_balance(&router, recipient_3); - assert_eq!(cw20_balance, Uint128::from(5u128)); + let cw20_balance = cw20.query_balance(&router, andr.get_wallet("recipient1")); + assert_eq!(cw20_balance, Uint128::from(100u128)); + let cw20_balance = cw20.query_balance(&router, andr.get_wallet("recipient2")); + assert_eq!(cw20_balance, Uint128::from(100u128)); + let cw20_balance = cw20.query_balance(&router, owner); + assert_eq!(cw20_balance, Uint128::from(1_000_000u128 - 200u128)); } From 6abc8659debafcf6043272ff9af43ed38ae9426f Mon Sep 17 00:00:00 2001 From: Joe Monem Date: Wed, 11 Dec 2024 09:10:57 +0200 Subject: [PATCH 25/27] chore: version bump set amount splitter --- Cargo.lock | 2 +- contracts/finance/andromeda-set-amount-splitter/Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 9caf5f8c0..7ef7aa64a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -796,7 +796,7 @@ dependencies = [ [[package]] name = "andromeda-set-amount-splitter" -version = "1.1.0-beta" +version = "2.0.0-b.1" dependencies = [ "andromeda-app", "andromeda-finance", diff --git a/contracts/finance/andromeda-set-amount-splitter/Cargo.toml b/contracts/finance/andromeda-set-amount-splitter/Cargo.toml index e9e37bc6b..6c97cb745 100644 --- a/contracts/finance/andromeda-set-amount-splitter/Cargo.toml +++ b/contracts/finance/andromeda-set-amount-splitter/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "andromeda-set-amount-splitter" -version = "1.1.0-beta" +version = "2.0.0-b.1" edition = "2021" rust-version = "1.75.0" From fcc5541cdd3cdf1d91235d4b7bea0a289ebe71f6 Mon Sep 17 00:00:00 2001 From: Joe Monem Date: Wed, 11 Dec 2024 10:24:18 +0200 Subject: [PATCH 26/27] ref: simplify init in splitter and set amount splitter --- .../src/contract.rs | 41 ++++++------------- .../andromeda-splitter/src/contract.rs | 26 ++++-------- 2 files changed, 22 insertions(+), 45 deletions(-) diff --git a/contracts/finance/andromeda-set-amount-splitter/src/contract.rs b/contracts/finance/andromeda-set-amount-splitter/src/contract.rs index be03d5cbe..7cff6d2e3 100644 --- a/contracts/finance/andromeda-set-amount-splitter/src/contract.rs +++ b/contracts/finance/andromeda-set-amount-splitter/src/contract.rs @@ -37,36 +37,21 @@ pub fn instantiate( info: MessageInfo, msg: InstantiateMsg, ) -> Result { - let splitter = match msg.lock_time { - Some(ref lock_time) => { - // New lock time can't be too short - ensure!( - lock_time.get_time(&env.block).seconds() >= ONE_DAY, - ContractError::LockTimeTooShort {} - ); - - // New lock time can't be too long - ensure!( - lock_time.get_time(&env.block).seconds() <= ONE_YEAR, - ContractError::LockTimeTooLong {} - ); - Splitter { - recipients: msg.recipients.clone(), - lock: lock_time.get_time(&env.block), - default_recipient: msg.default_recipient.clone(), - } - } - None => { - Splitter { - recipients: msg.recipients.clone(), - // If locking isn't desired upon instantiation, it's automatically set to 0 - lock: Milliseconds::default(), - default_recipient: msg.default_recipient.clone(), - } - } + let lock = if let Some(ref lock_time) = msg.lock_time { + let lock_seconds = lock_time.get_time(&env.block).seconds(); + ensure!(lock_seconds >= ONE_DAY, ContractError::LockTimeTooShort {}); + ensure!(lock_seconds <= ONE_YEAR, ContractError::LockTimeTooLong {}); + lock_time.get_time(&env.block) + } else { + Milliseconds::default() + }; + let splitter = Splitter { + recipients: msg.recipients.clone(), + lock, + default_recipient: msg.default_recipient.clone(), }; - // Save kernel address after validating it + // Save kernel address after validating it SPLITTER.save(deps.storage, &splitter)?; let inst_resp = ADOContract::default().instantiate( diff --git a/contracts/finance/andromeda-splitter/src/contract.rs b/contracts/finance/andromeda-splitter/src/contract.rs index 139f6b289..41875a5bb 100644 --- a/contracts/finance/andromeda-splitter/src/contract.rs +++ b/contracts/finance/andromeda-splitter/src/contract.rs @@ -28,23 +28,15 @@ pub fn instantiate( info: MessageInfo, msg: InstantiateMsg, ) -> Result { - let splitter = match msg.lock_time { - Some(ref lock_time) => { - let time = validate_expiry_duration(lock_time, &env.block)?; - Splitter { - recipients: msg.recipients.clone(), - lock: time, - default_recipient: msg.default_recipient.clone(), - } - } - None => { - Splitter { - recipients: msg.recipients.clone(), - // If locking isn't desired upon instantiation, it's automatically set to 0 - lock: Milliseconds::default(), - default_recipient: msg.default_recipient.clone(), - } - } + let splitter = Splitter { + recipients: msg.recipients.clone(), + lock: msg + .clone() + .lock_time + .map(|lock_time| validate_expiry_duration(&lock_time, &env.block)) + .transpose()? + .unwrap_or_default(), + default_recipient: msg.default_recipient.clone(), }; // Save kernel address after validating it From abe11b211595424d5bcf89a597372a0adb3f47c7 Mon Sep 17 00:00:00 2001 From: Joe Monem Date: Wed, 11 Dec 2024 13:22:38 +0200 Subject: [PATCH 27/27] fix: version change --- Cargo.lock | 4 ++-- contracts/finance/andromeda-set-amount-splitter/Cargo.toml | 2 +- contracts/finance/andromeda-splitter/Cargo.toml | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 752f7754c..371f6e488 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -812,7 +812,7 @@ dependencies = [ [[package]] name = "andromeda-set-amount-splitter" -version = "2.0.0-b.1" +version = "1.2.0-b.1" dependencies = [ "andromeda-app", "andromeda-finance", @@ -849,7 +849,7 @@ dependencies = [ [[package]] name = "andromeda-splitter" -version = "3.0.0-b.1" +version = "2.3.0-b.1" dependencies = [ "andromeda-app", "andromeda-finance", diff --git a/contracts/finance/andromeda-set-amount-splitter/Cargo.toml b/contracts/finance/andromeda-set-amount-splitter/Cargo.toml index 6c97cb745..48f2f415f 100644 --- a/contracts/finance/andromeda-set-amount-splitter/Cargo.toml +++ b/contracts/finance/andromeda-set-amount-splitter/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "andromeda-set-amount-splitter" -version = "2.0.0-b.1" +version = "1.2.0-b.1" edition = "2021" rust-version = "1.75.0" diff --git a/contracts/finance/andromeda-splitter/Cargo.toml b/contracts/finance/andromeda-splitter/Cargo.toml index 5a87cb54e..3c6d6fad6 100644 --- a/contracts/finance/andromeda-splitter/Cargo.toml +++ b/contracts/finance/andromeda-splitter/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "andromeda-splitter" -version = "3.0.0-b.1" +version = "2.3.0-b.1" edition = "2021" rust-version = "1.75.0"