From fb8439939015b34cca8d5a760979a75eda2a87f7 Mon Sep 17 00:00:00 2001 From: Laurens <32776056+LaurensKubat@users.noreply.github.com> Date: Fri, 17 May 2024 12:12:11 +0200 Subject: [PATCH 1/6] set incentive admin in instantiate fmt --- .../contracts/merkle-incentives/src/contract.rs | 8 ++++---- smart-contracts/contracts/merkle-incentives/src/msg.rs | 4 +++- .../merkle-incentives/src/test_tube/initialize.rs | 4 +++- 3 files changed, 10 insertions(+), 6 deletions(-) diff --git a/smart-contracts/contracts/merkle-incentives/src/contract.rs b/smart-contracts/contracts/merkle-incentives/src/contract.rs index 6f63ae7cf..4197001f4 100644 --- a/smart-contracts/contracts/merkle-incentives/src/contract.rs +++ b/smart-contracts/contracts/merkle-incentives/src/contract.rs @@ -19,14 +19,14 @@ const CONTRACT_VERSION: &str = env!("CARGO_PKG_VERSION"); pub fn instantiate( deps: DepsMut, _env: Env, - info: MessageInfo, - _msg: InstantiateMsg, + _info: MessageInfo, + msg: InstantiateMsg, ) -> Result { set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?; - INCENTIVES_ADMIN.save(deps.storage, &info.sender)?; + INCENTIVES_ADMIN.save(deps.storage, &deps.api.addr_validate(&msg.incentive_admin)?)?; - Ok(Response::default().add_attribute("incentive_admin", info.sender)) + Ok(Response::default().add_attribute("incentive_admin", msg.incentive_admin)) } #[cfg_attr(not(feature = "library"), entry_point)] diff --git a/smart-contracts/contracts/merkle-incentives/src/msg.rs b/smart-contracts/contracts/merkle-incentives/src/msg.rs index 9a3fa1054..c2d8054ca 100644 --- a/smart-contracts/contracts/merkle-incentives/src/msg.rs +++ b/smart-contracts/contracts/merkle-incentives/src/msg.rs @@ -7,7 +7,9 @@ use crate::{ }; #[cw_serde] -pub struct InstantiateMsg {} +pub struct InstantiateMsg { + pub incentive_admin: String, +} #[cw_serde] pub enum ExecuteMsg { diff --git a/smart-contracts/contracts/merkle-incentives/src/test_tube/initialize.rs b/smart-contracts/contracts/merkle-incentives/src/test_tube/initialize.rs index bf4afae4a..11e4c2e48 100644 --- a/smart-contracts/contracts/merkle-incentives/src/test_tube/initialize.rs +++ b/smart-contracts/contracts/merkle-incentives/src/test_tube/initialize.rs @@ -41,7 +41,9 @@ pub mod initialize { let contract = wasm .instantiate( code_id, - &InstantiateMsg {}, + &InstantiateMsg { + incentive_admin: admin.address(), + }, Some(admin.address().as_str()), Some("merkle-incentives"), &[], From ba687a6db23ca91a1acab557845a7a81b783ac5c Mon Sep 17 00:00:00 2001 From: Laurens <32776056+LaurensKubat@users.noreply.github.com> Date: Fri, 17 May 2024 12:22:41 +0200 Subject: [PATCH 2/6] add incentive admin to readme --- smart-contracts/contracts/merkle-incentives/README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/smart-contracts/contracts/merkle-incentives/README.md b/smart-contracts/contracts/merkle-incentives/README.md index c79202ea5..06d0fa6ac 100644 --- a/smart-contracts/contracts/merkle-incentives/README.md +++ b/smart-contracts/contracts/merkle-incentives/README.md @@ -24,6 +24,9 @@ The merkle proof/root generator will strip commas and spaces from the csv before during the claim, the contract will automatically sort the tokens and strip commas/whitespace, so no additional work is required on the client side in order to claim, as long as they have the proper proof generated. +## Updating merkle root +The merkle root of the contract is updated by the incentives admin. This admin is an address set at instantiation and can be updated by the contract admin. + ## Test The test data directory contains a csv called testdata.csv. This is the source document that a merkle tree can be generated on. As you can see we can easily add more rows to add users and more columns to add more incentive tokens. From f2dc9586dc6117ee7ef2c95775d53bb37f9826c4 Mon Sep 17 00:00:00 2001 From: Laurens <32776056+LaurensKubat@users.noreply.github.com> Date: Fri, 17 May 2024 13:08:25 +0200 Subject: [PATCH 3/6] move test-tube tests to a tests dir --- .../contracts/merkle-incentives/.cargo/config | 2 +- .../contracts/merkle-incentives/src/lib.rs | 3 - .../src/test_tube/initialize.rs | 62 --- .../merkle-incentives/src/test_tube/merkle.rs | 446 ------------------ .../merkle-incentives/src/test_tube/mod.rs | 2 - .../merkle-incentives/tests/initialize.rs | 59 +++ .../contracts/merkle-incentives/tests/lib.rs | 4 + .../merkle-incentives/tests/merkle.rs | 440 +++++++++++++++++ 8 files changed, 504 insertions(+), 514 deletions(-) delete mode 100644 smart-contracts/contracts/merkle-incentives/src/test_tube/initialize.rs delete mode 100644 smart-contracts/contracts/merkle-incentives/src/test_tube/merkle.rs delete mode 100644 smart-contracts/contracts/merkle-incentives/src/test_tube/mod.rs create mode 100644 smart-contracts/contracts/merkle-incentives/tests/initialize.rs create mode 100644 smart-contracts/contracts/merkle-incentives/tests/lib.rs create mode 100644 smart-contracts/contracts/merkle-incentives/tests/merkle.rs diff --git a/smart-contracts/contracts/merkle-incentives/.cargo/config b/smart-contracts/contracts/merkle-incentives/.cargo/config index 787f73e72..2a2ac1382 100644 --- a/smart-contracts/contracts/merkle-incentives/.cargo/config +++ b/smart-contracts/contracts/merkle-incentives/.cargo/config @@ -2,5 +2,5 @@ wasm = "build --release --lib --target wasm32-unknown-unknown" unit-test = "test --lib" schema = "run --bin schema" -test-tube = "test --package merkle-incentives --lib -- --include-ignored test_tube:: --nocapture --test-threads=1" +test-tube = "test --package merkle-incentives --test merkle -- --include-ignored --nocapture --test-threads=1" test-tube-build = "build --release --lib --target wasm32-unknown-unknown --target-dir ./test-tube-build" diff --git a/smart-contracts/contracts/merkle-incentives/src/lib.rs b/smart-contracts/contracts/merkle-incentives/src/lib.rs index dd5744e91..1f9dca4f7 100644 --- a/smart-contracts/contracts/merkle-incentives/src/lib.rs +++ b/smart-contracts/contracts/merkle-incentives/src/lib.rs @@ -6,7 +6,4 @@ pub mod incentives; pub mod msg; pub mod state; -#[cfg(test)] -mod test_tube; - pub use crate::error::ContractError; diff --git a/smart-contracts/contracts/merkle-incentives/src/test_tube/initialize.rs b/smart-contracts/contracts/merkle-incentives/src/test_tube/initialize.rs deleted file mode 100644 index 11e4c2e48..000000000 --- a/smart-contracts/contracts/merkle-incentives/src/test_tube/initialize.rs +++ /dev/null @@ -1,62 +0,0 @@ -#[cfg(test)] -pub mod initialize { - use cosmwasm_std::{Addr, Coin}; - use osmosis_test_tube::{Account, Module, OsmosisTestApp, SigningAccount, Wasm}; - - use crate::msg::InstantiateMsg; - - pub fn default_init(gauge_coins: Vec) -> (OsmosisTestApp, Addr, SigningAccount) { - init_test_contract( - "./test-tube-build/wasm32-unknown-unknown/release/merkle_incentives.wasm", - gauge_coins, - ) - } - - pub fn init_test_contract( - filename: &str, - gauge_coins: Vec, - ) -> (OsmosisTestApp, Addr, SigningAccount) { - // Create new osmosis appchain instance - let app = OsmosisTestApp::new(); - let wasm = Wasm::new(&app); - - // Ensure uosmo is always included by checking and adding if necessary - let mut coins_with_uosmo = gauge_coins.clone(); - if !gauge_coins.iter().any(|coin| coin.denom == "uosmo") { - coins_with_uosmo.push(Coin::new(100_000_000_000_000_000_000, "uosmo")); - } - - // Create new account with initial funds - let admin = app.init_account(&coins_with_uosmo).unwrap(); - - // Load compiled wasm bytecode - let wasm_byte_code = std::fs::read(filename).unwrap(); - let code_id = wasm - .store_code(&wasm_byte_code, None, &admin) - .unwrap() - .data - .code_id; - - // Instantiate vault - let contract = wasm - .instantiate( - code_id, - &InstantiateMsg { - incentive_admin: admin.address(), - }, - Some(admin.address().as_str()), - Some("merkle-incentives"), - &[], - &admin, - ) - .unwrap(); - - (app, Addr::unchecked(contract.data.address), admin) - } - - #[test] - #[ignore] - fn default_init_works() { - // TODO - } -} diff --git a/smart-contracts/contracts/merkle-incentives/src/test_tube/merkle.rs b/smart-contracts/contracts/merkle-incentives/src/test_tube/merkle.rs deleted file mode 100644 index cca742f05..000000000 --- a/smart-contracts/contracts/merkle-incentives/src/test_tube/merkle.rs +++ /dev/null @@ -1,446 +0,0 @@ -#[cfg(test)] -mod tests { - use crate::{ - admin::execute::AdminExecuteMsg, - incentives::{execute::IncentivesExecuteMsg, CoinVec}, - msg::{ExecuteMsg, QueryMsg}, - state::ClaimAccount, - test_tube::initialize::initialize::default_init, - }; - use base64::{engine::general_purpose::STANDARD, Engine}; - use cosmwasm_std::{Coin, Uint128}; - use cw_storage_plus::KeyDeserialize; - use osmosis_test_tube::osmosis_std::types::cosmos::bank::v1beta1::QueryBalanceRequest; - use osmosis_test_tube::{ - osmosis_std::types::cosmos::{bank::v1beta1::MsgSend, base::v1beta1::Coin as OsmoCoin}, - Account, Bank, Module, Wasm, - }; - use rs_merkle::{ - algorithms::{self, Sha256}, - Hasher, MerkleProof, MerkleTree, - }; - - #[test] - #[ignore] - fn merkle_complete_cycle_works() { - let (app, contract, admin) = default_init(vec![Coin { - denom: "ugauge".to_string(), - amount: Uint128::new(1000000000000000000u128), - }]); - let bank = Bank::new(&app); - let wasm = Wasm::new(&app); - - // Create accounts - let accounts = app - .init_accounts(&[Coin::new(100_000_000_000, "uosmo")], 10) - .unwrap(); - - // get all eligible claimers - let leaves_str = vec![ - format!("{}900000000ugauge", accounts[0].address().to_string()).to_string(), - format!("{}9000000000ugauge", accounts[0].address().to_string()).to_string(), - format!("{}90000000000ugauge", accounts[0].address().to_string()).to_string(), - format!("{}900000000000ugauge", accounts[0].address().to_string()).to_string(), - format!("{}9000000000000ugauge", accounts[0].address().to_string()).to_string(), - format!("{}90000000000000ugauge", accounts[0].address().to_string()).to_string(), - format!("{}900000000000000ugauge", accounts[0].address().to_string()).to_string(), - format!( - "{}9000000000900000ugauge", - accounts[0].address().to_string() - ) - .to_string(), - format!( - "{}90000000009000000ugauge", - accounts[0].address().to_string() - ) - .to_string(), - format!( - "{}900000000090000000ugauge", - accounts[0].address().to_string() - ) - .to_string(), - ]; - - // , accounts[0].address().to_string()which seems to generate this root: 0hGvbH+l9pdPgOmJY6wZuwjsrvtPsuslgTURavrUP6I= - - // create leave hashes from above strings - let leaves = leaves_str - .iter() - .map(|x| algorithms::Sha256::hash(x.as_bytes())) - .collect::>(); - - // construct merkle tree from leaves - let merkle_tree: MerkleTree = - rs_merkle::MerkleTree::from_leaves(&leaves); - - // todo: we can easily make an iterator here that will go thru and create claim accounts for each leaf, instead of just the first one - let indices_to_prove = vec![0]; - let leaves_to_prove = leaves.get(0..1).unwrap(); - // gen proof for first leaf - let merkle_proof = merkle_tree.proof(&indices_to_prove); - - let merkle_root = merkle_tree.root().unwrap(); - - // Serialize proof to pass it to the client - let proof_bytes = merkle_proof.to_bytes(); - - // Parse proof back on the client - let proof = MerkleProof::::try_from(proof_bytes).unwrap(); - - // quickly sanity check if we did anything dumb - assert!(proof.verify( - merkle_root, - &indices_to_prove, - leaves_to_prove, - leaves.len() - )); - - // ClaimAccounts related to above ToVerifies - let claim_accounts: Vec = vec![ClaimAccount { - proof: merkle_proof - .proof_hashes() - .iter() - .map(|x| Vec::from_slice(x).unwrap()) - .collect(), - coins: CoinVec::from(vec![Coin::new(900000000, "ugauge")]), - }]; - - // Fund the incentives contract as admin - let _ = bank.send( - MsgSend { - from_address: admin.address().to_string(), - to_address: contract.to_string(), - amount: vec![OsmoCoin { - amount: "1000000000000000000".to_string(), // 1_000_000_000_000.000000 GAUGE (1T $GAUGE) - denom: "ugauge".to_string(), - }], - }, - &admin, - ); - - // Assert initial balance on gauge contract - let contract_balance = bank - .query_balance(&QueryBalanceRequest { - address: contract.to_string(), - denom: "ugauge".to_string(), - }) - .unwrap(); - assert_eq!( - contract_balance.balance.unwrap().amount, - "1000000000000000000".to_string() - ); - - // Execute AdminMsg::UpdateAdmin - let new_admin = app - .init_account(&[Coin::new(1_000_000_000, "uosmo")]) - .unwrap(); - let _ = wasm - .execute( - contract.as_str(), - &ExecuteMsg::AdminMsg(AdminExecuteMsg::UpdateAdmin { - new_admin: new_admin.address(), - }), - &[], - &admin, - ) - .unwrap(); - - // TODO: Assert admin changed and queriable - - // AdminMsg::UpdateMerkleRoot - let binding = STANDARD.encode(merkle_tree.root().unwrap()); - let merkle_root: &str = binding.as_str(); - let _ = wasm - .execute( - contract.as_str(), - &ExecuteMsg::AdminMsg(AdminExecuteMsg::UpdateMerkleRoot { - new_root: merkle_root.to_string(), - }), - &[], - &new_admin, - ) - .unwrap(); - - // Execute IncentivesMsg::Claim - for (index, claim_account) in claim_accounts.iter().enumerate() { - let mut proof_hashes: Vec<[u8; 32]> = Vec::new(); - for proof in &claim_account.proof { - if proof.len() == 32 { - let mut arr = [0u8; 32]; - arr.copy_from_slice(&proof); - proof_hashes.push(arr); - } else { - eprintln!("Error: Hash is not 32 bytes."); - } - } - - // Execute claim for the current user - let _ = wasm - .execute( - contract.as_str(), - &ExecuteMsg::IncentivesMsg(IncentivesExecuteMsg::Claim { - address: accounts.get(index).unwrap().address().to_string(), - coins: claim_account.coins.clone(), - proof_hashes, - leaf_index: index, - total_leaves_count: 10usize, - // total_leaves_count: claim_accounts.len(), // TODO: reimplement this with all 10 users claiming - }), - &[], - &accounts.get(index).unwrap(), - ) - .unwrap(); - - // Assert bank send occurred - let address_balance = bank - .query_balance(&QueryBalanceRequest { - address: accounts.get(index).unwrap().address().to_string(), - denom: "ugauge".to_string(), - }) - .unwrap(); - assert_eq!( - address_balance.balance.unwrap().amount, - claim_account - .coins - .coins() - .get(0) - .unwrap() - .amount - .to_string() - ); - } - - // Assert final balance on gauge contract - // Positive due to precision loss - let contract_balance = bank - .query_balance(&QueryBalanceRequest { - address: contract.to_string(), - denom: "ugauge".to_string(), - }) - .unwrap(); - assert_eq!( - contract_balance.balance.unwrap().amount, - // "100000".to_string() // TODO: reimplement this with all 10 users claiming - "999999999100000000".to_string() - ); - } - - #[test] - #[ignore] - fn merkle_complete_cycle_works_mainnet_data() { - let (app, contract, admin) = default_init(vec![ - Coin { - denom: "factory/osmo1nz7qdp7eg30sr959wvrwn9j9370h4xt6ttm0h3/ussosmo".to_string(), - amount: Uint128::new(300000000u128), - }, - Coin { - denom: "ibc/206DE6A2D1882D91A12F01C8BD85A33C9673C6A39761D36CF736436D6EF4C588" - .to_string(), - amount: Uint128::new(27500000u128), - }, - ]); - let bank = Bank::new(&app); - let wasm = Wasm::new(&app); - - // Fund the incentives contract as admin - let _ = bank.send( - MsgSend { - from_address: admin.address().to_string(), - to_address: contract.to_string(), - amount: vec![OsmoCoin { - amount: "300000000".to_string(), - denom: "factory/osmo1nz7qdp7eg30sr959wvrwn9j9370h4xt6ttm0h3/ussosmo" - .to_string(), - }], - }, - &admin, - ); - let _ = bank.send( - MsgSend { - from_address: admin.address().to_string(), - to_address: contract.to_string(), - amount: vec![OsmoCoin { - amount: "27500000".to_string(), - denom: "ibc/206DE6A2D1882D91A12F01C8BD85A33C9673C6A39761D36CF736436D6EF4C588" - .to_string(), - }], - }, - &admin, - ); - - // Assert initial balance on gauge contract - let contract_balance = bank - .query_balance(&QueryBalanceRequest { - address: contract.to_string(), - denom: "factory/osmo1nz7qdp7eg30sr959wvrwn9j9370h4xt6ttm0h3/ussosmo".to_string(), - }) - .unwrap(); - assert_eq!( - contract_balance.balance.unwrap().amount, - "300000000".to_string() - ); - let contract_balance = bank - .query_balance(&QueryBalanceRequest { - address: contract.to_string(), - denom: "ibc/206DE6A2D1882D91A12F01C8BD85A33C9673C6A39761D36CF736436D6EF4C588" - .to_string(), - }) - .unwrap(); - assert_eq!( - contract_balance.balance.unwrap().amount, - "27500000".to_string() - ); - - // AdminMsg::UpdateMerkleRoot - let merkle_root: &str = "M1aYKj1y+ClZeeyNzfznnpJIgWhuPvpExosYK0G5nFU="; - let _ = wasm - .execute( - contract.as_str(), - &ExecuteMsg::AdminMsg(AdminExecuteMsg::UpdateMerkleRoot { - new_root: merkle_root.to_string(), - }), - &[], - &admin, - ) - .unwrap(); - - let proofs: Vec<(&str, CoinVec, Vec>)> = vec![ - ( - "osmo1t9g70h7su684gggcl0g7e2l3r3gjk9x4eppvxv", - CoinVec::from(vec![ - Coin::new( - 659745, - "factory/osmo1nz7qdp7eg30sr959wvrwn9j9370h4xt6ttm0h3/ussosmo", - ), - Coin::new( - 60426, - "ibc/206DE6A2D1882D91A12F01C8BD85A33C9673C6A39761D36CF736436D6EF4C588", - ), - ]), - vec![ - STANDARD - .decode("j3zyEtaQ88vnMBN4Wbb2vssWy3LEGJerxlz2xyHcWEY=") - .unwrap(), - STANDARD - .decode("L7EOyHK/x+/x/Pc0osavxYd8qqb6bg9W5E2ppXOxHKc=") - .unwrap(), - ], - ), - ( - "osmo1dx6rd4j4qg8tgzzm2l0a0yl8520xk8ukdfs65d", - CoinVec::from(vec![ - Coin::new( - 146212740, - "factory/osmo1nz7qdp7eg30sr959wvrwn9j9370h4xt6ttm0h3/ussosmo", - ), - Coin::new( - 13378537, - "ibc/206DE6A2D1882D91A12F01C8BD85A33C9673C6A39761D36CF736436D6EF4C588", - ), - ]), - vec![ - STANDARD - .decode("VJEFLHCJcLsip2zZf/C/VYZCz8x8ioqLOSiZHCizpj4=") - .unwrap(), - STANDARD - .decode("L7EOyHK/x+/x/Pc0osavxYd8qqb6bg9W5E2ppXOxHKc=") - .unwrap(), - ], - ), - ( - "osmo16nsxukkff43y703xzj4p7mcg9z7enuher6h4t4", - CoinVec::from(vec![ - Coin::new( - 11477154, - "factory/osmo1nz7qdp7eg30sr959wvrwn9j9370h4xt6ttm0h3/ussosmo", - ), - Coin::new( - 1050442, - "ibc/206DE6A2D1882D91A12F01C8BD85A33C9673C6A39761D36CF736436D6EF4C588", - ), - ]), - vec![ - STANDARD - .decode("bXHudyZ1T8GL1JwCp7m2pWiNfA8ixD8S9POOelU0qZI=") - .unwrap(), - STANDARD - .decode("DZuG8wjPum3sMPU98Ld4WvSXcHOMz8v32twL1/OOLyQ=") - .unwrap(), - ], - ), - ( - "osmo1c7m4gmauthgs5rhacgvfxkp6z4l38sclu4gtaw", - CoinVec::from(vec![ - Coin::new( - 35479062, - "factory/osmo1nz7qdp7eg30sr959wvrwn9j9370h4xt6ttm0h3/ussosmo", - ), - Coin::new( - 3247592, - "ibc/206DE6A2D1882D91A12F01C8BD85A33C9673C6A39761D36CF736436D6EF4C588", - ), - ]), - vec![ - STANDARD - .decode("MnF8oGUkUxO9Z1J+E9CnOe0fPEzzQZ85X0W77btlINo=") - .unwrap(), - STANDARD - .decode("DZuG8wjPum3sMPU98Ld4WvSXcHOMz8v32twL1/OOLyQ=") - .unwrap(), - ], - ), - ]; - - // Declare proof_hashes to store (address, coins, proof_hash) - let mut proof_hashes: Vec<(&str, CoinVec, Vec<[u8; 32]>)> = Vec::new(); - - for (address, coins, proofs) in &proofs { - let mut user_proofs: Vec<[u8; 32]> = Vec::new(); - for proof in proofs { - if proof.len() == 32 { - let mut arr = [0u8; 32]; - arr.copy_from_slice(proof); - user_proofs.push(arr); - } else { - eprintln!("Error: Hash is not 32 bytes."); - } - } - if user_proofs.len() == 2 { - proof_hashes.push((*address, coins.clone(), user_proofs)); - } else { - eprintln!("Error: Expected two proofs for address {}", address); - } - } - - // No need to change the logic for executing IncentivesMsg::Claim as we are directly using proofs - for (index, (address, coins, user_proofs)) in proof_hashes.iter().enumerate() { - // Execute claim for the current user using the address, coins, and the two proof hashes - let msg = ExecuteMsg::IncentivesMsg(IncentivesExecuteMsg::Claim { - address: address.to_string(), - coins: coins.clone(), - proof_hashes: user_proofs.clone(), // Pass the vector of two [u8; 32] arrays - leaf_index: index, - total_leaves_count: proof_hashes.len(), - }); - let _ = wasm - .execute( - contract.as_str(), - &msg, - &[], // always empty - &admin, // admin claims on behalf of users - ) - .unwrap(); - - let _: Option = wasm - .query( - contract.as_str(), - &QueryMsg::IncentivesQuery( - crate::incentives::query::IncentivesQueryMsg::ClaimedIncentives { - address: address.to_string(), - }, - ), - ) - .unwrap(); - } - } -} diff --git a/smart-contracts/contracts/merkle-incentives/src/test_tube/mod.rs b/smart-contracts/contracts/merkle-incentives/src/test_tube/mod.rs deleted file mode 100644 index 690cab2e3..000000000 --- a/smart-contracts/contracts/merkle-incentives/src/test_tube/mod.rs +++ /dev/null @@ -1,2 +0,0 @@ -mod initialize; -mod merkle; diff --git a/smart-contracts/contracts/merkle-incentives/tests/initialize.rs b/smart-contracts/contracts/merkle-incentives/tests/initialize.rs new file mode 100644 index 000000000..0b9667ca3 --- /dev/null +++ b/smart-contracts/contracts/merkle-incentives/tests/initialize.rs @@ -0,0 +1,59 @@ +use cosmwasm_std::{Addr, Coin}; +use osmosis_test_tube::{Account, Module, OsmosisTestApp, SigningAccount, Wasm}; + +use merkle_incentives::msg::InstantiateMsg; + +pub fn default_init(gauge_coins: Vec) -> (OsmosisTestApp, Addr, SigningAccount) { + init_test_contract( + "./test-tube-build/wasm32-unknown-unknown/release/merkle_incentives.wasm", + gauge_coins, + ) +} + +pub fn init_test_contract( + filename: &str, + gauge_coins: Vec, +) -> (OsmosisTestApp, Addr, SigningAccount) { + // Create new osmosis appchain instance + let app = OsmosisTestApp::new(); + let wasm = Wasm::new(&app); + + // Ensure uosmo is always included by checking and adding if necessary + let mut coins_with_uosmo = gauge_coins.clone(); + if !gauge_coins.iter().any(|coin| coin.denom == "uosmo") { + coins_with_uosmo.push(Coin::new(100_000_000_000_000_000_000, "uosmo")); + } + + // Create new account with initial funds + let admin = app.init_account(&coins_with_uosmo).unwrap(); + + // Load compiled wasm bytecode + let wasm_byte_code = std::fs::read(filename).unwrap(); + let code_id = wasm + .store_code(&wasm_byte_code, None, &admin) + .unwrap() + .data + .code_id; + + // Instantiate vault + let contract = wasm + .instantiate( + code_id, + &InstantiateMsg { + incentive_admin: admin.address(), + }, + Some(admin.address().as_str()), + Some("merkle-incentives"), + &[], + &admin, + ) + .unwrap(); + + (app, Addr::unchecked(contract.data.address), admin) +} + +#[test] +#[ignore] +fn default_init_works() { + // TODO +} diff --git a/smart-contracts/contracts/merkle-incentives/tests/lib.rs b/smart-contracts/contracts/merkle-incentives/tests/lib.rs new file mode 100644 index 000000000..dafd59b7c --- /dev/null +++ b/smart-contracts/contracts/merkle-incentives/tests/lib.rs @@ -0,0 +1,4 @@ +pub mod initialize; +mod merkle; + +pub use initialize::default_init; diff --git a/smart-contracts/contracts/merkle-incentives/tests/merkle.rs b/smart-contracts/contracts/merkle-incentives/tests/merkle.rs new file mode 100644 index 000000000..95be7dc13 --- /dev/null +++ b/smart-contracts/contracts/merkle-incentives/tests/merkle.rs @@ -0,0 +1,440 @@ +use base64::{engine::general_purpose::STANDARD, Engine}; +use cosmwasm_std::{Coin, Uint128}; +use cw_storage_plus::KeyDeserialize; +use merkle_incentives::{ + admin::execute::AdminExecuteMsg, + incentives::{execute::IncentivesExecuteMsg, query::IncentivesQueryMsg, CoinVec}, + msg::{ExecuteMsg, QueryMsg}, + state::ClaimAccount, +}; +use osmosis_test_tube::osmosis_std::types::cosmos::bank::v1beta1::QueryBalanceRequest; +use osmosis_test_tube::{ + osmosis_std::types::cosmos::{bank::v1beta1::MsgSend, base::v1beta1::Coin as OsmoCoin}, + Account, Bank, Module, Wasm, +}; +use rs_merkle::{ + algorithms::{self, Sha256}, + Hasher, MerkleProof, MerkleTree, +}; + +#[path="initialize.rs"] mod initialize; + +#[test] +#[ignore] +fn merkle_complete_cycle_works() { + let (app, contract, admin) = initialize::default_init(vec![Coin { + denom: "ugauge".to_string(), + amount: Uint128::new(1000000000000000000u128), + }]); + let bank = Bank::new(&app); + let wasm = Wasm::new(&app); + + // Create accounts + let accounts = app + .init_accounts(&[Coin::new(100_000_000_000, "uosmo")], 10) + .unwrap(); + + // get all eligible claimers + let leaves_str = vec![ + format!("{}900000000ugauge", accounts[0].address().to_string()).to_string(), + format!("{}9000000000ugauge", accounts[0].address().to_string()).to_string(), + format!("{}90000000000ugauge", accounts[0].address().to_string()).to_string(), + format!("{}900000000000ugauge", accounts[0].address().to_string()).to_string(), + format!("{}9000000000000ugauge", accounts[0].address().to_string()).to_string(), + format!("{}90000000000000ugauge", accounts[0].address().to_string()).to_string(), + format!("{}900000000000000ugauge", accounts[0].address().to_string()).to_string(), + format!( + "{}9000000000900000ugauge", + accounts[0].address().to_string() + ) + .to_string(), + format!( + "{}90000000009000000ugauge", + accounts[0].address().to_string() + ) + .to_string(), + format!( + "{}900000000090000000ugauge", + accounts[0].address().to_string() + ) + .to_string(), + ]; + + // , accounts[0].address().to_string()which seems to generate this root: 0hGvbH+l9pdPgOmJY6wZuwjsrvtPsuslgTURavrUP6I= + + // create leave hashes from above strings + let leaves = leaves_str + .iter() + .map(|x| algorithms::Sha256::hash(x.as_bytes())) + .collect::>(); + + // construct merkle tree from leaves + let merkle_tree: MerkleTree = rs_merkle::MerkleTree::from_leaves(&leaves); + + // todo: we can easily make an iterator here that will go thru and create claim accounts for each leaf, instead of just the first one + let indices_to_prove = vec![0]; + let leaves_to_prove = leaves.get(0..1).unwrap(); + // gen proof for first leaf + let merkle_proof = merkle_tree.proof(&indices_to_prove); + + let merkle_root = merkle_tree.root().unwrap(); + + // Serialize proof to pass it to the client + let proof_bytes = merkle_proof.to_bytes(); + + // Parse proof back on the client + let proof = MerkleProof::::try_from(proof_bytes).unwrap(); + + // quickly sanity check if we did anything dumb + assert!(proof.verify( + merkle_root, + &indices_to_prove, + leaves_to_prove, + leaves.len() + )); + + // ClaimAccounts related to above ToVerifies + let claim_accounts: Vec = vec![ClaimAccount { + proof: merkle_proof + .proof_hashes() + .iter() + .map(|x| Vec::from_slice(x).unwrap()) + .collect(), + coins: CoinVec::from(vec![Coin::new(900000000, "ugauge")]), + }]; + + // Fund the incentives contract as admin + let _ = bank.send( + MsgSend { + from_address: admin.address().to_string(), + to_address: contract.to_string(), + amount: vec![OsmoCoin { + amount: "1000000000000000000".to_string(), // 1_000_000_000_000.000000 GAUGE (1T $GAUGE) + denom: "ugauge".to_string(), + }], + }, + &admin, + ); + + // Assert initial balance on gauge contract + let contract_balance = bank + .query_balance(&QueryBalanceRequest { + address: contract.to_string(), + denom: "ugauge".to_string(), + }) + .unwrap(); + assert_eq!( + contract_balance.balance.unwrap().amount, + "1000000000000000000".to_string() + ); + + // Execute AdminMsg::UpdateAdmin + let new_admin = app + .init_account(&[Coin::new(1_000_000_000, "uosmo")]) + .unwrap(); + let _ = wasm + .execute( + contract.as_str(), + &ExecuteMsg::AdminMsg(AdminExecuteMsg::UpdateAdmin { + new_admin: new_admin.address(), + }), + &[], + &admin, + ) + .unwrap(); + + // TODO: Assert admin changed and queriable + + // AdminMsg::UpdateMerkleRoot + let binding = STANDARD.encode(merkle_tree.root().unwrap()); + let merkle_root: &str = binding.as_str(); + let _ = wasm + .execute( + contract.as_str(), + &ExecuteMsg::AdminMsg(AdminExecuteMsg::UpdateMerkleRoot { + new_root: merkle_root.to_string(), + }), + &[], + &new_admin, + ) + .unwrap(); + + // Execute IncentivesMsg::Claim + for (index, claim_account) in claim_accounts.iter().enumerate() { + let mut proof_hashes: Vec<[u8; 32]> = Vec::new(); + for proof in &claim_account.proof { + if proof.len() == 32 { + let mut arr = [0u8; 32]; + arr.copy_from_slice(&proof); + proof_hashes.push(arr); + } else { + eprintln!("Error: Hash is not 32 bytes."); + } + } + + // Execute claim for the current user + let _ = wasm + .execute( + contract.as_str(), + &ExecuteMsg::IncentivesMsg(IncentivesExecuteMsg::Claim { + address: accounts.get(index).unwrap().address().to_string(), + coins: claim_account.coins.clone(), + proof_hashes, + leaf_index: index, + total_leaves_count: 10usize, + // total_leaves_count: claim_accounts.len(), // TODO: reimplement this with all 10 users claiming + }), + &[], + &accounts.get(index).unwrap(), + ) + .unwrap(); + + // Assert bank send occurred + let address_balance = bank + .query_balance(&QueryBalanceRequest { + address: accounts.get(index).unwrap().address().to_string(), + denom: "ugauge".to_string(), + }) + .unwrap(); + assert_eq!( + address_balance.balance.unwrap().amount, + claim_account + .coins + .coins() + .get(0) + .unwrap() + .amount + .to_string() + ); + } + + // Assert final balance on gauge contract + // Positive due to precision loss + let contract_balance = bank + .query_balance(&QueryBalanceRequest { + address: contract.to_string(), + denom: "ugauge".to_string(), + }) + .unwrap(); + assert_eq!( + contract_balance.balance.unwrap().amount, + // "100000".to_string() // TODO: reimplement this with all 10 users claiming + "999999999100000000".to_string() + ); +} + +#[test] +#[ignore] +fn merkle_complete_cycle_works_mainnet_data() { + let (app, contract, admin) = initialize::default_init(vec![ + Coin { + denom: "factory/osmo1nz7qdp7eg30sr959wvrwn9j9370h4xt6ttm0h3/ussosmo".to_string(), + amount: Uint128::new(300000000u128), + }, + Coin { + denom: "ibc/206DE6A2D1882D91A12F01C8BD85A33C9673C6A39761D36CF736436D6EF4C588" + .to_string(), + amount: Uint128::new(27500000u128), + }, + ]); + let bank = Bank::new(&app); + let wasm = Wasm::new(&app); + + // Fund the incentives contract as admin + let _ = bank.send( + MsgSend { + from_address: admin.address().to_string(), + to_address: contract.to_string(), + amount: vec![OsmoCoin { + amount: "300000000".to_string(), + denom: "factory/osmo1nz7qdp7eg30sr959wvrwn9j9370h4xt6ttm0h3/ussosmo".to_string(), + }], + }, + &admin, + ); + let _ = bank.send( + MsgSend { + from_address: admin.address().to_string(), + to_address: contract.to_string(), + amount: vec![OsmoCoin { + amount: "27500000".to_string(), + denom: "ibc/206DE6A2D1882D91A12F01C8BD85A33C9673C6A39761D36CF736436D6EF4C588" + .to_string(), + }], + }, + &admin, + ); + + // Assert initial balance on gauge contract + let contract_balance = bank + .query_balance(&QueryBalanceRequest { + address: contract.to_string(), + denom: "factory/osmo1nz7qdp7eg30sr959wvrwn9j9370h4xt6ttm0h3/ussosmo".to_string(), + }) + .unwrap(); + assert_eq!( + contract_balance.balance.unwrap().amount, + "300000000".to_string() + ); + let contract_balance = bank + .query_balance(&QueryBalanceRequest { + address: contract.to_string(), + denom: "ibc/206DE6A2D1882D91A12F01C8BD85A33C9673C6A39761D36CF736436D6EF4C588" + .to_string(), + }) + .unwrap(); + assert_eq!( + contract_balance.balance.unwrap().amount, + "27500000".to_string() + ); + + // AdminMsg::UpdateMerkleRoot + let merkle_root: &str = "M1aYKj1y+ClZeeyNzfznnpJIgWhuPvpExosYK0G5nFU="; + let _ = wasm + .execute( + contract.as_str(), + &ExecuteMsg::AdminMsg(AdminExecuteMsg::UpdateMerkleRoot { + new_root: merkle_root.to_string(), + }), + &[], + &admin, + ) + .unwrap(); + + let proofs: Vec<(&str, CoinVec, Vec>)> = vec![ + ( + "osmo1t9g70h7su684gggcl0g7e2l3r3gjk9x4eppvxv", + CoinVec::from(vec![ + Coin::new( + 659745, + "factory/osmo1nz7qdp7eg30sr959wvrwn9j9370h4xt6ttm0h3/ussosmo", + ), + Coin::new( + 60426, + "ibc/206DE6A2D1882D91A12F01C8BD85A33C9673C6A39761D36CF736436D6EF4C588", + ), + ]), + vec![ + STANDARD + .decode("j3zyEtaQ88vnMBN4Wbb2vssWy3LEGJerxlz2xyHcWEY=") + .unwrap(), + STANDARD + .decode("L7EOyHK/x+/x/Pc0osavxYd8qqb6bg9W5E2ppXOxHKc=") + .unwrap(), + ], + ), + ( + "osmo1dx6rd4j4qg8tgzzm2l0a0yl8520xk8ukdfs65d", + CoinVec::from(vec![ + Coin::new( + 146212740, + "factory/osmo1nz7qdp7eg30sr959wvrwn9j9370h4xt6ttm0h3/ussosmo", + ), + Coin::new( + 13378537, + "ibc/206DE6A2D1882D91A12F01C8BD85A33C9673C6A39761D36CF736436D6EF4C588", + ), + ]), + vec![ + STANDARD + .decode("VJEFLHCJcLsip2zZf/C/VYZCz8x8ioqLOSiZHCizpj4=") + .unwrap(), + STANDARD + .decode("L7EOyHK/x+/x/Pc0osavxYd8qqb6bg9W5E2ppXOxHKc=") + .unwrap(), + ], + ), + ( + "osmo16nsxukkff43y703xzj4p7mcg9z7enuher6h4t4", + CoinVec::from(vec![ + Coin::new( + 11477154, + "factory/osmo1nz7qdp7eg30sr959wvrwn9j9370h4xt6ttm0h3/ussosmo", + ), + Coin::new( + 1050442, + "ibc/206DE6A2D1882D91A12F01C8BD85A33C9673C6A39761D36CF736436D6EF4C588", + ), + ]), + vec![ + STANDARD + .decode("bXHudyZ1T8GL1JwCp7m2pWiNfA8ixD8S9POOelU0qZI=") + .unwrap(), + STANDARD + .decode("DZuG8wjPum3sMPU98Ld4WvSXcHOMz8v32twL1/OOLyQ=") + .unwrap(), + ], + ), + ( + "osmo1c7m4gmauthgs5rhacgvfxkp6z4l38sclu4gtaw", + CoinVec::from(vec![ + Coin::new( + 35479062, + "factory/osmo1nz7qdp7eg30sr959wvrwn9j9370h4xt6ttm0h3/ussosmo", + ), + Coin::new( + 3247592, + "ibc/206DE6A2D1882D91A12F01C8BD85A33C9673C6A39761D36CF736436D6EF4C588", + ), + ]), + vec![ + STANDARD + .decode("MnF8oGUkUxO9Z1J+E9CnOe0fPEzzQZ85X0W77btlINo=") + .unwrap(), + STANDARD + .decode("DZuG8wjPum3sMPU98Ld4WvSXcHOMz8v32twL1/OOLyQ=") + .unwrap(), + ], + ), + ]; + + // Declare proof_hashes to store (address, coins, proof_hash) + let mut proof_hashes: Vec<(&str, CoinVec, Vec<[u8; 32]>)> = Vec::new(); + + for (address, coins, proofs) in &proofs { + let mut user_proofs: Vec<[u8; 32]> = Vec::new(); + for proof in proofs { + if proof.len() == 32 { + let mut arr = [0u8; 32]; + arr.copy_from_slice(proof); + user_proofs.push(arr); + } else { + eprintln!("Error: Hash is not 32 bytes."); + } + } + if user_proofs.len() == 2 { + proof_hashes.push((*address, coins.clone(), user_proofs)); + } else { + eprintln!("Error: Expected two proofs for address {}", address); + } + } + + // No need to change the logic for executing IncentivesMsg::Claim as we are directly using proofs + for (index, (address, coins, user_proofs)) in proof_hashes.iter().enumerate() { + // Execute claim for the current user using the address, coins, and the two proof hashes + let msg = ExecuteMsg::IncentivesMsg(IncentivesExecuteMsg::Claim { + address: address.to_string(), + coins: coins.clone(), + proof_hashes: user_proofs.clone(), // Pass the vector of two [u8; 32] arrays + leaf_index: index, + total_leaves_count: proof_hashes.len(), + }); + let _ = wasm + .execute( + contract.as_str(), + &msg, + &[], // always empty + &admin, // admin claims on behalf of users + ) + .unwrap(); + + let _: Option = wasm + .query( + contract.as_str(), + &QueryMsg::IncentivesQuery(IncentivesQueryMsg::ClaimedIncentives { + address: address.to_string(), + }), + ) + .unwrap(); + } +} From f03df837bce4fe043bcaa3fe8dd86ee7eb8c96f1 Mon Sep 17 00:00:00 2001 From: Laurens <32776056+LaurensKubat@users.noreply.github.com> Date: Fri, 17 May 2024 13:15:13 +0200 Subject: [PATCH 4/6] add incentive admin to tests --- .../merkle-incentives/tests/initialize.rs | 16 ++++++++--- .../merkle-incentives/tests/merkle.rs | 27 ++++++++++++++----- 2 files changed, 33 insertions(+), 10 deletions(-) diff --git a/smart-contracts/contracts/merkle-incentives/tests/initialize.rs b/smart-contracts/contracts/merkle-incentives/tests/initialize.rs index 0b9667ca3..94b027ec4 100644 --- a/smart-contracts/contracts/merkle-incentives/tests/initialize.rs +++ b/smart-contracts/contracts/merkle-incentives/tests/initialize.rs @@ -3,7 +3,9 @@ use osmosis_test_tube::{Account, Module, OsmosisTestApp, SigningAccount, Wasm}; use merkle_incentives::msg::InstantiateMsg; -pub fn default_init(gauge_coins: Vec) -> (OsmosisTestApp, Addr, SigningAccount) { +pub fn default_init( + gauge_coins: Vec, +) -> (OsmosisTestApp, Addr, SigningAccount, SigningAccount) { init_test_contract( "./test-tube-build/wasm32-unknown-unknown/release/merkle_incentives.wasm", gauge_coins, @@ -13,7 +15,7 @@ pub fn default_init(gauge_coins: Vec) -> (OsmosisTestApp, Addr, SigningAcc pub fn init_test_contract( filename: &str, gauge_coins: Vec, -) -> (OsmosisTestApp, Addr, SigningAccount) { +) -> (OsmosisTestApp, Addr, SigningAccount, SigningAccount) { // Create new osmosis appchain instance let app = OsmosisTestApp::new(); let wasm = Wasm::new(&app); @@ -26,6 +28,7 @@ pub fn init_test_contract( // Create new account with initial funds let admin = app.init_account(&coins_with_uosmo).unwrap(); + let incentive_admin = app.init_account(&coins_with_uosmo).unwrap(); // Load compiled wasm bytecode let wasm_byte_code = std::fs::read(filename).unwrap(); @@ -40,7 +43,7 @@ pub fn init_test_contract( .instantiate( code_id, &InstantiateMsg { - incentive_admin: admin.address(), + incentive_admin: incentive_admin.address(), }, Some(admin.address().as_str()), Some("merkle-incentives"), @@ -49,7 +52,12 @@ pub fn init_test_contract( ) .unwrap(); - (app, Addr::unchecked(contract.data.address), admin) + ( + app, + Addr::unchecked(contract.data.address), + admin, + incentive_admin, + ) } #[test] diff --git a/smart-contracts/contracts/merkle-incentives/tests/merkle.rs b/smart-contracts/contracts/merkle-incentives/tests/merkle.rs index 95be7dc13..006f068e9 100644 --- a/smart-contracts/contracts/merkle-incentives/tests/merkle.rs +++ b/smart-contracts/contracts/merkle-incentives/tests/merkle.rs @@ -17,12 +17,13 @@ use rs_merkle::{ Hasher, MerkleProof, MerkleTree, }; -#[path="initialize.rs"] mod initialize; +#[path = "initialize.rs"] +mod initialize; #[test] #[ignore] fn merkle_complete_cycle_works() { - let (app, contract, admin) = initialize::default_init(vec![Coin { + let (app, contract, admin, incentive_admin) = initialize::default_init(vec![Coin { denom: "ugauge".to_string(), amount: Uint128::new(1000000000000000000u128), }]); @@ -128,6 +129,21 @@ fn merkle_complete_cycle_works() { "1000000000000000000".to_string() ); + // check that we can update as the incentive admin + let binding = STANDARD.encode(merkle_tree.root().unwrap()); + let merkle_root: &str = binding.as_str(); + + let _ = wasm + .execute( + contract.as_str(), + &ExecuteMsg::AdminMsg(AdminExecuteMsg::UpdateMerkleRoot { + new_root: merkle_root.to_string(), + }), + &[], + &incentive_admin, + ) + .unwrap(); + // Execute AdminMsg::UpdateAdmin let new_admin = app .init_account(&[Coin::new(1_000_000_000, "uosmo")]) @@ -146,8 +162,7 @@ fn merkle_complete_cycle_works() { // TODO: Assert admin changed and queriable // AdminMsg::UpdateMerkleRoot - let binding = STANDARD.encode(merkle_tree.root().unwrap()); - let merkle_root: &str = binding.as_str(); + // check that we can update as the new admin let _ = wasm .execute( contract.as_str(), @@ -226,7 +241,7 @@ fn merkle_complete_cycle_works() { #[test] #[ignore] fn merkle_complete_cycle_works_mainnet_data() { - let (app, contract, admin) = initialize::default_init(vec![ + let (app, contract, admin, incentive_admin) = initialize::default_init(vec![ Coin { denom: "factory/osmo1nz7qdp7eg30sr959wvrwn9j9370h4xt6ttm0h3/ussosmo".to_string(), amount: Uint128::new(300000000u128), @@ -297,7 +312,7 @@ fn merkle_complete_cycle_works_mainnet_data() { new_root: merkle_root.to_string(), }), &[], - &admin, + &incentive_admin, ) .unwrap(); From f6f596463f5af3d6f96a205878599cfebc928459 Mon Sep 17 00:00:00 2001 From: Laurens <32776056+LaurensKubat@users.noreply.github.com> Date: Wed, 22 May 2024 11:25:15 -0400 Subject: [PATCH 5/6] make incentives plural instead of singular --- smart-contracts/contracts/merkle-incentives/src/contract.rs | 4 ++-- smart-contracts/contracts/merkle-incentives/src/msg.rs | 2 +- .../contracts/merkle-incentives/tests/initialize.rs | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/smart-contracts/contracts/merkle-incentives/src/contract.rs b/smart-contracts/contracts/merkle-incentives/src/contract.rs index 4197001f4..ba2fab618 100644 --- a/smart-contracts/contracts/merkle-incentives/src/contract.rs +++ b/smart-contracts/contracts/merkle-incentives/src/contract.rs @@ -24,9 +24,9 @@ pub fn instantiate( ) -> Result { set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?; - INCENTIVES_ADMIN.save(deps.storage, &deps.api.addr_validate(&msg.incentive_admin)?)?; + INCENTIVES_ADMIN.save(deps.storage, &deps.api.addr_validate(&msg.incentives_admin)?)?; - Ok(Response::default().add_attribute("incentive_admin", msg.incentive_admin)) + Ok(Response::default().add_attribute("incentive_admin", msg.incentives_admin)) } #[cfg_attr(not(feature = "library"), entry_point)] diff --git a/smart-contracts/contracts/merkle-incentives/src/msg.rs b/smart-contracts/contracts/merkle-incentives/src/msg.rs index c2d8054ca..9798dc6c2 100644 --- a/smart-contracts/contracts/merkle-incentives/src/msg.rs +++ b/smart-contracts/contracts/merkle-incentives/src/msg.rs @@ -8,7 +8,7 @@ use crate::{ #[cw_serde] pub struct InstantiateMsg { - pub incentive_admin: String, + pub incentives_admin: String, } #[cw_serde] diff --git a/smart-contracts/contracts/merkle-incentives/tests/initialize.rs b/smart-contracts/contracts/merkle-incentives/tests/initialize.rs index 94b027ec4..ef8538047 100644 --- a/smart-contracts/contracts/merkle-incentives/tests/initialize.rs +++ b/smart-contracts/contracts/merkle-incentives/tests/initialize.rs @@ -43,7 +43,7 @@ pub fn init_test_contract( .instantiate( code_id, &InstantiateMsg { - incentive_admin: incentive_admin.address(), + incentives_admin: incentive_admin.address(), }, Some(admin.address().as_str()), Some("merkle-incentives"), From dda7cbe6f418c749a7d7f15584bd251cd217a076 Mon Sep 17 00:00:00 2001 From: Laurens <32776056+LaurensKubat@users.noreply.github.com> Date: Mon, 27 May 2024 18:52:59 -0400 Subject: [PATCH 6/6] fmt --- smart-contracts/contracts/merkle-incentives/src/contract.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/smart-contracts/contracts/merkle-incentives/src/contract.rs b/smart-contracts/contracts/merkle-incentives/src/contract.rs index ba2fab618..ec8ca414c 100644 --- a/smart-contracts/contracts/merkle-incentives/src/contract.rs +++ b/smart-contracts/contracts/merkle-incentives/src/contract.rs @@ -24,7 +24,10 @@ pub fn instantiate( ) -> Result { set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?; - INCENTIVES_ADMIN.save(deps.storage, &deps.api.addr_validate(&msg.incentives_admin)?)?; + INCENTIVES_ADMIN.save( + deps.storage, + &deps.api.addr_validate(&msg.incentives_admin)?, + )?; Ok(Response::default().add_attribute("incentive_admin", msg.incentives_admin)) }