diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..d260a25 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "rust-analyzer.checkOnSave.command": "clippy" +} diff --git a/Cargo.lock b/Cargo.lock index fe5f974..15358d7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -15,14 +15,16 @@ dependencies = [ [[package]] name = "alliance-nft-collection" -version = "1.0.1" +version = "1.1.0" dependencies = [ "alliance-nft-packages", "cosmwasm-schema", "cosmwasm-std", - "cw-storage-plus 1.1.0", + "cw-asset", + "cw-storage-plus 1.2.0", "cw-utils 1.0.2", "cw2 1.1.1", + "cw20", "cw721 0.18.0", "cw721-base 0.18.0", "schemars", @@ -33,12 +35,13 @@ dependencies = [ [[package]] name = "alliance-nft-minter" -version = "1.0.1" +version = "1.1.0" dependencies = [ "alliance-nft-packages", "cosmwasm-schema", "cosmwasm-std", - "cw-storage-plus 1.1.0", + "cw-asset", + "cw-storage-plus 1.2.0", "cw-utils 1.0.2", "cw2 1.1.1", "cw721 0.18.0", @@ -51,11 +54,12 @@ dependencies = [ [[package]] name = "alliance-nft-packages" -version = "1.0.1" +version = "1.1.0" dependencies = [ "cosmwasm-schema", "cosmwasm-std", - "cw-storage-plus 1.1.0", + "cw-asset", + "cw-storage-plus 1.2.0", "cw-utils 1.0.2", "cw2 1.1.1", "cw721 0.18.0", @@ -96,6 +100,12 @@ version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" +[[package]] +name = "bech32" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d86b93f97252c47b41663388e6d155714a9d0c398b99f1005cbc5f978b29f445" + [[package]] name = "block-buffer" version = "0.9.0" @@ -116,9 +126,9 @@ dependencies = [ [[package]] name = "bnum" -version = "0.8.0" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "128a44527fc0d6abf05f9eda748b9027536e12dff93f5acc8449f51583309350" +checksum = "56953345e39537a3e18bdaeba4cb0c58a78c1f61f361dc0fa7c5c7340ae87c5f" [[package]] name = "bumpalo" @@ -155,11 +165,12 @@ checksum = "28c122c3980598d243d63d9a704629a2d748d101f278052ff068be5a4423ab6f" [[package]] name = "cosmwasm-crypto" -version = "1.4.1" +version = "1.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a6fb22494cf7d23d0c348740e06e5c742070b2991fd41db77bba0bcfbae1a723" +checksum = "9934c79e58d9676edfd592557dee765d2a6ef54c09d5aa2edb06156b00148966" dependencies = [ "digest 0.10.7", + "ecdsa", "ed25519-zebra", "k256", "rand_core 0.6.4", @@ -168,18 +179,18 @@ dependencies = [ [[package]] name = "cosmwasm-derive" -version = "1.4.1" +version = "1.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e199424486ea97d6b211db6387fd72e26b4a439d40cc23140b2d8305728055b" +checksum = "bc5e72e330bd3bdab11c52b5ecbdeb6a8697a004c57964caeb5d876f0b088b3c" dependencies = [ "syn 1.0.109", ] [[package]] name = "cosmwasm-schema" -version = "1.4.1" +version = "1.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fef683a9c1c4eabd6d31515719d0d2cc66952c4c87f7eb192bfc90384517dc34" +checksum = "ac3e3a2136e2a60e8b6582f5dffca5d1a683ed77bf38537d330bc1dfccd69010" dependencies = [ "cosmwasm-schema-derive", "schemars", @@ -190,9 +201,9 @@ dependencies = [ [[package]] name = "cosmwasm-schema-derive" -version = "1.4.1" +version = "1.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9567025acbb4c0c008178393eb53b3ac3c2e492c25949d3bf415b9cbe80772d8" +checksum = "f5d803bea6bd9ed61bd1ee0b4a2eb09ee20dbb539cc6e0b8795614d20952ebb1" dependencies = [ "proc-macro2", "quote", @@ -201,11 +212,12 @@ dependencies = [ [[package]] name = "cosmwasm-std" -version = "1.4.1" +version = "1.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d89d680fb60439b7c5947b15f9c84b961b88d1f8a3b20c4bd178a3f87db8bae" +checksum = "ef8666e572a3a2519010dde88c04d16e9339ae751b56b2bb35081fe3f7d6be74" dependencies = [ "base64", + "bech32", "bnum", "cosmwasm-crypto", "cosmwasm-derive", @@ -216,6 +228,7 @@ dependencies = [ "serde", "serde-json-wasm", "sha2 0.10.8", + "static_assertions", "thiserror", ] @@ -272,6 +285,20 @@ dependencies = [ "cosmwasm-std", ] +[[package]] +name = "cw-asset" +version = "3.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c999a12f8cd8736f6f86e9a4ede5905530cb23cfdef946b9da1c506ad1b70799" +dependencies = [ + "cosmwasm-schema", + "cosmwasm-std", + "cw-address-like", + "cw-storage-plus 1.2.0", + "cw20", + "thiserror", +] + [[package]] name = "cw-ownable" version = "0.5.1" @@ -282,7 +309,7 @@ dependencies = [ "cosmwasm-std", "cw-address-like", "cw-ownable-derive", - "cw-storage-plus 1.1.0", + "cw-storage-plus 1.2.0", "cw-utils 1.0.2", "thiserror", ] @@ -311,9 +338,9 @@ dependencies = [ [[package]] name = "cw-storage-plus" -version = "1.1.0" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f0e92a069d62067f3472c62e30adedb4cab1754725c0f2a682b3128d2bf3c79" +checksum = "d5ff29294ee99373e2cd5fd21786a3c0ced99a52fec2ca347d565489c61b723c" dependencies = [ "cosmwasm-std", "schemars", @@ -371,12 +398,25 @@ checksum = "9431d14f64f49e41c6ef5561ed11a5391c417d0cb16455dea8cdcb9037a8d197" dependencies = [ "cosmwasm-schema", "cosmwasm-std", - "cw-storage-plus 1.1.0", + "cw-storage-plus 1.2.0", "schemars", "serde", "thiserror", ] +[[package]] +name = "cw20" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "526e39bb20534e25a1cd0386727f0038f4da294e5e535729ba3ef54055246abd" +dependencies = [ + "cosmwasm-schema", + "cosmwasm-std", + "cw-utils 1.0.2", + "schemars", + "serde", +] + [[package]] name = "cw721" version = "0.16.0" @@ -429,7 +469,7 @@ dependencies = [ "cosmwasm-schema", "cosmwasm-std", "cw-ownable", - "cw-storage-plus 1.1.0", + "cw-storage-plus 1.2.0", "cw-utils 1.0.2", "cw2 1.1.1", "cw721 0.18.0", @@ -740,9 +780,9 @@ checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" [[package]] name = "proc-macro2" -version = "1.0.69" +version = "1.0.79" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "134c189feb4956b20f6f547d2cf727d4c0fe06722b20a0eec87ed445a97f92da" +checksum = "e835ff2298f5721608eb1a980ecaee1aef2c132bf95ecc026a11b7bf3c01c02e" dependencies = [ "unicode-ident", ] @@ -781,9 +821,9 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.33" +version = "1.0.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae" +checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef" dependencies = [ "proc-macro2", ] @@ -874,9 +914,9 @@ dependencies = [ [[package]] name = "serde-json-wasm" -version = "0.5.1" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16a62a1fad1e1828b24acac8f2b468971dade7b8c3c2e672bcadefefb1f8c137" +checksum = "9e9213a07d53faa0b8dd81e767a54a8188a242fdb9be99ab75ec576a774bfdd7" dependencies = [ "serde", ] @@ -898,7 +938,7 @@ checksum = "67c5609f394e5c2bd7fc51efda478004ea80ef42fee983d5c67a65e34f32c0e3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.38", + "syn 2.0.53", ] [[package]] @@ -967,6 +1007,12 @@ dependencies = [ "der", ] +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + [[package]] name = "subtle" version = "2.5.0" @@ -995,9 +1041,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.38" +version = "2.0.53" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e96b79aaa137db8f61e26363a0c9b47d8b4ec75da28b7d1d614c2303e232408b" +checksum = "7383cd0e49fff4b6b90ca5670bfd3e9d6a733b3f90c686605aa7eec8c4996032" dependencies = [ "proc-macro2", "quote", @@ -1036,22 +1082,22 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.50" +version = "1.0.58" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9a7210f5c9a7156bb50aa36aed4c95afb51df0df00713949448cf9e97d382d2" +checksum = "03468839009160513471e86a034bb2c5c0e4baae3b43f79ffc55c4a5427b3297" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.50" +version = "1.0.58" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "266b2e40bc00e5a6c09c3584011e08b06f123c00362c92b975ba9843aaaa14b8" +checksum = "c61f3ba182994efc43764a46c018c347bc492c79f024e705f46567b418f6d4f7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.38", + "syn 2.0.53", ] [[package]] @@ -1126,7 +1172,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn 2.0.38", + "syn 2.0.53", "wasm-bindgen-shared", ] @@ -1148,7 +1194,7 @@ checksum = "54681b18a46765f095758388f2d0cf16eb8d4169b639ab575a8f5693af210c7b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.38", + "syn 2.0.53", "wasm-bindgen-backend", "wasm-bindgen-shared", ] diff --git a/Cargo.toml b/Cargo.toml index d03bb78..f31f97c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,7 +8,7 @@ members = [ [workspace.package] description = "Alliance NFT Collection dropped to the Game of Alliance players" authors = ["Terra Money "] -version = "1.0.1" +version = "1.1.0" edition = "2021" license = "Apache-2.0" repository = "https://github.com/terra-money/alliance-nft-collection" @@ -20,11 +20,13 @@ rust-version = "1.73" cosmwasm-std = { version = "1.4.1", features = ["stargate"] } cosmwasm-schema = "1.4.1" cw2 = "1.1.0" +cw20 = "1.1.0" cw721 = { version = "0.18.0" } cw721-base = { version = "0.18.0", features = ["library"] } schemars = "0.8.15" cw-storage-plus = "1.1.0" cw-utils = "1.0.2" +cw-asset = "3.1.1" serde = { version = "1.0.190", default-features = false, features = ["derive"] } thiserror = "1.0.50" terra-proto-rs = { version = "4.0.2", default-features = false } diff --git a/contracts/alliance-nft-collection/.cargo/config b/contracts/alliance-nft-collection/.cargo/config index 7d1a066..9bd2a93 100644 --- a/contracts/alliance-nft-collection/.cargo/config +++ b/contracts/alliance-nft-collection/.cargo/config @@ -2,4 +2,4 @@ wasm = "build --release --target wasm32-unknown-unknown" wasm-debug = "build --target wasm32-unknown-unknown" unit-test = "test --lib" -schema = "run --example schema" +schema = "run --example schema_collection" diff --git a/contracts/alliance-nft-collection/Cargo.toml b/contracts/alliance-nft-collection/Cargo.toml index 1df5cda..70e12ab 100644 --- a/contracts/alliance-nft-collection/Cargo.toml +++ b/contracts/alliance-nft-collection/Cargo.toml @@ -25,7 +25,11 @@ cw721-base = { workspace = true } schemars = { workspace = true } cw-storage-plus = { workspace = true } cw-utils = { workspace = true } +cw-asset = { workspace = true } serde = { workspace = true } thiserror = { workspace = true } terra-proto-rs = { workspace = true } -alliance-nft-packages = { workspace = true } \ No newline at end of file +alliance-nft-packages = { workspace = true } + +[dev-dependencies] +cw20 = { workspace = true } \ No newline at end of file diff --git a/contracts/alliance-nft-collection/README.md b/contracts/alliance-nft-collection/README.md index 80cb8b0..b48fd68 100644 --- a/contracts/alliance-nft-collection/README.md +++ b/contracts/alliance-nft-collection/README.md @@ -1,3 +1,36 @@ # Alliance NFT Reward NFT collection to the participants from [Game Of Alliance](https://docs.alliance.terra.money/game-of-alliance/overview/) that helped to test the [Alliance module](https://github.com/terra-money/alliance). Each NFT will receive staking rewards from Terra Blockchain and will also enable voting in the Alliance DAO. + +## Update 1.1.0 + +Update 1.1.0 introduces the staking of rewards in the ERIS LUNA Amplifier. This allows the DAO to participate in compounding staking rewards. + +### New features + +- **StakeRewardsCallback**: This execution will check if LUNA are in the contract and then stake the available amount in the LST. It will call the `UpdateRewardsCallback` with the `previous_lst_balance` to track how many ampLUNA have been added to the rewards. + +- **UpdateRewardsCallback**: Instead of storing the LUNA balance in a temporary state, the previous ampLUNA balance is being sent to the `UpdateRewardsCallback`. This way the contract is more gas-efficient and simplified. + +- **UpdateConfig**: The owner is allowed to update the config with the following properties + - `dao_treasury_share`: Specifies how much of the alliance staking rewards should be distributed to the dao treasury. + - `set_whitelisted_reward_assets`: Sets all whitelisted reward assets that should be checked for distribution when breaking an NFT. + - `add_whitelisted_reward_assets`: Adds whitelisted rewards assets to the cfg stored. + +- **BreakNft**: On breaking an NFT, a user receives their share in ampLUNA. The user also receives their share of all whitelisted reward assets. The user's reward share is calculated by dividing their rewarded ampLUNA by the total amount of ampLUNA in the contract. + +### Additional Changes + +- Removed non-used state constants (UNBONDINGS, REDELEGATIONS) +- Removed TEMP_BALANCE, as the previous balance is being sent directly in the callback message. +- REWARD_BALANCE holds the amount of LST per unbroken NFT instead of the amount of LUNA. +- NFT minter contract is now forwarding migrations to the nft collection if specified. + +### Migration + +The migration requires the following fields under the version110_data. + +- **dao_treasury_address**: Specifies the DAO treasury for the reward sharing. +- **dao_treasury_share**: Specifies the amount of rewards to be shared with the DAO treasury (0-20%). +- **lst_hub**: Specifies the ERIS liquid staking hub contract address. +- **lst_asset_info**: Specifies the AssetInfo of ampLUNA. diff --git a/contracts/alliance-nft-collection/examples/schema.rs b/contracts/alliance-nft-collection/examples/schema_collection.rs similarity index 100% rename from contracts/alliance-nft-collection/examples/schema.rs rename to contracts/alliance-nft-collection/examples/schema_collection.rs diff --git a/contracts/alliance-nft-collection/src/contract/execute.rs b/contracts/alliance-nft-collection/src/contract/execute.rs index 4ff32f5..71400bb 100644 --- a/contracts/alliance-nft-collection/src/contract/execute.rs +++ b/contracts/alliance-nft-collection/src/contract/execute.rs @@ -1,10 +1,15 @@ +use alliance_nft_packages::eris::{ + dedupe_assetinfos, validate_dao_treasury_share, validate_whitelisted_assets, AssetInfoExt, +}; +use alliance_nft_packages::execute::{UpdateConfigMsg, UpdateRewardsCallbackMsg}; use alliance_nft_packages::state::ALLOWED_DENOM; use cosmwasm_std::{ - coins, entry_point, to_binary, Addr, BankMsg, Binary, Coin as CwCoin, CosmosMsg, Order, SubMsg, - Uint128, WasmMsg, Storage, QuerierWrapper, + attr, entry_point, to_json_binary, Addr, Binary, CosmosMsg, Decimal, Order, SubMsg, Uint128, + WasmMsg, }; use cosmwasm_std::{DepsMut, Env, MessageInfo, Response}; use cw721::Cw721Query; +use cw_asset::AssetInfoBase; use terra_proto_rs::alliance::alliance::{MsgClaimDelegationRewards, MsgRedelegate, MsgUndelegate}; use terra_proto_rs::{ alliance::alliance::MsgDelegate, cosmos::base::v1beta1::Coin, traits::Message, @@ -12,20 +17,19 @@ use terra_proto_rs::{ use crate::state::{ reduce_val_stake, upsert_val, BROKEN_NFTS, CONFIG, NFT_BALANCE_CLAIMED, NUM_ACTIVE_NFTS, - REWARD_BALANCE, TEMP_BALANCE, VALS, + REWARD_BALANCE, VALS, }; use alliance_nft_packages::{ errors::ContractError, execute::{ - AllianceDelegateMsg, AllianceRedelegateMsg, AllianceUndelegateMsg, ExecuteCollectionMsg, MintMsg, + AllianceDelegateMsg, AllianceRedelegateMsg, AllianceUndelegateMsg, ExecuteCollectionMsg, + MintMsg, }, AllianceNftCollection, }; -use super::query::try_query_contract_balance; use super::reply::CLAIM_REWARD_ERROR_REPLY_ID; - #[cfg_attr(not(feature = "library"), entry_point)] pub fn execute( deps: DepsMut, @@ -36,31 +40,34 @@ pub fn execute( let parent: AllianceNftCollection = AllianceNftCollection::default(); match msg { ExecuteCollectionMsg::AllianceDelegate(msg) => try_alliance_delegate(deps, env, info, msg), - ExecuteCollectionMsg::AllianceUndelegate(msg) => try_alliance_undelegate(deps, env, info, msg), - ExecuteCollectionMsg::AllianceRedelegate(msg) => try_alliance_redelegate(deps, env, info, msg), + ExecuteCollectionMsg::AllianceUndelegate(msg) => { + try_alliance_undelegate(deps, env, info, msg) + } + ExecuteCollectionMsg::AllianceRedelegate(msg) => { + try_alliance_redelegate(deps, env, info, msg) + } + ExecuteCollectionMsg::AllianceClaimRewards {} => try_alliance_claim_rewards(deps, env), - ExecuteCollectionMsg::AllianceClaimRewards {} => try_alliance_claim_rewards(deps, env, info), - ExecuteCollectionMsg::UpdateRewardsCallback {} => update_reward_callback(deps, env, info), + ExecuteCollectionMsg::StakeRewardsCallback {} => try_stake_reward_callback(deps, env, info), + ExecuteCollectionMsg::UpdateRewardsCallback(msg) => { + try_update_reward_callback(deps, env, info, msg) + } ExecuteCollectionMsg::BreakNft(token_id) => try_breaknft(deps, env, info, parent, token_id), ExecuteCollectionMsg::Mint(mint_msg) => try_mint(deps, info, parent, mint_msg), ExecuteCollectionMsg::ChangeOwner(new_owner) => try_change_owner(deps, info, new_owner), + ExecuteCollectionMsg::UpdateConfig(msg) => try_update_config(deps, info, msg), _ => Ok(parent.execute(deps, env, info, msg.into())?), } } -fn try_alliance_claim_rewards( - deps: DepsMut, - env: Env, - info: MessageInfo, -) -> Result { +fn try_alliance_claim_rewards(deps: DepsMut, env: Env) -> Result { let cfg = CONFIG.load(deps.storage)?; let num_of_active = NUM_ACTIVE_NFTS.load(deps.storage)?; if num_of_active == 0 { return Err(ContractError::NoActiveNfts {}); } - store_temp_contract_funds(&info.funds, deps.querier, deps.storage, &env.contract.address)?; let validators = VALS .range(deps.storage, None, None, Order::Ascending) @@ -82,11 +89,8 @@ fn try_alliance_claim_rewards( SubMsg::reply_on_error(msg, CLAIM_REWARD_ERROR_REPLY_ID) }) .collect(); - let msg = CosmosMsg::Wasm(WasmMsg::Execute { - contract_addr: env.contract.address.to_string(), - msg: to_binary(&ExecuteCollectionMsg::UpdateRewardsCallback {}).unwrap(), - funds: vec![], - }); + + let msg = get_stake_reward_callback_msg(env); Ok(Response::new() .add_attributes(vec![("action", "update_rewards")]) @@ -94,29 +98,106 @@ fn try_alliance_claim_rewards( .add_message(msg)) } -fn update_reward_callback( +fn get_stake_reward_callback_msg(env: Env) -> CosmosMsg { + CosmosMsg::Wasm(WasmMsg::Execute { + contract_addr: env.contract.address.to_string(), + msg: to_json_binary(&ExecuteCollectionMsg::StakeRewardsCallback {}).unwrap(), + funds: vec![], + }) +} + +fn try_stake_reward_callback( deps: DepsMut, env: Env, info: MessageInfo, ) -> Result { authorize_execution(env.contract.address.clone(), info.sender)?; + let config = CONFIG.load(deps.storage)?; + + // check if there are tokens to stake + let tokens_to_stake = AssetInfoBase::native(ALLOWED_DENOM) + .query_balance(&deps.querier, env.contract.address.clone())?; + + if tokens_to_stake.is_zero() { + return Ok(Response::new().add_attributes(vec![ + ("action", "stake_reward_callback"), + ("tokens_to_stake", "0"), + ])); + } + + // create stake / bond message + let stake_msg = config + .lst_hub_address + .bond_msg(ALLOWED_DENOM, tokens_to_stake.u128(), None)?; + + // prepare update rewards callback, by querying the current total lsts in the contract. + let previous_lst_balance = config + .lst_asset_info + .query_balance(&deps.querier, env.contract.address.clone())?; + + let update_rewards_msg = CosmosMsg::Wasm(WasmMsg::Execute { + contract_addr: env.contract.address.to_string(), + msg: to_json_binary(&ExecuteCollectionMsg::UpdateRewardsCallback( + UpdateRewardsCallbackMsg { + previous_lst_balance, + }, + ))?, + funds: vec![], + }); + + Ok(Response::new() + .add_attributes(vec![ + ("action", "stake_reward_callback"), + ("tokens_to_stake", &tokens_to_stake.to_string()), + ]) + .add_message(stake_msg) + .add_message(update_rewards_msg)) +} + +fn try_update_reward_callback( + deps: DepsMut, + env: Env, + info: MessageInfo, + msg: UpdateRewardsCallbackMsg, +) -> Result { + authorize_execution(env.contract.address.clone(), info.sender)?; + let config = CONFIG.load(deps.storage)?; + + let current_balance = config + .lst_asset_info + .query_balance(&deps.querier, env.contract.address.clone())?; + let mut rewards_collected = current_balance - msg.previous_lst_balance; + + // if there is an lst_treasury_share, then the specified amount will be sent to the dao treasury. + let mut msgs = vec![]; + let mut attributes = vec![]; + if !config.dao_treasury_share.is_zero() { + let treasury_amount = config.dao_treasury_share * rewards_collected; + if !treasury_amount.is_zero() { + rewards_collected = rewards_collected.checked_sub(treasury_amount)?; + msgs.push( + config + .lst_asset_info + .with_balance(treasury_amount) + .transfer_msg(config.dao_treasury_address)?, + ); + attributes.push(attr("treasury_amount", treasury_amount)); + } + } - let current_balance = deps - .querier - .query_balance(env.contract.address, ALLOWED_DENOM)? - .amount; - let previous_balance = TEMP_BALANCE.load(deps.storage)?; - let rewards_collected = current_balance - previous_balance; let num_of_active_nfts = NUM_ACTIVE_NFTS.load(deps.storage)?; let average_reward = rewards_collected / Uint128::from(num_of_active_nfts); REWARD_BALANCE.update(deps.storage, |balance| -> Result<_, ContractError> { Ok(balance + average_reward) })?; - TEMP_BALANCE.remove(deps.storage); Ok(Response::new() - .add_attributes(vec![("action", "update_rewards_callback")] - )) + .add_attributes(vec![ + attr("action", "update_rewards_callback"), + attr("rewards_collected", rewards_collected), + ]) + .add_attributes(attributes) + .add_messages(msgs)) } fn try_alliance_delegate( @@ -127,8 +208,7 @@ fn try_alliance_delegate( ) -> Result { let cfg = CONFIG.load(deps.as_ref().storage)?; authorize_execution(cfg.owner.clone(), info.sender)?; - store_temp_contract_funds(&info.funds, deps.querier, deps.storage, &env.contract.address)?; - + let mut cosmos_msg: Vec = Vec::new(); for del in msg.delegations.iter() { @@ -151,13 +231,9 @@ fn try_alliance_delegate( cosmos_msg.push(msg); } - let msg = CosmosMsg::Wasm(WasmMsg::Execute { - contract_addr: env.contract.address.to_string(), - msg: to_binary(&ExecuteCollectionMsg::UpdateRewardsCallback {}).unwrap(), - funds: vec![], - }); + let msg = get_stake_reward_callback_msg(env); cosmos_msg.push(msg); - + Ok(Response::default() .add_attribute("method", "try_alliance_delegate") .add_messages(cosmos_msg)) @@ -171,7 +247,6 @@ fn try_alliance_undelegate( ) -> Result { let cfg = CONFIG.load(deps.storage)?; authorize_execution(cfg.owner.clone(), info.sender)?; - store_temp_contract_funds(&info.funds, deps.querier, deps.storage, &env.contract.address)?; if msg.undelegations.is_empty() { return Err(ContractError::EmptyDelegation {}); @@ -193,11 +268,7 @@ fn try_alliance_undelegate( cosmos_msg.push(msg); reduce_val_stake(deps.storage, delegation.validator, delegation.amount)?; } - let msg = CosmosMsg::Wasm(WasmMsg::Execute { - contract_addr: env.contract.address.to_string(), - msg: to_binary(&ExecuteCollectionMsg::UpdateRewardsCallback {}).unwrap(), - funds: vec![], - }); + let msg = get_stake_reward_callback_msg(env); cosmos_msg.push(msg); Ok(Response::new() .add_attributes(vec![("action", "try_alliance_undelegate")]) @@ -212,7 +283,6 @@ fn try_alliance_redelegate( ) -> Result { let cfg = CONFIG.load(deps.storage)?; authorize_execution(cfg.owner.clone(), info.sender)?; - store_temp_contract_funds(&info.funds, deps.querier, deps.storage, &env.contract.address)?; if msg.redelegations.is_empty() { return Err(ContractError::EmptyDelegation {}); @@ -238,11 +308,7 @@ fn try_alliance_redelegate( upsert_val(deps.storage, dst_validator, redelegation.amount)?; reduce_val_stake(deps.storage, src_validator, redelegation.amount)?; } - let msg = CosmosMsg::Wasm(WasmMsg::Execute { - contract_addr: env.contract.address.to_string(), - msg: to_binary(&ExecuteCollectionMsg::UpdateRewardsCallback {}).unwrap(), - funds: vec![], - }); + let msg = get_stake_reward_callback_msg(env); cosmos_msg.push(msg); Ok(Response::new() .add_attributes(vec![("action", "try_alliance_redelegate")]) @@ -256,7 +322,8 @@ fn try_breaknft( parent: AllianceNftCollection, token_id: String, ) -> Result { - let owner_res = parent.owner_of(deps.as_ref(), env, token_id.clone(), false)?; + let cfg = CONFIG.load(deps.storage)?; + let owner_res = parent.owner_of(deps.as_ref(), env.clone(), token_id.clone(), false)?; let owner = deps.api.addr_validate(&owner_res.owner)?; authorize_execution(owner.clone(), info.sender)?; @@ -290,16 +357,43 @@ fn try_breaknft( ("rewards", rewards_claimable.to_string().as_str()), ])) } else { - let send_msg = CosmosMsg::Bank(BankMsg::Send { - amount: coins(rewards_claimable.u128(), ALLOWED_DENOM), - to_address: owner.to_string(), - }); + // send user share to owner. + let send_msg = cfg + .lst_asset_info + .clone() + .with_balance(rewards_claimable) + .transfer_msg(owner.to_string())?; + + // prepare the additional whitelisted reward assets based on the user share + // user share = rewards_claimable / rewards_total + let mut transfer_reward_asset_msgs = vec![]; + let rewards_total = cfg + .lst_asset_info + .query_balance(&deps.querier, env.contract.address.to_string())?; + let user_share = Decimal::from_ratio(rewards_claimable, rewards_total); + + for whitelisted_reward_asset in cfg.whitelisted_reward_assets { + let balance = whitelisted_reward_asset + .query_balance(&deps.querier, env.contract.address.to_string())?; + // always floored + let user_balance = user_share * balance; + if !user_balance.is_zero() { + transfer_reward_asset_msgs.push( + whitelisted_reward_asset + .with_balance(user_balance) + .transfer_msg(owner.to_string())?, + ); + } + } + Ok(Response::default() .add_message(send_msg) + .add_messages(transfer_reward_asset_msgs) .add_attributes(vec![ ("action", "break_nft"), ("token_id", token_id.as_str()), ("rewards", rewards_claimable.to_string().as_str()), + ("user_share", user_share.to_string().as_str()), ])) } } @@ -345,29 +439,46 @@ fn try_change_owner( ])) } +fn try_update_config( + deps: DepsMut, + info: MessageInfo, + msg: UpdateConfigMsg, +) -> Result { + let mut cfg = CONFIG.load(deps.storage)?; + authorize_execution(cfg.owner.clone(), info.sender)?; + + if let Some(dao_treasury_address) = msg.dao_treasury_address { + cfg.dao_treasury_address = deps.api.addr_validate(&dao_treasury_address)?; + } + + if let Some(dao_treasury_share) = msg.dao_treasury_share { + cfg.dao_treasury_share = validate_dao_treasury_share(dao_treasury_share)?; + } + + if let Some(set_whitelisted_reward_assets) = msg.set_whitelisted_reward_assets { + let mut set_assets = + validate_whitelisted_assets(&deps, &cfg.lst_asset_info, set_whitelisted_reward_assets)?; + dedupe_assetinfos(&mut set_assets); + cfg.whitelisted_reward_assets = set_assets; + } + + if let Some(add_whitelisted_reward_assets) = msg.add_whitelisted_reward_assets { + let mut add_assets = + validate_whitelisted_assets(&deps, &cfg.lst_asset_info, add_whitelisted_reward_assets)?; + let mut existing = cfg.whitelisted_reward_assets; + existing.append(&mut add_assets); + dedupe_assetinfos(&mut existing); + cfg.whitelisted_reward_assets = existing; + } + + CONFIG.save(deps.storage, &cfg)?; + + Ok(Response::default().add_attributes(vec![("action", "try_update_config")])) +} + fn authorize_execution(owner: Addr, sender: Addr) -> Result { if sender != owner { return Err(ContractError::Unauthorized(sender, owner)); } Ok(Response::default()) } - -// Given the sent fund and the current contract balance, -// store the difference in TEMP_BALANCE so we can keep, -// track of the rewards collected in the current tx. -fn store_temp_contract_funds( - funds: &Vec, - querier:QuerierWrapper, - storage: &mut dyn Storage, - contract_addr: &Addr -) -> Result<(), ContractError> { - let reward_sent_in_tx: Option<&CwCoin> = funds.iter().find(|c| c.denom == ALLOWED_DENOM); - let sent_balance = if let Some(coin) = reward_sent_in_tx { - coin.amount - } else { - Uint128::zero() - }; - let contract_balance = try_query_contract_balance(querier, contract_addr)?; - TEMP_BALANCE.save(storage, &(contract_balance - sent_balance))?; - Ok(()) -} \ No newline at end of file diff --git a/contracts/alliance-nft-collection/src/contract/instantiate.rs b/contracts/alliance-nft-collection/src/contract/instantiate.rs index 0176a07..aaa5a59 100644 --- a/contracts/alliance-nft-collection/src/contract/instantiate.rs +++ b/contracts/alliance-nft-collection/src/contract/instantiate.rs @@ -1,4 +1,5 @@ use alliance_nft_packages::{ + eris::{validate_dao_treasury_share, Hub}, errors::ContractError, instantiate::InstantiateCollectionMsg, state::Config, @@ -19,7 +20,7 @@ use terra_proto_rs::{ traits::Message, }; -use crate::state::{CONFIG, REWARD_BALANCE, NUM_ACTIVE_NFTS}; +use crate::state::{CONFIG, NUM_ACTIVE_NFTS, REWARD_BALANCE}; use super::reply::INSTANTIATE_REPLY_ID; @@ -43,6 +44,12 @@ pub fn instantiate( &Config { owner: msg.owner.clone(), asset_denom: format!("factory/{}/{}", env.contract.address, SUBDENOM), + + dao_treasury_address: deps.api.addr_validate(&msg.dao_treasury_address)?, + lst_hub_address: Hub(deps.api.addr_validate(&msg.lst_hub_address)?), + dao_treasury_share: validate_dao_treasury_share(msg.dao_treasury_share)?, + lst_asset_info: msg.lst_asset_info.check(deps.api, None)?, + whitelisted_reward_assets: vec![], }, )?; diff --git a/contracts/alliance-nft-collection/src/contract/migrate.rs b/contracts/alliance-nft-collection/src/contract/migrate.rs index 347882e..30a9e87 100644 --- a/contracts/alliance-nft-collection/src/contract/migrate.rs +++ b/contracts/alliance-nft-collection/src/contract/migrate.rs @@ -1,20 +1,88 @@ +use alliance_nft_packages::eris::{validate_dao_treasury_share, Hub}; +use alliance_nft_packages::migrate::Version110MigrateData; +use alliance_nft_packages::state::{Config, ConfigV100, ALLOWED_DENOM}; use cosmwasm_std::entry_point; use cosmwasm_std::{DepsMut, Env, Response}; use cw2::{get_contract_version, set_contract_version}; use alliance_nft_packages::{errors::ContractError, migrate::MigrateMsg}; +use cw_asset::AssetInfo; +use cw_storage_plus::Item; + +use crate::state::{CONFIG, REWARD_BALANCE}; #[cfg_attr(not(feature = "library"), entry_point)] -pub fn migrate(deps: DepsMut, _env: Env, msg: MigrateMsg) -> Result { - let MigrateMsg { version } = msg; - try_migrate(deps, version) +pub fn migrate(deps: DepsMut, env: Env, msg: MigrateMsg) -> Result { + try_migrate(deps, env, msg) } -fn try_migrate(deps: DepsMut, version: String) -> Result { +fn try_migrate(deps: DepsMut, env: Env, msg: MigrateMsg) -> Result { + let version = msg.version; let contract_version = get_contract_version(deps.storage)?; - set_contract_version(deps.storage, contract_version.contract, version)?; + set_contract_version( + deps.storage, + contract_version.contract.clone(), + version.clone(), + )?; + + if version == "1.1.0" { + return match msg.version110_data { + Some(data) => return migrate_to_1_1_0(deps, env, data, contract_version.version), + None => Err(ContractError::MissingMigrationData(version)), + }; + } Ok(Response::new() .add_attribute("method", "try_migrate") .add_attribute("version", contract_version.version)) } + +fn migrate_to_1_1_0( + deps: DepsMut, + env: Env, + data: Version110MigrateData, + version: String, +) -> Result { + // apply config from migration data. Only share, whitelisted_reward_assets can be changed via an updateconfig message. + let config_old: ConfigV100 = Item::new("cfg").load(deps.storage)?; + let config = Config { + owner: config_old.owner, + asset_denom: config_old.asset_denom, + dao_treasury_address: deps.api.addr_validate(&data.dao_treasury_address)?, + dao_treasury_share: validate_dao_treasury_share(data.dao_treasury_share)?, + lst_hub_address: Hub(deps.api.addr_validate(&data.lst_hub)?), + lst_asset_info: data.lst_asset_info.check(deps.api, None)?, + whitelisted_reward_assets: vec![], + }; + CONFIG.save(deps.storage, &config)?; + + // for simplification, we just use the exchange rate to estimate the resulting received ampLUNA + // if there is a rounding issue, we will top up the missing ampLUNA to the contract, which is way less than 1 ampLUNA + // this allows us to keep the contract simplified and not work with callback messages to check how much ampLUNA was really received. + // total_ustake = 200k, total_uluna = 300k, rewards_current = 10k -> rewards_in_lst = 6.66k + let lst_hub_state = config.lst_hub_address.query_state(&deps.querier)?; + let rewards_current = REWARD_BALANCE.load(deps.storage)?; + let rewards_in_lst = + rewards_current.multiply_ratio(lst_hub_state.total_ustake, lst_hub_state.total_uluna); + REWARD_BALANCE.save(deps.storage, &rewards_in_lst)?; + + // create bond message + let balance_native = + AssetInfo::native(ALLOWED_DENOM).query_balance(&deps.querier, env.contract.address)?; + let bond_msg = config + .lst_hub_address + .bond_msg(ALLOWED_DENOM, balance_native.u128(), None)?; + + // we are not updating NFT_BALANCE_CLAIMED (nb), as all NFT_BALANCE_CLAIMED are at 0, further once broken it does not matter anymore. + // and there arent any NFTs that are not broken and having a claimed balance. + // This can only happen if a mint happened after the first rewards were distributed, which is not the case for Alliance DAO + // and should not be the case between now and the application of the migration. + + Ok(Response::new() + .add_attribute("method", "migrate_to_1_1_0") + .add_attribute("version", version) + .add_attribute("balance_native", balance_native) + .add_attribute("rewards_current", rewards_current) + .add_attribute("rewards_in_lst", rewards_in_lst) + .add_message(bond_msg)) +} diff --git a/contracts/alliance-nft-collection/src/contract/query.rs b/contracts/alliance-nft-collection/src/contract/query.rs index 19547dd..fb5e7bd 100644 --- a/contracts/alliance-nft-collection/src/contract/query.rs +++ b/contracts/alliance-nft-collection/src/contract/query.rs @@ -1,40 +1,76 @@ -use alliance_nft_packages::errors::ContractError; -use cosmwasm_std::{entry_point, to_binary, Uint128, Addr, QuerierWrapper}; +use alliance_nft_packages::eris::AssetInfoExt; +use alliance_nft_packages::query::RewardsResponse; +use alliance_nft_packages::state::{Config, Trait}; +use alliance_nft_packages::{query::QueryCollectionMsg, AllianceNftCollection, Extension}; +use cosmwasm_std::{entry_point, to_json_binary, Decimal, StdError, Uint128}; use cosmwasm_std::{Binary, Deps, Env, StdResult}; use cw721::{AllNftInfoResponse, Approval, NftInfoResponse, OwnerOfResponse}; use cw721_base::state::{Approval as BaseApproval, TokenInfo}; -use alliance_nft_packages::state::{Trait, Config, ALLOWED_DENOM}; -use alliance_nft_packages::{query::QueryCollectionMsg, AllianceNftCollection, Extension}; - -use crate::state::{CONFIG, BROKEN_NFTS, REWARD_BALANCE, NFT_BALANCE_CLAIMED}; +use crate::state::{BROKEN_NFTS, CONFIG, NFT_BALANCE_CLAIMED, REWARD_BALANCE}; #[cfg_attr(not(feature = "library"), entry_point)] pub fn query(deps: Deps, env: Env, msg: QueryCollectionMsg) -> StdResult { let parent = AllianceNftCollection::default(); match msg { - QueryCollectionMsg::Config {} => to_binary(&query_config(deps)?), - QueryCollectionMsg::NftInfo { token_id } => to_binary(&query_nft_info(deps, parent, token_id)?), + QueryCollectionMsg::Config {} => to_json_binary(&query_config(deps)?), + QueryCollectionMsg::NftInfo { token_id } => { + to_json_binary(&query_nft_info(deps, parent, token_id)?) + } QueryCollectionMsg::AllNftInfo { token_id, include_expired, - } => to_binary(&query_all_nft_info( + } => to_json_binary(&query_all_nft_info( deps, env, parent, token_id, include_expired, )?), + + QueryCollectionMsg::Rewards { token_id } => { + to_json_binary(&query_rewards(deps, env, token_id)?) + } + _ => parent.query(deps, env, msg.into()), } } -fn query_config(deps : Deps) -> StdResult{ +fn query_config(deps: Deps) -> StdResult { let res = CONFIG.load(deps.storage)?; Ok(res) } +fn query_rewards(deps: Deps, env: Env, token_id: String) -> StdResult { + let cfg = CONFIG.load(deps.storage)?; + + let rewards_claimed = NFT_BALANCE_CLAIMED.load(deps.storage, token_id.to_string())?; + let average_rewards = REWARD_BALANCE.load(deps.storage)?; + let rewards_claimable = average_rewards - rewards_claimed; + + let mut rewards = vec![cfg.lst_asset_info.clone().with_balance(rewards_claimable)]; + + let rewards_total = cfg + .lst_asset_info + .query_balance(&deps.querier, env.contract.address.to_string()) + .map_err(|e| StdError::generic_err(format!("failed lst query: {0}", e)))?; + let user_share = Decimal::from_ratio(rewards_claimable, rewards_total); + + for whitelisted_reward_asset in cfg.whitelisted_reward_assets { + let balance = whitelisted_reward_asset + .query_balance(&deps.querier, env.contract.address.to_string()) + .map_err(|e| StdError::generic_err(format!("failed balance query: {0}", e)))?; + + // always floored + let user_balance = user_share * balance; + if !user_balance.is_zero() { + rewards.push(whitelisted_reward_asset.with_balance(user_balance)); + } + } + Ok(RewardsResponse { rewards }) +} + fn query_token_info( deps: Deps, parent: AllianceNftCollection, @@ -116,12 +152,3 @@ fn humanize_approval(approval: &BaseApproval) -> Approval { expires: approval.expires, } } - -// Given the querier and the contract address -// return the balance of the contract -pub fn try_query_contract_balance(querier: QuerierWrapper, contract_addr: &Addr) -> Result { - let contract_balance = querier - .query_balance(contract_addr, ALLOWED_DENOM)? - .amount; - Ok(contract_balance) -} \ No newline at end of file diff --git a/contracts/alliance-nft-collection/src/state.rs b/contracts/alliance-nft-collection/src/state.rs index e43bf04..d97ae3f 100644 --- a/contracts/alliance-nft-collection/src/state.rs +++ b/contracts/alliance-nft-collection/src/state.rs @@ -38,16 +38,6 @@ pub fn reduce_val_stake( Ok(()) } -// Keep track of in-progress unbondings -// - max of 7 unbondings at the same time -pub const UNBONDINGS: Map = Map::new("unb"); - -// Keep track of in-progress redelegations -// - max of 7 redelegations at the same time -pub const REDELEGATIONS: Map = Map::new("red"); - -pub const TEMP_BALANCE: Item = Item::new("temp_balance"); - // Keep track of rewards claimed for each token_id pub const NFT_BALANCE_CLAIMED: Map = Map::new("nb"); pub const REWARD_BALANCE: Item = Item::new("rb"); diff --git a/contracts/alliance-nft-collection/src/tests/custom_querier.rs b/contracts/alliance-nft-collection/src/tests/custom_querier.rs new file mode 100644 index 0000000..4ba1a2c --- /dev/null +++ b/contracts/alliance-nft-collection/src/tests/custom_querier.rs @@ -0,0 +1,117 @@ +use alliance_nft_packages::eris::StateResponse; +use alliance_nft_packages::state::ALLOWED_DENOM; +use cosmwasm_std::testing::{BankQuerier, MOCK_CONTRACT_ADDR}; +use cosmwasm_std::{ + coin, from_json, to_json_binary, Coin, Decimal, Empty, Querier, QuerierResult, QueryRequest, + SystemError, SystemResult, Uint128, WasmQuery, +}; +use cw20::Cw20QueryMsg; +use std::collections::HashMap; + +use super::cw20_querier::Cw20Querier; + +#[derive(Default)] +pub(super) struct CustomQuerier { + pub cw20_querier: Cw20Querier, + pub bank_querier: BankQuerier, +} + +impl Querier for CustomQuerier { + fn raw_query(&self, bin_request: &[u8]) -> QuerierResult { + let request: QueryRequest<_> = match from_json(bin_request) { + Ok(v) => v, + Err(e) => { + return Err(SystemError::InvalidRequest { + error: format!("Parsing query request: {}", e), + request: bin_request.into(), + }) + .into() + } + }; + self.handle_query(&request) + } +} + +impl CustomQuerier { + #[allow(dead_code)] + pub fn set_cw20_balance(&mut self, token: &str, user: &str, balance: u128) { + match self.cw20_querier.balances.get_mut(token) { + Some(contract_balances) => { + contract_balances.insert(user.to_string(), balance); + } + None => { + let mut contract_balances: HashMap = HashMap::default(); + contract_balances.insert(user.to_string(), balance); + self.cw20_querier + .balances + .insert(token.to_string(), contract_balances); + } + }; + } + + #[allow(dead_code)] + pub fn get_cw20_balance(&mut self, token: &str, user: &str) -> u128 { + match self.cw20_querier.balances.get_mut(token) { + Some(contract_balances) => contract_balances.get(user).copied().unwrap_or_default(), + None => 0u128, + } + } + + #[allow(dead_code)] + pub fn set_cw20_total_supply(&mut self, token: &str, total_supply: u128) { + self.cw20_querier + .total_supplies + .insert(token.to_string(), total_supply); + } + + #[allow(dead_code)] + pub fn set_bank_balances(&mut self, balances: &[Coin]) { + self.bank_querier = BankQuerier::new(&[(MOCK_CONTRACT_ADDR, balances)]) + } + + #[allow(dead_code)] + pub(crate) fn set_bank_balance(&mut self, amount: u128) { + self.set_bank_balances(&[coin(amount, ALLOWED_DENOM)]); + } + + pub fn handle_query(&self, request: &QueryRequest) -> QuerierResult { + match request { + QueryRequest::Wasm(WasmQuery::Smart { contract_addr, msg }) => { + if let Ok(query) = from_json::(msg) { + return self.cw20_querier.handle_query(contract_addr, query); + } + + if let Ok(query) = from_json::(msg) { + return match query { + alliance_nft_packages::eris::QueryMsg::State {} => { + Ok(to_json_binary(&StateResponse { + total_ustake: Uint128::new(100000), + total_uluna: Uint128::new(120000), + exchange_rate: Decimal::from_ratio(120u128, 100u128), + unlocked_coins: vec![], + unbonding: Uint128::zero(), + available: Uint128::zero(), + tvl_uluna: Uint128::new(120000), + }) + .into()) + .into() + } + }; + } + + err_unsupported_query(msg) + } + + QueryRequest::Bank(query) => self.bank_querier.query(query), + + _ => err_unsupported_query(request), + } + } +} + +pub(super) fn err_unsupported_query(request: T) -> QuerierResult { + SystemResult::Err(SystemError::InvalidRequest { + error: format!("[mock] unsupported query: {:?}", request), + request: Default::default(), + }) +} diff --git a/contracts/alliance-nft-collection/src/tests/cw20_querier.rs b/contracts/alliance-nft-collection/src/tests/cw20_querier.rs new file mode 100644 index 0000000..e875adf --- /dev/null +++ b/contracts/alliance-nft-collection/src/tests/cw20_querier.rs @@ -0,0 +1,70 @@ +use std::collections::HashMap; + +use cosmwasm_std::{to_json_binary, QuerierResult, SystemError, Uint128}; +use cw20::{BalanceResponse, Cw20QueryMsg, TokenInfoResponse}; + +use super::custom_querier::err_unsupported_query; + +#[derive(Default)] +pub(super) struct Cw20Querier { + /// Mapping token address to its total supply + pub total_supplies: HashMap, + /// Mapping token address and user address to the user's token balance + pub balances: HashMap>, +} + +impl Cw20Querier { + pub fn handle_query(&self, contract_addr: &str, query: Cw20QueryMsg) -> QuerierResult { + match &query { + Cw20QueryMsg::TokenInfo {} => { + let total_supply = self + .total_supplies + .get(contract_addr) + .ok_or_else(|| SystemError::InvalidRequest { + error: format!("[mock] total supply not set for cw20 `{}`", contract_addr), + request: Default::default(), + }) + .unwrap(); + + Ok(to_json_binary(&TokenInfoResponse { + name: "".to_string(), + symbol: "".to_string(), + decimals: 0, + total_supply: Uint128::new(*total_supply), + }) + .into()) + .into() + } + + Cw20QueryMsg::Balance { address } => { + let contract_balances = self + .balances + .get(contract_addr) + .ok_or_else(|| SystemError::InvalidRequest { + error: format!("[mock] balances not set for cw20 `{}`", contract_addr), + request: Default::default(), + }) + .unwrap(); + + let balance = contract_balances + .get(address) + .ok_or_else(|| SystemError::InvalidRequest { + error: format!( + "[mock] balance not set for cw20 `{}` and user `{}`", + contract_addr, address + ), + request: Default::default(), + }) + .unwrap(); + + Ok(to_json_binary(&BalanceResponse { + balance: Uint128::new(*balance), + }) + .into()) + .into() + } + + other_query => err_unsupported_query(other_query), + } + } +} diff --git a/contracts/alliance-nft-collection/src/tests/execute.rs b/contracts/alliance-nft-collection/src/tests/execute.rs index 491bac3..65c72e3 100644 --- a/contracts/alliance-nft-collection/src/tests/execute.rs +++ b/contracts/alliance-nft-collection/src/tests/execute.rs @@ -1,16 +1,27 @@ -use crate::tests::helpers::{break_nft, claim_alliance_emissions, mint, query_nft, setup_contract}; +use crate::contract::execute::execute; +use crate::tests::helpers::{ + break_nft, claim_alliance_emissions, mint, query_config, query_nft, query_rewards, + setup_contract, MOCK_DAO_TREASURY_ADDRESS, MOCK_LST, +}; +use alliance_nft_packages::eris::{AssetInfoExt, Hub}; +use alliance_nft_packages::errors::ContractError; +use alliance_nft_packages::execute::{ + ExecuteCollectionMsg, MintMsg, UpdateConfigMsg, UpdateRewardsCallbackMsg, +}; +use alliance_nft_packages::query::RewardsResponse; +use alliance_nft_packages::state::{Config, Trait, ALLOWED_DENOM}; use alliance_nft_packages::Extension; -use alliance_nft_packages::execute::{ExecuteCollectionMsg, MintMsg}; -use alliance_nft_packages::state::Trait; -use cosmwasm_std::testing::{mock_dependencies, mock_dependencies_with_balance, mock_env, mock_info}; -use cosmwasm_std::{BankMsg, Coin, CosmosMsg, Response, Uint128}; +use cosmwasm_std::testing::{mock_env, mock_info, MockApi, MockStorage, MOCK_CONTRACT_ADDR}; +use cosmwasm_std::{attr, Addr, Decimal, OwnedDeps, Response, Uint128}; use cw721::NftInfoResponse; -use crate::contract::execute::execute; +use cw_asset::{Asset, AssetInfo, AssetInfoUnchecked}; + +use super::custom_querier::CustomQuerier; #[test] fn mint_and_query_nft() { let mut deps = mock_dependencies(); - setup_contract(deps.as_mut()); + setup_contract(&mut deps); let res = mint(deps.as_mut(), "1"); @@ -63,58 +74,70 @@ fn mint_and_query_nft() { #[test] fn mint_invalid() { let mut deps = mock_dependencies(); - setup_contract(deps.as_mut()); + setup_contract(&mut deps); mint(deps.as_mut(), "1"); // Mint with the same token id - execute(deps.as_mut(), mock_env(), mock_info("minter", &[]), ExecuteCollectionMsg::Mint(MintMsg { - owner: "owner".to_string(), - token_id: "1".to_string(), - token_uri: None, - extension: Extension { - image: Some("image".to_string()), - image_data: None, - external_url: None, - description: None, - name: None, - attributes: Some(vec![Trait { - display_type: None, - trait_type: "trait_type".to_string(), - value: "value".to_string(), - }]), - background_color: None, - animation_url: None, - youtube_url: None, - }, - })).unwrap_err(); + execute( + deps.as_mut(), + mock_env(), + mock_info("minter", &[]), + ExecuteCollectionMsg::Mint(MintMsg { + owner: "owner".to_string(), + token_id: "1".to_string(), + token_uri: None, + extension: Extension { + image: Some("image".to_string()), + image_data: None, + external_url: None, + description: None, + name: None, + attributes: Some(vec![Trait { + display_type: None, + trait_type: "trait_type".to_string(), + value: "value".to_string(), + }]), + background_color: None, + animation_url: None, + youtube_url: None, + }, + }), + ) + .unwrap_err(); // Mint with the wrong minter - execute(deps.as_mut(), mock_env(), mock_info("wrong_minter", &[]), ExecuteCollectionMsg::Mint(MintMsg { - owner: "owner".to_string(), - token_id: "2".to_string(), - token_uri: None, - extension: Extension { - image: Some("image".to_string()), - image_data: None, - external_url: None, - description: None, - name: None, - attributes: Some(vec![Trait { - display_type: None, - trait_type: "trait_type".to_string(), - value: "value".to_string(), - }]), - background_color: None, - animation_url: None, - youtube_url: None, - }, - })).unwrap_err(); + execute( + deps.as_mut(), + mock_env(), + mock_info("wrong_minter", &[]), + ExecuteCollectionMsg::Mint(MintMsg { + owner: "owner".to_string(), + token_id: "2".to_string(), + token_uri: None, + extension: Extension { + image: Some("image".to_string()), + image_data: None, + external_url: None, + description: None, + name: None, + attributes: Some(vec![Trait { + display_type: None, + trait_type: "trait_type".to_string(), + value: "value".to_string(), + }]), + background_color: None, + animation_url: None, + youtube_url: None, + }, + }), + ) + .unwrap_err(); } #[test] fn break_nft_without_rewards() { let mut deps = mock_dependencies(); - setup_contract(deps.as_mut()); + setup_contract(&mut deps); mint(deps.as_mut(), "1"); @@ -166,11 +189,12 @@ fn break_nft_without_rewards() { #[test] fn break_nft_with_rewards() { - let mut deps = mock_dependencies_with_balance(&[Coin::new(1_000_000_000, "uluna")]); - setup_contract(deps.as_mut()); + let mut deps = mock_dependencies(); + + setup_contract(&mut deps); mint(deps.as_mut(), "1"); mint(deps.as_mut(), "2"); - claim_alliance_emissions(deps.as_mut(), Uint128::new(500_000_000)); + claim_alliance_emissions(&mut deps, Uint128::new(500_000_000)); let nft = query_nft(deps.as_ref(), "1"); assert_eq!( @@ -197,7 +221,11 @@ fn break_nft_with_rewards() { Trait { display_type: None, trait_type: "rewards".to_string(), - value: "250000000".to_string() + // 500 LUNA -> 416.66 ampLUNA + // 41.66 ampLUNA treasury share + // 375 ampLUNA total rewards + // 187.5 ampLUNA per NFT + value: "187500000".to_string() }, ]), background_color: None, @@ -211,17 +239,28 @@ fn break_nft_with_rewards() { assert_eq!( res, Response::default() - .add_message(CosmosMsg::Bank(BankMsg::Send { - amount: vec![Coin::new(250_000_000, "uluna")], - to_address: "owner".to_string(), - })) + .add_message( + AssetInfo::cw20(Addr::unchecked(MOCK_LST)) + .with_balance(Uint128::new(187500000)) + .transfer_msg("owner") + .unwrap() + ) .add_attributes(vec![ - ("action", "break_nft"), - ("token_id", "1"), - ("rewards", "250000000"), + attr("action", "break_nft"), + attr("token_id", "1"), + attr("rewards", "187500000"), + attr("user_share", "0.5"), ]) ); + // remove share + let previouse_balance = deps.querier.get_cw20_balance(MOCK_LST, MOCK_CONTRACT_ADDR); + deps.querier.set_cw20_balance( + MOCK_LST, + MOCK_CONTRACT_ADDR, + previouse_balance - 187500000u128, + ); + let nft = query_nft(deps.as_ref(), "1"); assert_eq!( nft, @@ -258,7 +297,7 @@ fn break_nft_with_rewards() { ); // Claim more rewards from alliance module. All rewards should go to remaining NFTs - claim_alliance_emissions(deps.as_mut(), Uint128::new(500_000_000)); + claim_alliance_emissions(&mut deps, Uint128::new(500_000_000)); let nft = query_nft(deps.as_ref(), "1"); assert_eq!( nft, @@ -319,7 +358,8 @@ fn break_nft_with_rewards() { Trait { display_type: None, trait_type: "rewards".to_string(), - value: "750000000".to_string() + // 187.5 (from before) + 375 (total added) for the single NFT + value: "562500000".to_string() }, ]), background_color: None, @@ -328,18 +368,480 @@ fn break_nft_with_rewards() { } } ); + + let nft = query_rewards(deps.as_ref(), "2"); + assert_eq!( + nft, + RewardsResponse { + rewards: vec![Asset::cw20(Addr::unchecked(MOCK_LST), 562500000u128)] + } + ); +} + +#[test] +fn break_nft_with_multi_rewards() { + let mut deps = mock_dependencies(); + + setup_contract(&mut deps); + mint(deps.as_mut(), "1"); + mint(deps.as_mut(), "2"); + claim_alliance_emissions(&mut deps, Uint128::new(500_000_000)); + + // add multi-coin rewards + let info = mock_info("owner", &[]); + let env = mock_env(); + let msg = ExecuteCollectionMsg::UpdateConfig(UpdateConfigMsg { + set_whitelisted_reward_assets: Some(vec![AssetInfoUnchecked::cw20("random")]), + dao_treasury_address: None, + dao_treasury_share: None, + add_whitelisted_reward_assets: None, + }); + execute(deps.as_mut(), env.clone(), info.clone(), msg).unwrap(); + deps.querier + .set_cw20_balance("random", MOCK_CONTRACT_ADDR, 10_000000u128); + + let nft = query_nft(deps.as_ref(), "1"); + assert_eq!( + nft, + NftInfoResponse { + token_uri: None, + extension: Extension { + image: Some("image".to_string()), + image_data: None, + external_url: None, + description: None, + name: None, + attributes: Some(vec![ + Trait { + display_type: None, + trait_type: "trait_type".to_string(), + value: "value".to_string() + }, + Trait { + display_type: None, + trait_type: "broken".to_string(), + value: "false".to_string() + }, + Trait { + display_type: None, + trait_type: "rewards".to_string(), + // 500 LUNA -> 416.66 ampLUNA + // 41.66 ampLUNA treasury share + // 375 ampLUNA total rewards + // 187.5 ampLUNA per NFT + value: "187500000".to_string() + }, + ]), + background_color: None, + animation_url: None, + youtube_url: None + } + } + ); + + let res = break_nft(deps.as_mut(), "1"); + assert_eq!( + res, + Response::default() + .add_message( + AssetInfo::cw20(Addr::unchecked(MOCK_LST)) + .with_balance(Uint128::new(187500000)) + .transfer_msg("owner") + .unwrap() + ) + .add_message( + AssetInfo::cw20(Addr::unchecked("random")) + .with_balance(Uint128::new(5_000000u128)) + .transfer_msg("owner") + .unwrap() + ) + .add_attributes(vec![ + attr("action", "break_nft"), + attr("token_id", "1"), + attr("rewards", "187500000"), + attr("user_share", "0.5"), + ]) + ); + + // remove share + let previouse_balance = deps.querier.get_cw20_balance(MOCK_LST, MOCK_CONTRACT_ADDR); + deps.querier.set_cw20_balance( + MOCK_LST, + MOCK_CONTRACT_ADDR, + previouse_balance - 187500000u128, + ); + let previouse_balance = deps.querier.get_cw20_balance("random", MOCK_CONTRACT_ADDR); + deps.querier.set_cw20_balance( + "random", + MOCK_CONTRACT_ADDR, + previouse_balance - 5_000000u128, + ); + + let nft = query_nft(deps.as_ref(), "1"); + assert_eq!( + nft, + NftInfoResponse { + token_uri: None, + extension: Extension { + image: Some("image".to_string()), + image_data: None, + external_url: None, + description: None, + name: None, + attributes: Some(vec![ + Trait { + display_type: None, + trait_type: "trait_type".to_string(), + value: "value".to_string() + }, + Trait { + display_type: None, + trait_type: "broken".to_string(), + value: "true".to_string() + }, + Trait { + display_type: None, + trait_type: "rewards".to_string(), + value: "0".to_string() + }, + ]), + background_color: None, + animation_url: None, + youtube_url: None + } + } + ); + + // Claim more rewards from alliance module. All rewards should go to remaining NFTs + claim_alliance_emissions(&mut deps, Uint128::new(500_000_000)); + let nft = query_nft(deps.as_ref(), "1"); + assert_eq!( + nft, + NftInfoResponse { + token_uri: None, + extension: Extension { + image: Some("image".to_string()), + image_data: None, + external_url: None, + description: None, + name: None, + attributes: Some(vec![ + Trait { + display_type: None, + trait_type: "trait_type".to_string(), + value: "value".to_string() + }, + Trait { + display_type: None, + trait_type: "broken".to_string(), + value: "true".to_string() + }, + Trait { + display_type: None, + trait_type: "rewards".to_string(), + value: "0".to_string() + }, + ]), + background_color: None, + animation_url: None, + youtube_url: None + } + } + ); + + let nft = query_nft(deps.as_ref(), "2"); + assert_eq!( + nft, + NftInfoResponse { + token_uri: None, + extension: Extension { + image: Some("image".to_string()), + image_data: None, + external_url: None, + description: None, + name: None, + attributes: Some(vec![ + Trait { + display_type: None, + trait_type: "trait_type".to_string(), + value: "value".to_string() + }, + Trait { + display_type: None, + trait_type: "broken".to_string(), + value: "false".to_string() + }, + Trait { + display_type: None, + trait_type: "rewards".to_string(), + // 187.5 (from before) + 375 (total added) for the single NFT + value: "562500000".to_string() + }, + ]), + background_color: None, + animation_url: None, + youtube_url: None + } + } + ); + + let nft = query_rewards(deps.as_ref(), "2"); + assert_eq!( + nft, + RewardsResponse { + rewards: vec![ + Asset::cw20(Addr::unchecked(MOCK_LST), 562500000u128), + Asset::cw20(Addr::unchecked("random"), 5_000000u128) + ] + } + ); } #[test] fn break_nft_invalid() { - let mut deps = mock_dependencies_with_balance(&[Coin::new(1_000_000_000, "uluna")]); - setup_contract(deps.as_mut()); + let mut deps = mock_dependencies(); + setup_contract(&mut deps); mint(deps.as_mut(), "1"); // Cannot break as a different owner - execute(deps.as_mut(), mock_env(), mock_info("owner2", &[]), ExecuteCollectionMsg::BreakNft("1".to_string())).unwrap_err(); + execute( + deps.as_mut(), + mock_env(), + mock_info("owner2", &[]), + ExecuteCollectionMsg::BreakNft("1".to_string()), + ) + .unwrap_err(); break_nft(deps.as_mut(), "1"); // Cannot break a broken NFT - execute(deps.as_mut(), mock_env(), mock_info("owner", &[]), ExecuteCollectionMsg::BreakNft("1".to_string())).unwrap_err(); -} \ No newline at end of file + execute( + deps.as_mut(), + mock_env(), + mock_info("owner", &[]), + ExecuteCollectionMsg::BreakNft("1".to_string()), + ) + .unwrap_err(); +} + +#[test] +fn stake_reward_callback() { + let mut deps = mock_dependencies(); + setup_contract(&mut deps); + mint(deps.as_mut(), "1"); + + // Cannot break as a different owner + let err = execute( + deps.as_mut(), + mock_env(), + mock_info("anyone", &[]), + ExecuteCollectionMsg::StakeRewardsCallback {}, + ) + .unwrap_err(); + + assert_eq!( + err, + ContractError::Unauthorized( + Addr::unchecked("anyone"), + Addr::unchecked(MOCK_CONTRACT_ADDR) + ) + ); + + let res = execute( + deps.as_mut(), + mock_env(), + mock_info(MOCK_CONTRACT_ADDR, &[]), + ExecuteCollectionMsg::StakeRewardsCallback {}, + ) + .unwrap(); + + assert_eq!( + res.attributes, + vec![ + attr("action", "stake_reward_callback"), + attr("tokens_to_stake", "0") + ] + ); + deps.querier.set_bank_balance(100u128); + + let res = execute( + deps.as_mut(), + mock_env(), + mock_info(MOCK_CONTRACT_ADDR, &[]), + ExecuteCollectionMsg::StakeRewardsCallback {}, + ) + .unwrap(); + assert_eq!( + res.attributes, + vec![ + attr("action", "stake_reward_callback"), + attr("tokens_to_stake", "100") + ] + ); +} + +#[test] +fn update_rewards_callback() { + let mut deps = mock_dependencies(); + setup_contract(&mut deps); + mint(deps.as_mut(), "1"); + + // Cannot break as a different owner + let err = execute( + deps.as_mut(), + mock_env(), + mock_info("anyone", &[]), + ExecuteCollectionMsg::UpdateRewardsCallback(UpdateRewardsCallbackMsg { + previous_lst_balance: Uint128::zero(), + }), + ) + .unwrap_err(); + assert_eq!( + err, + ContractError::Unauthorized( + Addr::unchecked("anyone"), + Addr::unchecked(MOCK_CONTRACT_ADDR) + ) + ); + + deps.querier + .set_cw20_balance(MOCK_LST, MOCK_CONTRACT_ADDR, 100u128); + + let res = execute( + deps.as_mut(), + mock_env(), + mock_info(MOCK_CONTRACT_ADDR, &[]), + ExecuteCollectionMsg::UpdateRewardsCallback(UpdateRewardsCallbackMsg { + previous_lst_balance: Uint128::zero(), + }), + ) + .unwrap(); + + assert_eq!( + res.attributes, + vec![ + attr("action", "update_rewards_callback"), + attr("rewards_collected", "90"), + attr("treasury_amount", "10") + ] + ); +} + +#[test] +fn update_config_invalid() { + let mut deps = mock_dependencies(); + setup_contract(&mut deps); + + let info = mock_info("user", &[]); + let env = mock_env(); + let msg = ExecuteCollectionMsg::UpdateConfig(UpdateConfigMsg { + dao_treasury_address: None, + dao_treasury_share: None, + set_whitelisted_reward_assets: None, + add_whitelisted_reward_assets: Some(vec![AssetInfoUnchecked::cw20("random")]), + }); + let err = execute(deps.as_mut(), env.clone(), info.clone(), msg).unwrap_err(); + + assert_eq!( + err, + ContractError::Unauthorized(Addr::unchecked("user"), Addr::unchecked("owner")) + ); + + let info = mock_info("owner", &[]); + let env = mock_env(); + let msg = ExecuteCollectionMsg::UpdateConfig(UpdateConfigMsg { + dao_treasury_address: None, + dao_treasury_share: None, + set_whitelisted_reward_assets: None, + add_whitelisted_reward_assets: Some(vec![ + AssetInfoUnchecked::cw20("random"), + AssetInfoUnchecked::cw20(MOCK_LST), + ]), + }); + let err = execute(deps.as_mut(), env.clone(), info.clone(), msg).unwrap_err(); + assert_eq!(err, ContractError::InvalidWhitelistedAssetInfo {}); + + let msg = ExecuteCollectionMsg::UpdateConfig(UpdateConfigMsg { + dao_treasury_address: None, + dao_treasury_share: None, + set_whitelisted_reward_assets: Some(vec![AssetInfoUnchecked::native(ALLOWED_DENOM)]), + add_whitelisted_reward_assets: Some(vec![AssetInfoUnchecked::cw20("random")]), + }); + let err = execute(deps.as_mut(), env.clone(), info.clone(), msg).unwrap_err(); + assert_eq!(err, ContractError::InvalidWhitelistedAssetInfo {}); + + let msg = ExecuteCollectionMsg::UpdateConfig(UpdateConfigMsg { + dao_treasury_address: None, + dao_treasury_share: Some(Decimal::percent(21)), + set_whitelisted_reward_assets: None, + add_whitelisted_reward_assets: None, + }); + let err = execute(deps.as_mut(), env.clone(), info.clone(), msg).unwrap_err(); + assert_eq!(err, ContractError::InvalidDaoTreasuryShare {}); +} + +#[test] +fn update_config() { + let mut deps = mock_dependencies(); + setup_contract(&mut deps); + + let config = query_config(deps.as_ref()); + assert_eq!( + config, + Config { + asset_denom: format!("factory/{}/{}", MOCK_CONTRACT_ADDR, "AllianceNFT"), + owner: Addr::unchecked("owner"), + dao_treasury_share: Decimal::percent(10), + lst_asset_info: cw_asset::AssetInfoBase::Cw20(Addr::unchecked(MOCK_LST)), + lst_hub_address: Hub(Addr::unchecked("hub")), + dao_treasury_address: Addr::unchecked(MOCK_DAO_TREASURY_ADDRESS), + whitelisted_reward_assets: vec![] + } + ); + + let info = mock_info("owner", &[]); + let env = mock_env(); + let msg = ExecuteCollectionMsg::UpdateConfig(UpdateConfigMsg { + dao_treasury_address: Some("new_treasury".to_string()), + dao_treasury_share: Some(Decimal::zero()), + set_whitelisted_reward_assets: Some(vec![ + AssetInfoUnchecked::cw20("random"), + AssetInfoUnchecked::cw20("random2"), + AssetInfoUnchecked::native("usdc"), + ]), + add_whitelisted_reward_assets: Some(vec![ + AssetInfoUnchecked::cw20("random2"), + AssetInfoUnchecked::native("usdc"), + AssetInfoUnchecked::native("usdc2"), + ]), + }); + + execute(deps.as_mut(), env.clone(), info.clone(), msg).unwrap(); + + let config = query_config(deps.as_ref()); + assert_eq!( + config, + Config { + asset_denom: format!("factory/{}/{}", MOCK_CONTRACT_ADDR, "AllianceNFT"), + owner: Addr::unchecked("owner"), + dao_treasury_share: Decimal::percent(0), + lst_asset_info: cw_asset::AssetInfoBase::Cw20(Addr::unchecked(MOCK_LST)), + lst_hub_address: Hub(Addr::unchecked("hub")), + dao_treasury_address: Addr::unchecked("new_treasury"), + whitelisted_reward_assets: vec![ + AssetInfo::cw20(Addr::unchecked("random")), + AssetInfo::cw20(Addr::unchecked("random2")), + AssetInfo::native("usdc"), + AssetInfo::native("usdc2"), + ] + } + ); +} + +pub(super) fn mock_dependencies() -> OwnedDeps { + OwnedDeps { + storage: MockStorage::default(), + api: MockApi::default(), + querier: CustomQuerier::default(), + custom_query_type: std::marker::PhantomData, + } +} diff --git a/contracts/alliance-nft-collection/src/tests/helpers.rs b/contracts/alliance-nft-collection/src/tests/helpers.rs index bcb8449..6c3b96c 100644 --- a/contracts/alliance-nft-collection/src/tests/helpers.rs +++ b/contracts/alliance-nft-collection/src/tests/helpers.rs @@ -1,17 +1,32 @@ +use super::custom_querier::CustomQuerier; use crate::contract::execute::execute; use crate::contract::instantiate::instantiate; use crate::contract::query::query; -use crate::state::TEMP_BALANCE; -use alliance_nft_packages::Extension; -use alliance_nft_packages::execute::{MintMsg, ExecuteCollectionMsg}; +#[allow(unused_imports)] +use alliance_nft_packages::eris::{AssetInfoExt, Hub, QueryMsg}; +use alliance_nft_packages::execute::{ExecuteCollectionMsg, MintMsg, UpdateRewardsCallbackMsg}; use alliance_nft_packages::instantiate::InstantiateCollectionMsg; -use alliance_nft_packages::query::QueryCollectionMsg; -use alliance_nft_packages::state::Trait; -use cosmwasm_std::testing::{mock_env, mock_info, MOCK_CONTRACT_ADDR}; -use cosmwasm_std::{from_binary, Addr, Deps, DepsMut, Response, Uint128}; +use alliance_nft_packages::query::{QueryCollectionMsg, RewardsResponse}; +use alliance_nft_packages::state::{Config, Trait, ALLOWED_DENOM}; +use alliance_nft_packages::Extension; +use cosmwasm_std::testing::{mock_env, mock_info, MockApi, MockStorage, MOCK_CONTRACT_ADDR}; +use cosmwasm_std::{ + attr, coins, from_json, to_json_binary, Addr, CosmosMsg, Decimal, Deps, DepsMut, OwnedDeps, + Response, Uint128, +}; use cw721::NftInfoResponse; +use cw_asset::AssetInfo; +use serde::de::DeserializeOwned; + +type MockContext = OwnedDeps; + +pub const MOCK_LST: &str = "hub_lst"; +pub const MOCK_DAO_TREASURY_ADDRESS: &str = "dao_treasury_address"; + +pub fn setup_contract(deps: &mut MockContext) -> Response { + deps.querier + .set_cw20_balance(MOCK_LST, MOCK_CONTRACT_ADDR, 0); -pub fn setup_contract(deps: DepsMut) -> Response { let info = mock_info("admin", &[]); let env = mock_env(); @@ -20,8 +35,13 @@ pub fn setup_contract(deps: DepsMut) -> Response { name: "Collection Name".to_string(), symbol: "CNA".to_string(), owner: Addr::unchecked("owner"), + + dao_treasury_share: Decimal::percent(10), + lst_asset_info: cw_asset::AssetInfoBase::Cw20(MOCK_LST.to_string()), + lst_hub_address: "hub".to_string(), + dao_treasury_address: MOCK_DAO_TREASURY_ADDRESS.to_string(), }; - instantiate(deps, env, info, init_msg).unwrap() + instantiate(deps.as_mut(), env, info, init_msg).unwrap() } pub fn mint(deps: DepsMut, token_id: &str) -> Response { @@ -61,24 +81,106 @@ pub fn query_nft(deps: Deps, token_id: &str) -> NftInfoResponse { let msg = QueryCollectionMsg::NftInfo { token_id: token_id.to_string(), }; - from_binary(&query(deps, mock_env(), msg).unwrap()).unwrap() + from_json(query(deps, mock_env(), msg).unwrap()).unwrap() } -pub fn claim_alliance_emissions(deps: DepsMut, rewards: Uint128) { - let contract_balance = deps - .querier - .query_balance(MOCK_CONTRACT_ADDR, "uluna") - .unwrap(); - if rewards > contract_balance.amount { - panic!("Contract balance is not enough to claim rewards"); - } +pub fn query_rewards(deps: Deps, token_id: &str) -> RewardsResponse { + let msg = QueryCollectionMsg::Rewards { + token_id: token_id.to_string(), + }; + from_json(query(deps, mock_env(), msg).unwrap()).unwrap() +} - TEMP_BALANCE - .save(deps.storage, &(contract_balance.amount - rewards)) - .unwrap(); +pub fn query_config(deps: Deps) -> Config { + let msg = QueryCollectionMsg::Config {}; + from_json(query(deps, mock_env(), msg).unwrap()).unwrap() +} + +pub fn claim_alliance_emissions(deps: &mut MockContext, rewards: Uint128) { + deps.querier + .bank_querier + .update_balance(MOCK_CONTRACT_ADDR, coins(rewards.u128(), ALLOWED_DENOM)); + // CLAIM REWARDS let info = mock_info(MOCK_CONTRACT_ADDR, &[]); let env = mock_env(); - let msg = ExecuteCollectionMsg::UpdateRewardsCallback {}; - execute(deps, env, info, msg).unwrap(); + let msg = ExecuteCollectionMsg::AllianceClaimRewards {}; + let result = execute(deps.as_mut(), env.clone(), info.clone(), msg).unwrap(); + + assert_eq!(result.messages.len(), 1); + assert_eq!( + result.messages[0].msg, + CosmosMsg::Wasm(cosmwasm_std::WasmMsg::Execute { + contract_addr: MOCK_CONTRACT_ADDR.to_string(), + msg: to_json_binary(&ExecuteCollectionMsg::StakeRewardsCallback {}).unwrap(), + funds: vec![] + }) + ); + + // STAKE REWARDS (in LST) CALLBACK + let msg = ExecuteCollectionMsg::StakeRewardsCallback {}; + let result = execute(deps.as_mut(), env.clone(), info.clone(), msg).unwrap(); + + let previouse_balance = deps.querier.get_cw20_balance(MOCK_LST, MOCK_CONTRACT_ADDR); + + let result_msg = ExecuteCollectionMsg::UpdateRewardsCallback(UpdateRewardsCallbackMsg { + previous_lst_balance: Uint128::new(previouse_balance), + }); + assert_eq!(result.messages.len(), 2); + assert_eq!( + result.messages[0].msg, + Hub(Addr::unchecked("hub")) + .bond_msg(ALLOWED_DENOM, rewards.u128(), None) + .unwrap() + ); + assert_eq!( + result.messages[1].msg, + CosmosMsg::Wasm(cosmwasm_std::WasmMsg::Execute { + contract_addr: MOCK_CONTRACT_ADDR.to_string(), + msg: to_json_binary(&result_msg).unwrap(), + funds: vec![] + }) + ); + + // UPDATE REWARDS CALLBACK + // exchange rate of 1.2 + let added_lst_amount = rewards.multiply_ratio(100u128, 120u128); + deps.querier.set_cw20_balance( + MOCK_LST, + MOCK_CONTRACT_ADDR, + previouse_balance + added_lst_amount.u128(), + ); + + let msg = result_msg; + let result = execute(deps.as_mut(), env, info, msg).unwrap(); + + let treasury_amount = Decimal::percent(10) * added_lst_amount; + let rewards_collected = added_lst_amount - treasury_amount; + + assert_eq!(result.messages.len(), 1); + assert_eq!( + result.messages[0].msg, + AssetInfo::cw20(Addr::unchecked(MOCK_LST)) + .with_balance(treasury_amount) + .transfer_msg(MOCK_DAO_TREASURY_ADDRESS) + .unwrap() + ); + assert_eq!( + result.attributes, + vec![ + attr("action", "update_rewards_callback"), + attr("rewards_collected", rewards_collected), + attr("treasury_amount", treasury_amount) + ] + ); + + deps.querier.set_cw20_balance( + MOCK_LST, + MOCK_CONTRACT_ADDR, + previouse_balance + added_lst_amount.u128() - treasury_amount.u128(), + ); +} + +pub(super) fn query_helper(deps: Deps, msg: QueryCollectionMsg) -> T { + from_json(query(deps, mock_env(), msg).unwrap()).unwrap() } diff --git a/contracts/alliance-nft-collection/src/tests/instantiate.rs b/contracts/alliance-nft-collection/src/tests/instantiate.rs index 12aebdf..bdc4b93 100644 --- a/contracts/alliance-nft-collection/src/tests/instantiate.rs +++ b/contracts/alliance-nft-collection/src/tests/instantiate.rs @@ -1,12 +1,12 @@ +use super::custom_querier::CustomQuerier; use crate::contract::instantiate::{instantiate, CONTRACT_NAME, CONTRACT_VERSION}; use crate::contract::reply::reply; - +use crate::tests::execute::mock_dependencies; +use crate::tests::helpers::{MOCK_DAO_TREASURY_ADDRESS, MOCK_LST}; use alliance_nft_packages::instantiate::InstantiateCollectionMsg; -use cosmwasm_std::testing::{ - mock_dependencies, mock_env, mock_info, MockApi, MockQuerier, MockStorage, -}; +use cosmwasm_std::testing::{mock_env, mock_info, MockApi, MockStorage}; use cosmwasm_std::{ - Addr, Binary, CosmosMsg, Empty, Env, MessageInfo, OwnedDeps, Reply, Response, SubMsg, + Addr, Binary, CosmosMsg, Decimal, Env, MessageInfo, OwnedDeps, Reply, Response, SubMsg, SubMsgResponse, SubMsgResult, }; use cw2::get_contract_version; @@ -24,7 +24,7 @@ fn test_instantiate_and_reply() { } pub fn intantiate_with_reply() -> ( - OwnedDeps, + OwnedDeps, Env, MessageInfo, ) { @@ -39,6 +39,11 @@ pub fn intantiate_with_reply() -> ( name: "Collection Name".to_string(), symbol: "CNA".to_string(), owner: Addr::unchecked("owner"), + + dao_treasury_share: Decimal::percent(10), + lst_asset_info: cw_asset::AssetInfoBase::Cw20(MOCK_LST.to_string()), + lst_hub_address: "hub".to_string(), + dao_treasury_address: MOCK_DAO_TREASURY_ADDRESS.to_string(), }; let res = instantiate(deps.as_mut(), env.clone(), info.clone(), msg).unwrap(); diff --git a/contracts/alliance-nft-collection/src/tests/migrate.rs b/contracts/alliance-nft-collection/src/tests/migrate.rs new file mode 100644 index 0000000..228b754 --- /dev/null +++ b/contracts/alliance-nft-collection/src/tests/migrate.rs @@ -0,0 +1,97 @@ +use std::borrow::BorrowMut; + +use crate::{ + contract::{instantiate::CONTRACT_VERSION, migrate::migrate}, + state::REWARD_BALANCE, + tests::helpers::{MOCK_DAO_TREASURY_ADDRESS, MOCK_LST}, +}; +use alliance_nft_packages::{ + eris::Hub, + migrate::MigrateMsg, + state::{ConfigV100, ALLOWED_DENOM}, +}; +use cosmwasm_std::{attr, Addr, Decimal, Uint128}; +use cw_storage_plus::Item; + +use super::instantiate::intantiate_with_reply; + +#[test] +fn test_migrate() { + let (mut deps, env, _) = intantiate_with_reply(); + + // mock old storage + let old = ConfigV100 { + owner: Addr::unchecked("owner"), + asset_denom: "factory/cosmos2contract/AllianceNFT".to_string(), + }; + let item: Item = Item::new("cfg"); + item.save(deps.storage.borrow_mut(), &old).unwrap(); + REWARD_BALANCE + .save(deps.storage.borrow_mut(), &Uint128::new(120000u128)) + .unwrap(); + deps.querier.set_bank_balance(120000u128 * 10000u128); + + // migrate to different version (without applying 1.1.0) + let res = migrate( + deps.as_mut(), + env.clone(), + MigrateMsg { + nft_collection_code_id: Some(4711), + version: "1.0.0".to_string(), + version110_data: Some(alliance_nft_packages::migrate::Version110MigrateData { + dao_treasury_share: Decimal::percent(10), + lst_asset_info: cw_asset::AssetInfoBase::Cw20(MOCK_LST.to_string()), + lst_hub: "hub".to_string(), + dao_treasury_address: MOCK_DAO_TREASURY_ADDRESS.to_string(), + }), + }, + ) + .unwrap(); + + assert_eq!( + res.attributes, + vec![ + attr("method", "try_migrate"), + // is already 1.1.0 as we only mock the item state + attr("version", CONTRACT_VERSION) + ] + ); + assert_eq!(res.messages.len(), 0); + + // migrate to 1.1.0 + let res = migrate( + deps.as_mut(), + env, + MigrateMsg { + nft_collection_code_id: Some(4711), + version: "1.1.0".to_string(), + version110_data: Some(alliance_nft_packages::migrate::Version110MigrateData { + dao_treasury_share: Decimal::percent(10), + lst_asset_info: cw_asset::AssetInfoBase::Cw20(MOCK_LST.to_string()), + lst_hub: "hub".to_string(), + dao_treasury_address: MOCK_DAO_TREASURY_ADDRESS.to_string(), + }), + }, + ) + .unwrap(); + + assert_eq!( + res.attributes, + vec![ + attr("method", "migrate_to_1_1_0"), + // always the previously set version + attr("version", "1.0.0"), + attr("balance_native", "1200000000"), + attr("rewards_current", "120000"), + attr("rewards_in_lst", "100000") + ], + ); + + assert_eq!(res.messages.len(), 1); + assert_eq!( + res.messages[0].msg, + Hub(Addr::unchecked("hub")) + .bond_msg(ALLOWED_DENOM, 1200000000u128, None) + .unwrap() + ); +} diff --git a/contracts/alliance-nft-collection/src/tests/mod.rs b/contracts/alliance-nft-collection/src/tests/mod.rs index e15be26..8a40e44 100644 --- a/contracts/alliance-nft-collection/src/tests/mod.rs +++ b/contracts/alliance-nft-collection/src/tests/mod.rs @@ -1,4 +1,7 @@ -mod instantiate; +pub mod custom_querier; +pub mod cw20_querier; mod execute; +mod helpers; +mod instantiate; +pub mod migrate; mod query; -mod helpers; \ No newline at end of file diff --git a/contracts/alliance-nft-collection/src/tests/query.rs b/contracts/alliance-nft-collection/src/tests/query.rs index 17a2772..3e3095e 100644 --- a/contracts/alliance-nft-collection/src/tests/query.rs +++ b/contracts/alliance-nft-collection/src/tests/query.rs @@ -1,5 +1,9 @@ -use alliance_nft_packages::query::QueryCollectionMsg; -use crate::contract::query::query; +use crate::{ + contract::query::query, + tests::helpers::{query_helper, MOCK_DAO_TREASURY_ADDRESS, MOCK_LST}, +}; +use alliance_nft_packages::{eris::Hub, query::QueryCollectionMsg, state::Config}; +use cosmwasm_std::{Addr, Decimal}; use super::instantiate::intantiate_with_reply; @@ -7,8 +11,14 @@ use super::instantiate::intantiate_with_reply; fn test_query_info_and_config() { let (deps, env, _) = intantiate_with_reply(); - let contract_info_res = query(deps.as_ref(), env.clone(), QueryCollectionMsg::ContractInfo {}).unwrap(); - let contract_conf_res = query(deps.as_ref(), env, QueryCollectionMsg::Config {}).unwrap(); + let contract_info_res = query( + deps.as_ref(), + env.clone(), + QueryCollectionMsg::ContractInfo {}, + ) + .unwrap(); + + let contract_conf_res: Config = query_helper(deps.as_ref(), QueryCollectionMsg::Config {}); assert_eq!( contract_info_res, @@ -16,6 +26,14 @@ fn test_query_info_and_config() { ); assert_eq!( contract_conf_res, - "{\"owner\":\"owner\",\"asset_denom\":\"factory/cosmos2contract/AllianceNFT\"}".as_bytes() + Config { + owner: Addr::unchecked("owner"), + asset_denom: "factory/cosmos2contract/AllianceNFT".to_string(), + dao_treasury_share: Decimal::percent(10), + lst_asset_info: cw_asset::AssetInfoBase::Cw20(Addr::unchecked(MOCK_LST)), + lst_hub_address: Hub(Addr::unchecked("hub")), + dao_treasury_address: Addr::unchecked(MOCK_DAO_TREASURY_ADDRESS), + whitelisted_reward_assets: vec![] + } ); } diff --git a/contracts/alliance-nft-minter/Cargo.toml b/contracts/alliance-nft-minter/Cargo.toml index 9b5f2f2..f0653ba 100644 --- a/contracts/alliance-nft-minter/Cargo.toml +++ b/contracts/alliance-nft-minter/Cargo.toml @@ -25,6 +25,7 @@ cw721-base = { workspace = true } schemars = { workspace = true } cw-storage-plus = { workspace = true } cw-utils = { workspace = true } +cw-asset = { workspace = true } serde = { workspace = true } thiserror = { workspace = true } terra-proto-rs = { workspace = true } diff --git a/contracts/alliance-nft-minter/src/contract/execute.rs b/contracts/alliance-nft-minter/src/contract/execute.rs index 176224e..686b45c 100644 --- a/contracts/alliance-nft-minter/src/contract/execute.rs +++ b/contracts/alliance-nft-minter/src/contract/execute.rs @@ -3,9 +3,8 @@ use std::collections::HashMap; use alliance_nft_packages::errors::ContractError; use alliance_nft_packages::execute::{ExecuteCollectionMsg, ExecuteMinterMsg, MintMsg}; use alliance_nft_packages::state::MinterExtension; -use cosmwasm_std::{ - entry_point, to_binary, DepsMut, Env, MessageInfo, Order::Ascending, Response, WasmMsg, -}; +use cosmwasm_std::to_json_binary; +use cosmwasm_std::{entry_point, DepsMut, Env, MessageInfo, Order::Ascending, Response, WasmMsg}; use crate::state::{CONFIG, NFT_METADATA, STATS}; @@ -72,7 +71,7 @@ fn try_append_nft_metadata( return Err(ContractError::AlreadyExists(key)); } NFT_METADATA.save(deps.storage, key, &value)?; - new_minted_nfts = new_minted_nfts + 1; + new_minted_nfts += 1; } STATS.update(deps.storage, |mut stats| -> Result<_, ContractError> { @@ -107,7 +106,7 @@ fn try_mint(deps: DepsMut, env: Env, info: MessageInfo) -> Result { let dao_treasury_addr = deps.api.addr_validate(&addr)?; Some(dao_treasury_addr) - }, - None => None + } + None => None, }; set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION) .map_err(ContractError::Std)?; - + STATS.save(deps.storage, &MinterStats::default())?; CONFIG.save( deps.storage, &MinterConfig { owner: info.sender.clone(), - dao_treasury_address: dao_treasury_address, + dao_treasury_address: dao_treasury_address.clone(), nft_collection_address: None, mint_start_time: msg.mint_start_time, mint_end_time: msg.mint_end_time, @@ -52,11 +53,18 @@ pub fn instantiate( let instantiate_message = WasmMsg::Instantiate { admin: Some(env.contract.address.to_string()), code_id: msg.nft_collection_code_id, - msg: to_binary(&InstantiateCollectionMsg { + msg: to_json_binary(&InstantiateCollectionMsg { name: "AllianceNFT".to_string(), symbol: "ALLIANCE".to_string(), minter: env.contract.address.to_string(), owner: env.contract.address.clone(), + + dao_treasury_address: dao_treasury_address + .unwrap_or(env.contract.address) + .to_string(), + lst_hub_address: msg.lst_hub_address, + dao_treasury_share: msg.dao_treasury_share, + lst_asset_info: msg.lst_asset_info, })?, funds: info.funds, label: "Alliance NFT Collection".to_string(), @@ -87,7 +95,7 @@ pub fn reply_on_instantiate(deps: DepsMut, reply: Reply) -> Result Result<_, ContractError> { config.nft_collection_address = Some(contract_addr); Ok(config) diff --git a/contracts/alliance-nft-minter/src/contract/migrate.rs b/contracts/alliance-nft-minter/src/contract/migrate.rs index 6ffac55..ae7c41a 100644 --- a/contracts/alliance-nft-minter/src/contract/migrate.rs +++ b/contracts/alliance-nft-minter/src/contract/migrate.rs @@ -1,21 +1,36 @@ use alliance_nft_packages::errors::ContractError; use alliance_nft_packages::migrate::MigrateMsg; -use cosmwasm_std::entry_point; +use cosmwasm_std::{entry_point, to_json_binary, CosmosMsg, WasmMsg}; use cosmwasm_std::{DepsMut, Env, Response}; use cw2::{get_contract_version, set_contract_version}; +use crate::state::CONFIG; #[cfg_attr(not(feature = "library"), entry_point)] pub fn migrate(deps: DepsMut, _env: Env, msg: MigrateMsg) -> Result { - let MigrateMsg { version } = msg; - try_migrate(deps, version) + try_migrate(deps, msg) } -fn try_migrate(deps: DepsMut, version: String) -> Result { +fn try_migrate(deps: DepsMut, msg: MigrateMsg) -> Result { + let version = msg.version.clone(); let contract_version = get_contract_version(deps.storage)?; set_contract_version(deps.storage, contract_version.contract, version)?; - Ok(Response::new() + let mut response = Response::new() .add_attribute("method", "try_migrate") - .add_attribute("version", contract_version.version)) + .add_attribute("version", contract_version.version); + + if let Some(nft_collection_code_id) = msg.nft_collection_code_id { + let config = CONFIG.load(deps.storage)?; + if let Some(nft_collection_address) = config.nft_collection_address { + let migrate_nft_collection_msg = CosmosMsg::Wasm(WasmMsg::Migrate { + contract_addr: nft_collection_address.to_string(), + new_code_id: nft_collection_code_id, + msg: to_json_binary(&msg)?, + }); + response = response.add_message(migrate_nft_collection_msg); + } + } + + Ok(response) } diff --git a/contracts/alliance-nft-minter/src/contract/query.rs b/contracts/alliance-nft-minter/src/contract/query.rs index 2bb8d7e..eb3b950 100644 --- a/contracts/alliance-nft-minter/src/contract/query.rs +++ b/contracts/alliance-nft-minter/src/contract/query.rs @@ -1,6 +1,6 @@ use alliance_nft_packages::query::QueryMinterMsg; -use alliance_nft_packages::state::{MinterConfig, MinterStats, MinterExtension}; -use cosmwasm_std::{entry_point, to_binary}; +use alliance_nft_packages::state::{MinterConfig, MinterExtension, MinterStats}; +use cosmwasm_std::{entry_point, to_json_binary}; use cosmwasm_std::{Binary, Deps, Env, StdResult}; use crate::state::{CONFIG, NFT_METADATA, STATS}; @@ -8,9 +8,9 @@ use crate::state::{CONFIG, NFT_METADATA, STATS}; #[cfg_attr(not(feature = "library"), entry_point)] pub fn query(deps: Deps, _env: Env, msg: QueryMinterMsg) -> StdResult { match msg { - QueryMinterMsg::Config {} => to_binary(&query_config(deps)?), - QueryMinterMsg::Stats {} => to_binary(&query_stats(deps)?), - QueryMinterMsg::NftData(address) => to_binary(&query_nft_data(deps, address)?), + QueryMinterMsg::Config {} => to_json_binary(&query_config(deps)?), + QueryMinterMsg::Stats {} => to_json_binary(&query_stats(deps)?), + QueryMinterMsg::NftData(address) => to_json_binary(&query_nft_data(deps, address)?), } } diff --git a/contracts/alliance-nft-minter/src/tests/execute.rs b/contracts/alliance-nft-minter/src/tests/execute.rs index b55f09e..2a23fb5 100644 --- a/contracts/alliance-nft-minter/src/tests/execute.rs +++ b/contracts/alliance-nft-minter/src/tests/execute.rs @@ -5,7 +5,7 @@ use alliance_nft_packages::query::QueryMinterMsg; use alliance_nft_packages::state::{MinterStats, Trait}; use alliance_nft_packages::Extension; use cosmwasm_std::testing::mock_info; -use cosmwasm_std::{to_binary, Response, WasmMsg}; +use cosmwasm_std::{to_json_binary, Response, WasmMsg}; use super::instantiate::intantiate_with_reply; @@ -17,7 +17,7 @@ fn append_nft_metadata() { // Execute the message let res = append_nft_metadata_execution( deps.as_mut(), - "creator", + "creator", "terra1zdpgj8am5nqqvht927k3etljyl6a52kwqup0je".to_string(), ); @@ -34,7 +34,7 @@ fn append_nft_metadata() { let query_res = query(deps.as_ref(), env, QueryMinterMsg::Stats {}).unwrap(); assert_eq!( query_res, - to_binary(&MinterStats { + to_json_binary(&MinterStats { available_nfts: 1, minted_nfts: 0, }) @@ -66,7 +66,7 @@ fn append_nft_metadata_unauthorized() { let query_res = query(deps.as_ref(), env, QueryMinterMsg::Stats {}).unwrap(); assert_eq!( query_res, - to_binary(&MinterStats { + to_json_binary(&MinterStats { available_nfts: 0, minted_nfts: 0, }) @@ -113,7 +113,7 @@ fn append_nft_metadata_duplicated_error() { let query_res = query(deps.as_ref(), env, QueryMinterMsg::Stats {}).unwrap(); assert_eq!( query_res, - to_binary(&MinterStats { + to_json_binary(&MinterStats { available_nfts: 1, minted_nfts: 0, }) @@ -153,7 +153,7 @@ fn mint_nft() { // assert message response let mint_msg = WasmMsg::Execute { contract_addr: "nft_collection_address".to_string(), - msg: to_binary(&ExecuteCollectionMsg::Mint(MintMsg { + msg: to_json_binary(&ExecuteCollectionMsg::Mint(MintMsg { token_id: "1".to_string(), owner: "terra1zdpgj8am5nqqvht927k3etljyl6a52kwqup0je".to_string(), extension: Extension { @@ -187,7 +187,7 @@ fn mint_nft() { let query_res = query(deps.as_ref(), env, QueryMinterMsg::Stats {}).unwrap(); assert_eq!( query_res, - to_binary(&MinterStats { + to_json_binary(&MinterStats { available_nfts: 0, minted_nfts: 1, }) @@ -208,14 +208,14 @@ fn mint_inexistent_nft() { ); assert_eq!( res.unwrap_err().to_string(), - String::from("alliance_nft_packages::state::MinterExtension not found") + String::from("type: alliance_nft_packages::state::MinterExtension; key: [00, 04, 6E, 66, 74, 73, 74, 65, 72, 72, 61, 31, 7A, 64, 70, 67, 6A, 38, 61, 6D, 35, 6E, 71, 71, 76, 68, 74, 39, 32, 37, 6B, 33, 65, 74, 6C, 6A, 79, 6C, 36, 61, 35, 32, 6B, 77, 71, 75, 70, 30, 6A, 65] not found") ); // query to see if stats match let query_res = query(deps.as_ref(), env, QueryMinterMsg::Stats {}).unwrap(); assert_eq!( query_res, - to_binary(&MinterStats { + to_json_binary(&MinterStats { available_nfts: 0, minted_nfts: 0, }) @@ -258,7 +258,7 @@ fn minting_outside_allowed_period() { let query_res = query(deps.as_ref(), env, QueryMinterMsg::Stats {}).unwrap(); assert_eq!( query_res, - to_binary(&MinterStats { + to_json_binary(&MinterStats { available_nfts: 0, minted_nfts: 0, }) @@ -300,7 +300,7 @@ fn send_to_dao_treasury() { // assert message response let mint_msg = WasmMsg::Execute { contract_addr: "nft_collection_address".to_string(), - msg: to_binary(&ExecuteCollectionMsg::Mint(MintMsg { + msg: to_json_binary(&ExecuteCollectionMsg::Mint(MintMsg { token_id: "1".to_string(), owner: "dao_treasury_address".to_string(), extension: Extension { @@ -334,7 +334,7 @@ fn send_to_dao_treasury() { let query_res = query(deps.as_ref(), env, QueryMinterMsg::Stats {}).unwrap(); assert_eq!( query_res, - to_binary(&MinterStats { + to_json_binary(&MinterStats { available_nfts: 0, minted_nfts: 1, }) @@ -380,7 +380,7 @@ fn send_to_dao_before_end_time() { let query_res = query(deps.as_ref(), env, QueryMinterMsg::Stats {}).unwrap(); assert_eq!( query_res, - to_binary(&MinterStats { + to_json_binary(&MinterStats { available_nfts: 1, minted_nfts: 0, }) @@ -460,7 +460,7 @@ fn test_try_change_owener() { ]) .add_message(WasmMsg::Execute { contract_addr: "nft_collection_address".to_string(), - msg: to_binary(&ExecuteCollectionMsg::ChangeOwner( + msg: to_json_binary(&ExecuteCollectionMsg::ChangeOwner( "terra1zdpgj8am5nqqvht927k3etljyl6a52kwqup0je".to_string() )) .unwrap(), @@ -481,7 +481,6 @@ fn remove_nft_from_mint() { "terra1zdpgj8am5nqqvht927k3etljyl6a52kwqup0je".to_string(), ); - // mint an nft let res = execute( deps.as_mut(), @@ -494,14 +493,17 @@ fn remove_nft_from_mint() { res.unwrap(), Response::default() .add_attribute("method", "try_remove_token") - .add_attribute("removed_token", "terra1zdpgj8am5nqqvht927k3etljyl6a52kwqup0je") + .add_attribute( + "removed_token", + "terra1zdpgj8am5nqqvht927k3etljyl6a52kwqup0je" + ) ); // query to see if stats match let query_res = query(deps.as_ref(), env, QueryMinterMsg::Stats {}).unwrap(); assert_eq!( query_res, - to_binary(&MinterStats { + to_json_binary(&MinterStats { available_nfts: 0, minted_nfts: 0, }) @@ -509,7 +511,6 @@ fn remove_nft_from_mint() { ); } - #[test] fn remove_nft_from_mint_wrong_sender() { // Create the env with the contract @@ -522,7 +523,6 @@ fn remove_nft_from_mint_wrong_sender() { "terra1zdpgj8am5nqqvht927k3etljyl6a52kwqup0je".to_string(), ); - // mint an nft let res = execute( deps.as_mut(), @@ -533,6 +533,8 @@ fn remove_nft_from_mint_wrong_sender() { assert_eq!( res.unwrap_err().to_string(), - String::from("Unauthorized execution, sender (creatorw) is not the expected address (creator)") + String::from( + "Unauthorized execution, sender (creatorw) is not the expected address (creator)" + ) ); -} \ No newline at end of file +} diff --git a/contracts/alliance-nft-minter/src/tests/instantiate.rs b/contracts/alliance-nft-minter/src/tests/instantiate.rs index 83fb9c6..bb8ee14 100644 --- a/contracts/alliance-nft-minter/src/tests/instantiate.rs +++ b/contracts/alliance-nft-minter/src/tests/instantiate.rs @@ -9,8 +9,8 @@ use cosmwasm_std::testing::{ mock_dependencies, mock_env, mock_info, MockApi, MockQuerier, MockStorage, }; use cosmwasm_std::{ - to_binary, Addr, Empty, Env, Event, MessageInfo, OwnedDeps, Reply, Response, SubMsg, - SubMsgResponse, SubMsgResult, Timestamp, WasmMsg, + to_json_binary, Addr, Decimal, Empty, Env, Event, MessageInfo, OwnedDeps, Reply, Response, + SubMsg, SubMsgResponse, SubMsgResult, Timestamp, WasmMsg, }; use cw2::get_contract_version; @@ -22,13 +22,15 @@ fn test_instantiate() { assert_eq!( res.to_string(), - to_binary(&MinterConfig { + to_json_binary(&MinterConfig { dao_treasury_address: Some(Addr::unchecked("dao_treasury_address")), nft_collection_address: Some(Addr::unchecked("nft_collection_address")), owner: Addr::unchecked("creator"), mint_start_time: Timestamp::from_seconds(1), mint_end_time: Timestamp::from_seconds(3), - }).unwrap().to_string() + }) + .unwrap() + .to_string() ); } @@ -46,12 +48,16 @@ fn test_instantiate_wrong_time_range() { nft_collection_code_id: 1, mint_start_time: Timestamp::from_seconds(3), mint_end_time: Timestamp::from_seconds(1), + + dao_treasury_share: Decimal::zero(), + lst_asset_info: cw_asset::AssetInfoBase::Cw20("hub_lst".to_string()), + lst_hub_address: "hub".to_string(), }; let res = instantiate(deps.as_mut(), env.clone(), info.clone(), msg); // assert the message error assert_eq!( - res.unwrap_err().to_string(), + res.unwrap_err().to_string(), String::from("Invalid mint time range, mint_start_time is greater than mint_end_time") ); } @@ -73,6 +79,10 @@ pub fn intantiate_with_reply() -> ( nft_collection_code_id: 1, mint_start_time: Timestamp::from_seconds(1), mint_end_time: Timestamp::from_seconds(3), + + dao_treasury_share: Decimal::zero(), + lst_asset_info: cw_asset::AssetInfoBase::Cw20("hub_lst".to_string()), + lst_hub_address: "hub".to_string(), }; let res = instantiate(deps.as_mut(), env.clone(), info.clone(), msg).unwrap(); @@ -86,11 +96,16 @@ pub fn intantiate_with_reply() -> ( WasmMsg::Instantiate { admin: Some(env.contract.address.to_string()), code_id: 1, - msg: to_binary(&InstantiateCollectionMsg { + msg: to_json_binary(&InstantiateCollectionMsg { name: "AllianceNFT".to_string(), symbol: "ALLIANCE".to_string(), minter: env.contract.address.to_string(), owner: Addr::unchecked("cosmos2contract"), + + dao_treasury_address: "dao_treasury_address".to_string(), + dao_treasury_share: Decimal::zero(), + lst_asset_info: cw_asset::AssetInfoBase::Cw20("hub_lst".to_string()), + lst_hub_address: "hub".to_string(), }) .unwrap(), funds: vec![], diff --git a/lcov.info b/lcov.info new file mode 100644 index 0000000..73edf19 --- /dev/null +++ b/lcov.info @@ -0,0 +1,902 @@ +TN: +SF:contracts/alliance-nft-collection/src/contract/execute.rs +FN:34,execute +FN:65,try_alliance_claim_rewards +FN:74,{closure#0} +FN:78,{closure#1} +FN:101,get_stake_reward_callback_msg +FN:109,try_stake_reward_callback +FN:157,try_update_reward_callback +FN:190,{closure#0} +FN:203,try_alliance_delegate +FN:242,try_alliance_undelegate +FN:278,try_alliance_redelegate +FN:318,try_breaknft +FN:333,{closure#0} +FN:352,{closure#1} +FN:401,try_mint +FN:408,{closure#0} +FN:423,try_change_owner +FN:432,{closure#0} +FN:442,try_update_config +FN:479,authorize_execution +FNF:20 +FNDA:9,execute +FNDA:1,try_alliance_claim_rewards +FNDA:0,{closure#0} +FNDA:2,{closure#1} +FNDA:2,get_stake_reward_callback_msg +FNDA:1,try_stake_reward_callback +FNDA:1,try_update_reward_callback +FNDA:3,{closure#0} +FNDA:0,try_alliance_delegate +FNDA:0,try_alliance_undelegate +FNDA:0,try_alliance_redelegate +FNDA:2,try_breaknft +FNDA:2,{closure#0} +FNDA:8,{closure#1} +FNDA:8,try_mint +FNDA:24,{closure#0} +FNDA:0,try_change_owner +FNDA:0,{closure#0} +FNDA:1,try_update_config +FNDA:1,authorize_execution +DA:34,9 +DA:40,9 +DA:41,9 +DA:42,0 +DA:43,0 +DA:44,0 +DA:46,0 +DA:47,0 +DA:49,2 +DA:51,2 +DA:52,1 +DA:53,2 +DA:56,3 +DA:57,9 +DA:58,0 +DA:59,2 +DA:61,0 +DA:65,1 +DA:66,2 +DA:67,2 +DA:68,1 +DA:69,0 +DA:72,2 +DA:73,1 +DA:74,0 +DA:76,5 +DA:78,2 +DA:79,0 +DA:80,0 +DA:81,0 +DA:82,0 +DA:84,0 +DA:85,0 +DA:86,0 +DA:89,0 +DA:93,4 +DA:95,8 +DA:96,4 +DA:97,2 +DA:98,2 +DA:101,2 +DA:102,2 +DA:103,2 +DA:104,2 +DA:105,2 +DA:109,1 +DA:114,3 +DA:115,1 +DA:118,2 +DA:119,2 +DA:121,1 +DA:122,4 +DA:123,1 +DA:124,1 +DA:129,2 +DA:131,2 +DA:134,2 +DA:136,1 +DA:138,2 +DA:139,1 +DA:140,3 +DA:141,2 +DA:145,1 +DA:148,4 +DA:149,5 +DA:150,2 +DA:151,5 +DA:153,2 +DA:154,2 +DA:157,1 +DA:163,3 +DA:164,1 +DA:166,1 +DA:168,1 +DA:169,2 +DA:172,1 +DA:173,1 +DA:174,2 +DA:175,2 +DA:176,1 +DA:177,1 +DA:178,1 +DA:179,4 +DA:181,1 +DA:182,1 +DA:184,1 +DA:188,3 +DA:189,3 +DA:190,3 +DA:191,1 +DA:194,5 +DA:195,3 +DA:196,1 +DA:197,2 +DA:199,2 +DA:200,2 +DA:203,0 +DA:209,0 +DA:210,0 +DA:212,0 +DA:214,0 +DA:216,0 +DA:217,0 +DA:230,0 +DA:231,0 +DA:234,0 +DA:235,0 +DA:237,0 +DA:239,0 +DA:242,0 +DA:248,0 +DA:249,0 +DA:251,0 +DA:252,0 +DA:254,0 +DA:255,0 +DA:257,0 +DA:261,0 +DA:262,0 +DA:265,0 +DA:266,0 +DA:268,0 +DA:269,0 +DA:271,0 +DA:272,0 +DA:273,0 +DA:274,0 +DA:275,0 +DA:278,0 +DA:284,0 +DA:285,0 +DA:287,0 +DA:288,0 +DA:290,0 +DA:291,0 +DA:292,0 +DA:293,0 +DA:295,0 +DA:299,0 +DA:300,0 +DA:301,0 +DA:304,0 +DA:305,0 +DA:307,0 +DA:308,0 +DA:309,0 +DA:311,0 +DA:312,0 +DA:313,0 +DA:314,0 +DA:315,0 +DA:318,2 +DA:325,4 +DA:326,4 +DA:327,4 +DA:328,5 +DA:330,4 +DA:331,1 +DA:332,1 +DA:333,2 +DA:334,2 +DA:335,1 +DA:336,2 +DA:337,1 +DA:339,0 +DA:342,2 +DA:347,4 +DA:348,4 +DA:349,4 +DA:351,2 +DA:352,8 +DA:353,4 +DA:354,12 +DA:355,2 +DA:356,4 +DA:357,4 +DA:361,3 +DA:364,1 +DA:365,2 +DA:369,1 +DA:370,2 +DA:372,1 +DA:373,2 +DA:375,2 +DA:376,1 +DA:377,1 +DA:379,2 +DA:380,1 +DA:381,1 +DA:382,3 +DA:383,1 +DA:384,1 +DA:389,5 +DA:390,1 +DA:391,2 +DA:392,3 +DA:393,1 +DA:394,2 +DA:395,2 +DA:396,2 +DA:401,8 +DA:408,24 +DA:409,8 +DA:410,8 +DA:411,8 +DA:414,1 +DA:415,1 +DA:416,7 +DA:417,1 +DA:418,7 +DA:423,0 +DA:428,0 +DA:429,0 +DA:430,0 +DA:432,0 +DA:433,0 +DA:434,0 +DA:436,0 +DA:437,0 +DA:438,0 +DA:442,1 +DA:447,2 +DA:448,4 +DA:450,1 +DA:451,2 +DA:454,3 +DA:455,3 +DA:458,3 +DA:459,3 +DA:461,1 +DA:462,1 +DA:465,2 +DA:466,4 +DA:468,1 +DA:469,1 +DA:470,1 +DA:471,1 +DA:474,2 +DA:476,2 +DA:479,1 +DA:480,8 +DA:481,3 +DA:483,2 +LF:243 +LH:160 +end_of_record +TN: +SF:contracts/alliance-nft-collection/src/contract/instantiate.rs +FN:33,instantiate +FN:79,reply_on_instantiate +FNF:2 +FNDA:6,instantiate +FNDA:2,reply_on_instantiate +DA:33,6 +DA:39,5 +DA:40,0 +DA:42,25 +DA:43,9 +DA:44,13 +DA:45,1 +DA:46,11 +DA:48,12 +DA:49,22 +DA:50,13 +DA:51,25 +DA:52,13 +DA:56,12 +DA:57,23 +DA:60,8 +DA:61,12 +DA:70,1 +DA:71,12 +DA:73,35 +DA:74,1 +DA:79,2 +DA:84,6 +DA:85,0 +DA:86,3 +DA:89,3 +DA:90,3 +DA:91,3 +DA:97,3 +DA:98,6 +DA:103,3 +DA:104,3 +DA:123,3 +DA:124,5 +DA:127,6 +DA:128,4 +DA:129,2 +LF:37 +LH:35 +end_of_record +TN: +SF:contracts/alliance-nft-collection/src/contract/migrate.rs +FN:15,migrate +FN:19,try_migrate +FN:40,migrate_to_1_1_0 +FNF:3 +FNDA:1,migrate +FNDA:1,try_migrate +FNDA:1,migrate_to_1_1_0 +DA:15,1 +DA:16,1 +DA:19,1 +DA:20,1 +DA:21,2 +DA:23,1 +DA:24,2 +DA:25,1 +DA:28,2 +DA:29,1 +DA:30,2 +DA:31,0 +DA:35,3 +DA:37,1 +DA:40,1 +DA:47,2 +DA:49,1 +DA:50,1 +DA:51,2 +DA:52,2 +DA:53,2 +DA:54,2 +DA:55,1 +DA:57,2 +DA:63,2 +DA:64,2 +DA:65,2 +DA:67,1 +DA:70,2 +DA:72,2 +DA:74,1 +DA:81,7 +DA:83,1 +DA:84,1 +DA:85,1 +DA:86,1 +DA:87,1 +LF:37 +LH:36 +end_of_record +TN: +SF:contracts/alliance-nft-collection/src/contract/query.rs +FN:13,query +FN:39,query_config +FN:45,query_rewards +FN:74,query_token_info +FN:108,query_nft_info +FN:120,query_all_nft_info +FN:149,humanize_approval +FNF:7 +FNDA:1,query +FNDA:1,query_config +FNDA:1,query_rewards +FNDA:1,query_token_info +FNDA:1,query_nft_info +FNDA:0,query_all_nft_info +FNDA:0,humanize_approval +DA:13,1 +DA:14,1 +DA:15,1 +DA:16,2 +DA:17,1 +DA:18,2 +DA:20,0 +DA:25,0 +DA:26,0 +DA:31,1 +DA:32,2 +DA:35,1 +DA:39,1 +DA:40,1 +DA:42,1 +DA:45,1 +DA:46,2 +DA:48,2 +DA:49,2 +DA:50,2 +DA:52,1 +DA:54,1 +DA:56,1 +DA:57,0 +DA:58,2 +DA:60,2 +DA:61,1 +DA:62,1 +DA:63,0 +DA:66,2 +DA:67,1 +DA:68,2 +DA:71,1 +DA:74,1 +DA:79,2 +DA:80,1 +DA:81,1 +DA:83,1 +DA:84,2 +DA:86,2 +DA:87,2 +DA:88,2 +DA:89,1 +DA:90,2 +DA:93,1 +DA:94,1 +DA:95,1 +DA:96,1 +DA:97,1 +DA:99,1 +DA:100,1 +DA:101,1 +DA:102,1 +DA:104,1 +DA:105,1 +DA:108,1 +DA:113,1 +DA:114,1 +DA:115,1 +DA:116,1 +DA:120,0 +DA:127,0 +DA:128,0 +DA:129,0 +DA:130,0 +DA:133,0 +DA:137,0 +DA:138,0 +DA:139,0 +DA:140,0 +DA:142,0 +DA:143,0 +DA:144,0 +DA:149,0 +DA:151,0 +DA:152,0 +LF:76 +LH:55 +end_of_record +TN: +SF:contracts/alliance-nft-collection/src/contract/reply.rs +FN:10,reply +FNF:1 +FNDA:1,reply +DA:10,1 +DA:11,1 +DA:12,4 +DA:13,0 +DA:15,0 +DA:17,0 +LF:6 +LH:3 +end_of_record +TN: +SF:contracts/alliance-nft-collection/src/state.rs +FN:11,upsert_val +FN:16,{closure#0} +FN:23,reduce_val_stake +FN:28,{closure#0} +FNF:4 +FNDA:0,upsert_val +FNDA:0,{closure#0} +FNDA:0,reduce_val_stake +FNDA:0,{closure#0} +DA:11,0 +DA:16,0 +DA:17,0 +DA:18,0 +DA:20,0 +DA:23,0 +DA:28,0 +DA:29,0 +DA:30,0 +DA:31,0 +DA:33,0 +DA:36,0 +DA:38,0 +LF:13 +LH:0 +end_of_record +TN: +SF:contracts/alliance-nft-minter/src/contract/execute.rs +FN:12,execute +FN:33,try_remove_token +FN:60,try_append_nft_metadata +FN:95,try_mint +FN:135,try_send_to_dao_treasury +FN:214,try_change_dao_treasury_address +FN:231,try_change_owner +FNF:7 +FNDA:1,execute +FNDA:1,try_remove_token +FNDA:8,try_append_nft_metadata +FNDA:2,try_mint +FNDA:2,try_send_to_dao_treasury +FNDA:1,try_change_dao_treasury_address +FNDA:1,try_change_owner +DA:12,1 +DA:18,4 +DA:19,8 +DA:20,9 +DA:22,3 +DA:23,2 +DA:24,3 +DA:25,1 +DA:26,2 +DA:28,2 +DA:33,1 +DA:38,2 +DA:39,3 +DA:41,2 +DA:42,1 +DA:43,2 +DA:44,1 +DA:45,1 +DA:49,4 +DA:50,1 +DA:51,2 +DA:60,8 +DA:65,16 +DA:66,17 +DA:68,7 +DA:69,12 +DA:70,14 +DA:71,1 +DA:73,12 +DA:74,7 +DA:77,12 +DA:78,6 +DA:79,6 +DA:82,19 +DA:83,6 +DA:84,8 +DA:95,2 +DA:96,4 +DA:97,4 +DA:99,1 +DA:100,1 +DA:101,0 +DA:104,4 +DA:105,2 +DA:108,1 +DA:109,2 +DA:115,1 +DA:118,2 +DA:119,1 +DA:120,2 +DA:121,1 +DA:124,3 +DA:126,1 +DA:135,2 +DA:140,4 +DA:141,5 +DA:142,1 +DA:143,1 +DA:144,0 +DA:146,1 +DA:147,1 +DA:148,0 +DA:151,2 +DA:152,1 +DA:153,0 +DA:155,2 +DA:156,1 +DA:159,1 +DA:160,1 +DA:161,1 +DA:166,4 +DA:167,1 +DA:168,2 +DA:169,1 +DA:170,0 +DA:172,2 +DA:173,2 +DA:175,1 +DA:176,1 +DA:177,1 +DA:178,1 +DA:179,1 +DA:180,1 +DA:181,1 +DA:184,1 +DA:187,1 +DA:188,1 +DA:190,1 +DA:194,1 +DA:195,1 +DA:196,2 +DA:197,4 +DA:200,2 +DA:201,2 +DA:204,3 +DA:205,1 +DA:206,2 +DA:207,1 +DA:214,1 +DA:219,2 +DA:220,2 +DA:222,3 +DA:223,1 +DA:224,1 +DA:226,3 +DA:228,1 +DA:231,1 +DA:236,2 +DA:237,2 +DA:238,1 +DA:239,3 +DA:240,2 +DA:241,1 +DA:243,1 +DA:244,1 +DA:245,0 +DA:249,1 +DA:250,2 +DA:251,1 +DA:254,4 +DA:255,3 +DA:256,1 +DA:257,3 +DA:259,1 +LF:124 +LH:118 +end_of_record +TN: +SF:contracts/alliance-nft-minter/src/contract/instantiate.rs +FN:18,instantiate +FN:81,reply_on_instantiate +FN:87,{closure#0} +FN:94,{closure#2} +FNF:4 +FNDA:15,instantiate +FNDA:7,reply_on_instantiate +FNDA:14,{closure#0} +FNDA:14,{closure#2} +DA:18,15 +DA:24,15 +DA:25,1 +DA:28,11 +DA:29,3 +DA:30,14 +DA:31,8 +DA:33,0 +DA:36,8 +DA:37,0 +DA:39,11 +DA:41,18 +DA:42,4 +DA:43,9 +DA:44,6 +DA:45,4 +DA:46,5 +DA:47,9 +DA:48,5 +DA:54,13 +DA:55,13 +DA:56,26 +DA:69,12 +DA:70,12 +DA:72,12 +DA:74,39 +DA:75,7 +DA:76,16 +DA:77,9 +DA:78,9 +DA:81,7 +DA:82,7 +DA:84,14 +DA:87,14 +DA:88,0 +DA:91,14 +DA:94,14 +DA:95,0 +DA:98,14 +DA:99,33 +DA:100,14 +DA:101,1 +DA:104,1 +LF:43 +LH:39 +end_of_record +TN: +SF:contracts/alliance-nft-minter/src/contract/migrate.rs +FN:10,migrate +FN:14,try_migrate +FNF:2 +FNDA:0,migrate +FNDA:0,try_migrate +DA:10,0 +DA:11,0 +DA:14,0 +DA:15,0 +DA:16,0 +DA:17,0 +DA:19,0 +DA:21,0 +DA:23,0 +DA:24,0 +DA:25,0 +DA:26,0 +DA:27,0 +DA:29,0 +DA:31,0 +DA:35,0 +LF:16 +LH:0 +end_of_record +TN: +SF:contracts/alliance-nft-minter/src/contract/query.rs +FN:9,query +FN:17,query_config +FN:23,query_stats +FN:29,query_nft_data +FNF:4 +FNDA:1,query +FNDA:1,query_config +FNDA:1,query_stats +FNDA:0,query_nft_data +DA:9,1 +DA:10,1 +DA:11,2 +DA:12,2 +DA:13,0 +DA:17,1 +DA:18,1 +DA:20,1 +DA:23,1 +DA:24,1 +DA:26,1 +DA:29,0 +DA:30,0 +DA:32,0 +LF:14 +LH:10 +end_of_record +TN: +SF:contracts/alliance-nft-minter/src/contract/reply.rs +FN:9,reply +FNF:1 +FNDA:13,reply +DA:9,13 +DA:10,1 +DA:11,20 +DA:12,0 +LF:4 +LH:3 +end_of_record +TN: +SF:packages/alliance-nft-packages/src/eris.rs +FN:11,validate_dao_treasury_share +FN:19,validate_whitelisted_assets +FN:47,dedupe_assetinfos +FN:113,with_balance +FNF:4 +FNDA:12,validate_dao_treasury_share +FNDA:1,validate_whitelisted_assets +FNDA:1,dedupe_assetinfos +FNDA:1,with_balance +DA:11,12 +DA:12,24 +DA:13,1 +DA:15,1 +DA:19,1 +DA:24,4 +DA:26,4 +DA:27,2 +DA:28,1 +DA:29,1 +DA:33,4 +DA:34,2 +DA:35,0 +DA:37,4 +DA:38,1 +DA:41,2 +DA:47,1 +DA:48,1 +DA:50,3 +DA:87,1 +DA:93,2 +DA:94,1 +DA:95,2 +DA:96,2 +DA:100,1 +DA:101,1 +DA:103,1 +DA:113,1 +DA:114,1 +DA:115,0 +DA:116,2 +LF:31 +LH:29 +end_of_record +TN: +SF:packages/alliance-nft-packages/src/execute.rs +FN:89,from +FNF:1 +FNDA:0,from +DA:89,0 +DA:90,0 +DA:91,0 +DA:98,0 +DA:107,0 +DA:116,0 +DA:119,0 +DA:123,0 +LF:8 +LH:0 +end_of_record +TN: +SF:packages/alliance-nft-packages/src/instantiate.rs +FN:20,from +FNF:1 +FNDA:1,from +DA:20,1 +DA:22,12 +DA:23,1 +DA:24,12 +LF:4 +LH:4 +end_of_record +TN: +SF:packages/alliance-nft-packages/src/query.rs +FN:146,from +FNF:1 +FNDA:1,from +DA:146,1 +DA:147,1 +DA:148,0 +DA:155,0 +DA:164,0 +DA:171,0 +DA:185,0 +DA:192,0 +DA:201,0 +DA:205,0 +LF:10 +LH:2 +end_of_record +TN: +SF:packages/alliance-nft-packages/src/state.rs +FNF:0 +DA:76,0 +DA:91,1 +DA:92,2 +DA:93,1 +DA:95,2 +DA:100,2 +DA:101,2 +DA:102,1 +DA:103,1 +DA:104,1 +DA:105,1 +DA:109,1 +DA:113,2 +DA:114,2 +DA:115,1 +DA:118,1 +LF:16 +LH:15 +end_of_record diff --git a/packages/alliance-nft-packages/Cargo.toml b/packages/alliance-nft-packages/Cargo.toml index a8505d4..34ea4f9 100644 --- a/packages/alliance-nft-packages/Cargo.toml +++ b/packages/alliance-nft-packages/Cargo.toml @@ -27,6 +27,7 @@ cw721-base = { workspace = true } schemars = { workspace = true } cw-storage-plus = { workspace = true } cw-utils = { workspace = true } +cw-asset = { workspace = true } serde = { workspace = true } thiserror = { workspace = true } terra-proto-rs = { workspace = true } \ No newline at end of file diff --git a/packages/alliance-nft-packages/src/eris.rs b/packages/alliance-nft-packages/src/eris.rs new file mode 100644 index 0000000..63ed18b --- /dev/null +++ b/packages/alliance-nft-packages/src/eris.rs @@ -0,0 +1,120 @@ +use std::collections::HashSet; + +use crate::{errors::ContractError, state::ALLOWED_DENOM}; +use cosmwasm_schema::cw_serde; +use cosmwasm_std::{ + coin, to_json_binary, Addr, Coin, CosmosMsg, Decimal, DepsMut, QuerierWrapper, StdResult, + Uint128, WasmMsg, +}; +use cw_asset::{Asset, AssetInfo, AssetInfoUnchecked}; + +pub fn validate_dao_treasury_share(share: Decimal) -> Result { + if share > Decimal::from_ratio(20u128, 100u128) { + Err(ContractError::InvalidDaoTreasuryShare {}) + } else { + Ok(share) + } +} + +pub fn validate_whitelisted_assets( + deps: &DepsMut, + lst_asset_info: &AssetInfo, + assets: Vec, +) -> Result, ContractError> { + assets + .iter() + .map(|a| -> Result { + if let cw_asset::AssetInfoBase::Native(native) = a { + if native == ALLOWED_DENOM { + return Err(ContractError::InvalidWhitelistedAssetInfo {}); + } + } + + let checked_assetinfo = a + .check(deps.api, None) + .map_err(ContractError::FromAssetError)?; + + if checked_assetinfo == *lst_asset_info { + return Err(ContractError::InvalidWhitelistedAssetInfo {}); + } + + Ok(checked_assetinfo) + }) + .collect::, ContractError>>() +} + +/// Dedupes a Vector of strings using a hashset. +pub fn dedupe_assetinfos(asset_infos: &mut Vec) { + let mut set = HashSet::new(); + + asset_infos.retain(|x| set.insert(x.clone())); +} + +#[cw_serde] +pub enum ExecuteMsg { + /// Bond specified amount of Luna + Bond { receiver: Option }, +} + +#[cw_serde] +pub enum QueryMsg { + State {}, +} + +#[cw_serde] +pub struct StateResponse { + /// Total supply to the Stake token + pub total_ustake: Uint128, + /// Total amount of uluna staked (bonded) + pub total_uluna: Uint128, + /// The exchange rate between ustake and uluna, in terms of uluna per ustake + pub exchange_rate: Decimal, + /// Staking rewards currently held by the contract that are ready to be reinvested + pub unlocked_coins: Vec, + // Amount of uluna currently unbonding + pub unbonding: Uint128, + // Amount of uluna currently available as balance of the contract + pub available: Uint128, + // Total amount of uluna within the contract (bonded + unbonding + available) + pub tvl_uluna: Uint128, +} + +#[cw_serde] +pub struct Hub(pub Addr); + +impl Hub { + /// executes a bond message to the staking Hub + pub fn bond_msg( + &self, + denom: impl Into, + amount: u128, + receiver: Option, + ) -> StdResult { + Ok(CosmosMsg::Wasm(WasmMsg::Execute { + contract_addr: self.0.to_string(), + msg: to_json_binary(&ExecuteMsg::Bond { receiver })?, + funds: vec![coin(amount, denom)], + })) + } + + pub fn query_state(&self, querier: &QuerierWrapper) -> StdResult { + let state: StateResponse = + querier.query_wasm_smart(self.0.to_string(), &QueryMsg::State {})?; + Ok(state) + } +} + +pub trait AssetInfoExt { + /// simplifies converting an AssetInfo to an Asset with balance + fn with_balance(self, balance: Uint128) -> Asset; +} + +impl AssetInfoExt for AssetInfo { + fn with_balance(self, amount: Uint128) -> Asset { + match self { + cw_asset::AssetInfoBase::Native(denom) => Asset::native(denom, amount), + cw_asset::AssetInfoBase::Cw20(contract_addr) => Asset::cw20(contract_addr, amount), + _ => todo!(), + } + } +} diff --git a/packages/alliance-nft-packages/src/errors.rs b/packages/alliance-nft-packages/src/errors.rs index 6ced21f..cc6d52a 100644 --- a/packages/alliance-nft-packages/src/errors.rs +++ b/packages/alliance-nft-packages/src/errors.rs @@ -1,8 +1,9 @@ -use cosmwasm_std::{Addr, StdError, Timestamp}; +use cosmwasm_std::{Addr, OverflowError, StdError, Timestamp}; use cw721_base::ContractError as CW721BaseError; +use cw_asset::AssetError; use thiserror::Error; -#[derive(Error, Debug)] +#[derive(Error, Debug, PartialEq)] pub enum ContractError { #[error("{0}")] Std(#[from] StdError), @@ -13,6 +14,12 @@ pub enum ContractError { #[error("{0}")] FromContractError(#[from] CW721BaseError), + #[error("{0}")] + FromAssetError(#[from] AssetError), + + #[error("{0}")] + FromOverflowError(#[from] OverflowError), + #[error("Invalid reply id {0}")] InvalidReplyId(u64), @@ -37,6 +44,12 @@ pub enum ContractError { #[error("Invalid DAO treasury address")] InvalidDaoTreasuryAddress {}, + #[error("Invalid DAO treasury share. Must be less than or equal 20%")] + InvalidDaoTreasuryShare {}, + + #[error("Cannot whitelist the main asset or LST")] + InvalidWhitelistedAssetInfo {}, + #[error("Minting period starts at {0} and ends at {1}. Current time is {2}")] OutOfMintingPeriod(Timestamp, Timestamp, Timestamp), @@ -48,14 +61,16 @@ pub enum ContractError { #[error("No available NFTs to be minted")] NoAvailableNfts {}, - + #[error("DAO Address must be set")] - DaoAddressNotSet{}, + DaoAddressNotSet {}, #[error("Dao treasury address must be set")] - DaoTreasuryAddressNotSet{}, - + DaoTreasuryAddressNotSet {}, + #[error("Nft collection address must be set")] - NftCollectionAddressNotSet{}, + NftCollectionAddressNotSet {}, + #[error("Migration data must be set: {0}")] + MissingMigrationData(String), } diff --git a/packages/alliance-nft-packages/src/execute.rs b/packages/alliance-nft-packages/src/execute.rs index 9be35d9..6f3f01a 100644 --- a/packages/alliance-nft-packages/src/execute.rs +++ b/packages/alliance-nft-packages/src/execute.rs @@ -1,8 +1,9 @@ use std::collections::HashMap; use cosmwasm_schema::cw_serde; -use cosmwasm_std::{Binary, Empty, Uint128}; +use cosmwasm_std::{Binary, Decimal, Empty, Uint128}; use cw721_base::ExecuteMsg as CW721ExecuteMsg; +use cw_asset::AssetInfoUnchecked; use cw_utils::Expiration; use crate::state::MinterExtension; @@ -30,8 +31,14 @@ pub enum ExecuteCollectionMsg { AllianceUndelegate(AllianceUndelegateMsg), AllianceRedelegate(AllianceRedelegateMsg), AllianceClaimRewards {}, - UpdateRewardsCallback {}, + + /// This callback will be used to check how many LUNA entered the contract to be staked in the Amplifier + StakeRewardsCallback {}, + /// This callback will after the staking be called to check received funds + UpdateRewardsCallback(UpdateRewardsCallbackMsg), + ChangeOwner(String), + UpdateConfig(UpdateConfigMsg), // Claim the accumulated rewards and send them to the owner // while the NFT is broken it will not accumulate rewards @@ -118,6 +125,19 @@ impl From for CW721ExecuteMsg { } } +#[cw_serde] +pub struct UpdateRewardsCallbackMsg { + pub previous_lst_balance: Uint128, +} + +#[cw_serde] +pub struct UpdateConfigMsg { + pub dao_treasury_address: Option, + pub dao_treasury_share: Option, + pub set_whitelisted_reward_assets: Option>, + pub add_whitelisted_reward_assets: Option>, +} + #[cw_serde] pub struct AllianceDelegation { pub validator: String, diff --git a/packages/alliance-nft-packages/src/instantiate.rs b/packages/alliance-nft-packages/src/instantiate.rs index f5c36c5..ca1905e 100644 --- a/packages/alliance-nft-packages/src/instantiate.rs +++ b/packages/alliance-nft-packages/src/instantiate.rs @@ -1,6 +1,7 @@ use cosmwasm_schema::cw_serde; -use cosmwasm_std::{Addr, Timestamp}; +use cosmwasm_std::{Addr, Decimal, Timestamp}; use cw721_base::InstantiateMsg as CW721InstantiateMsg; +use cw_asset::AssetInfoUnchecked; #[cw_serde] pub struct InstantiateCollectionMsg { @@ -8,6 +9,11 @@ pub struct InstantiateCollectionMsg { pub symbol: String, pub minter: String, pub owner: Addr, + + pub dao_treasury_address: String, + pub dao_treasury_share: Decimal, + pub lst_hub_address: String, + pub lst_asset_info: AssetInfoUnchecked, } impl From for CW721InstantiateMsg { @@ -26,4 +32,8 @@ pub struct InstantiateMinterMsg { pub nft_collection_code_id: u64, pub mint_start_time: Timestamp, pub mint_end_time: Timestamp, + + pub dao_treasury_share: Decimal, + pub lst_hub_address: String, + pub lst_asset_info: AssetInfoUnchecked, } diff --git a/packages/alliance-nft-packages/src/lib.rs b/packages/alliance-nft-packages/src/lib.rs index 8d9fca8..e142e73 100644 --- a/packages/alliance-nft-packages/src/lib.rs +++ b/packages/alliance-nft-packages/src/lib.rs @@ -1,3 +1,4 @@ +pub mod eris; pub mod errors; pub mod execute; pub mod instantiate; diff --git a/packages/alliance-nft-packages/src/migrate.rs b/packages/alliance-nft-packages/src/migrate.rs index c29801b..e7aead0 100644 --- a/packages/alliance-nft-packages/src/migrate.rs +++ b/packages/alliance-nft-packages/src/migrate.rs @@ -1,6 +1,20 @@ use cosmwasm_schema::cw_serde; +use cosmwasm_std::Decimal; +use cw_asset::AssetInfoUnchecked; #[cw_serde] pub struct MigrateMsg { pub version: String, + + pub nft_collection_code_id: Option, + + pub version110_data: Option, +} + +#[cw_serde] +pub struct Version110MigrateData { + pub dao_treasury_address: String, + pub dao_treasury_share: Decimal, + pub lst_hub: String, + pub lst_asset_info: AssetInfoUnchecked, } diff --git a/packages/alliance-nft-packages/src/query.rs b/packages/alliance-nft-packages/src/query.rs index c046898..472fa00 100644 --- a/packages/alliance-nft-packages/src/query.rs +++ b/packages/alliance-nft-packages/src/query.rs @@ -1,5 +1,6 @@ +#[allow(unused_imports)] use super::Extension; -use crate::state::{Config as ConfigRes, MinterConfig, MinterStats, MinterExtension}; +use crate::state::{Config as ConfigRes, MinterConfig, MinterExtension, MinterStats}; use cosmwasm_schema::{cw_serde, QueryResponses}; use cosmwasm_std::Empty; use cw721::{ @@ -7,6 +8,7 @@ use cw721::{ NumTokensResponse, OperatorsResponse, OwnerOfResponse, TokensResponse, }; use cw721_base::QueryMsg as CW721QueryMsg; +use cw_asset::Asset; #[cw_serde] #[derive(QueryResponses)] @@ -14,6 +16,9 @@ pub enum QueryCollectionMsg { #[returns(ConfigRes)] Config {}, + #[returns(RewardsResponse)] + Rewards { token_id: String }, + /// With MetaData Extension. /// Returns metadata about one particular token, /// based on *ERC721 Metadata JSON Schema* @@ -127,6 +132,11 @@ pub enum QueryCollectionMsg { Minter {}, } +#[cw_serde] +pub struct RewardsResponse { + pub rewards: Vec, +} + #[cw_serde] pub struct MinterResponse { pub minter: String, diff --git a/packages/alliance-nft-packages/src/state.rs b/packages/alliance-nft-packages/src/state.rs index c59a0af..ca1d20d 100644 --- a/packages/alliance-nft-packages/src/state.rs +++ b/packages/alliance-nft-packages/src/state.rs @@ -1,14 +1,15 @@ use cosmwasm_schema::cw_serde; -use cosmwasm_std::{Addr, Response, Timestamp}; +use cosmwasm_std::{Addr, Decimal, Response, Timestamp}; +use cw_asset::AssetInfo; -use crate::{errors::ContractError, Extension}; +use crate::{eris::Hub, errors::ContractError, Extension}; // The NFT collection may be able to accrual rewards // in different tokens if the take rate of an Alliance // is positive. But for now, breaking an NFT will allow // claiming and accounting rewards only for Luna Tokens. // -// Whatsoever, the DAO will be able to use these other +// Whatsoever, the DAO will be able to use these other // rewards to do anything they want collectively pub const ALLOWED_DENOM: &str = "uluna"; @@ -33,10 +34,33 @@ pub struct Metadata { pub youtube_url: Option, } +#[cw_serde] +pub struct ConfigV100 { + pub owner: Addr, + + /// this is the virtual staking token factory/.../ALLY + pub asset_denom: String, +} + #[cw_serde] pub struct Config { pub owner: Addr, + + /// this is the virtual staking token factory/.../ALLY pub asset_denom: String, + + /// Treasury address of the DAO + pub dao_treasury_address: Addr, + /// Specifies how much of the rewards should be sent to the DAO treasury address + pub dao_treasury_share: Decimal, + + /// Contract of ERIS Amplifier for uluna + pub lst_hub_address: Hub, + /// Contract of CW20 ampLUNA + pub lst_asset_info: AssetInfo, + + /// specifies all reward assets that will be queried and returned if available. + pub whitelisted_reward_assets: Vec, } #[cw_serde] @@ -58,8 +82,8 @@ impl MinterConfig { owner, mint_start_time, mint_end_time, - dao_treasury_address : None, - nft_collection_address : None, + dao_treasury_address: None, + nft_collection_address: None, } } @@ -96,24 +120,16 @@ impl MinterConfig { } #[cw_serde] +#[derive(Default)] pub struct MinterStats { pub available_nfts: i16, pub minted_nfts: i16, } -impl MinterStats { - pub fn default() -> MinterStats { - MinterStats { - available_nfts: 0, - minted_nfts: 0, - } - } -} - // Model necessary because the nfts are sorted by the token_id // the ones that scored more points should have a lower token_id ... #[cw_serde] pub struct MinterExtension { pub token_id: String, pub extension: Extension, -} \ No newline at end of file +} diff --git a/scripts/build_release.sh b/scripts/build_release.sh new file mode 100644 index 0000000..34d6f0c --- /dev/null +++ b/scripts/build_release.sh @@ -0,0 +1,19 @@ +#!/usr/bin/env bash + +# Running coverage +# docker run --security-opt seccomp=unconfined -v "/${PWD}:/volume" xd009642/tarpaulin sh -c "cargo tarpaulin --out lcov" + +set -e +set -o pipefail + +projectPath=$(dirname `pwd`) +folderName=$(basename $(dirname `pwd`)) + +mkdir -p "../../$folderName-cache" +mkdir -p "../../$folderName-cache/target" +mkdir -p "../../$folderName-cache/registry" + +docker run --env $1 --rm -v "/$projectPath":/code \ + --mount type=bind,source=/$projectPath-cache/target,target=/target \ + --mount type=bind,source=/$projectPath-cache/registry,target=/usr/local/cargo/registry \ + cosmwasm/workspace-optimizer:0.15.1 \ No newline at end of file diff --git a/scripts/package-lock.json b/scripts/package-lock.json index 33304aa..78087d9 100644 --- a/scripts/package-lock.json +++ b/scripts/package-lock.json @@ -7,10 +7,12 @@ "dependencies": { "@terra-money/feather.js": "2.0.0-beta.6", "@types/lodash": "^4.14.196", + "@types/promptly": "^3.0.5", "csv-parser": "^3.0.0", "dotenv": "^16.1.4", "lodash": "^4.17.21", "moment": "^2.29.4", + "promptly": "^3.2.0", "ts-node": "^10.9.1" } }, @@ -899,6 +901,14 @@ "version": "10.12.18", "license": "MIT" }, + "node_modules/@types/promptly": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@types/promptly/-/promptly-3.0.5.tgz", + "integrity": "sha512-LbcnaRi5mQ/6neVJ+re9Zps5RT/HaiYFvdER+9eHUNSl3pHiIay4+8J6xHmhstkOZpfMuMf0AJrqvQM+JJ10lw==", + "dependencies": { + "@types/node": "*" + } + }, "node_modules/acorn": { "version": "8.8.2", "license": "MIT", @@ -1845,6 +1855,11 @@ "node": "*" } }, + "node_modules/mute-stream": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.8.tgz", + "integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==" + }, "node_modules/nan": { "version": "2.17.0", "license": "MIT" @@ -1943,6 +1958,14 @@ "node": ">=0.12" } }, + "node_modules/promptly": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/promptly/-/promptly-3.2.0.tgz", + "integrity": "sha512-WnR9obtgW+rG4oUV3hSnNGl1pHm3V1H/qD9iJBumGSmVsSC5HpZOLuu8qdMb6yCItGfT7dcRszejr/5P3i9Pug==", + "dependencies": { + "read": "^1.0.4" + } + }, "node_modules/protobufjs": { "version": "6.11.3", "hasInstallScript": true, @@ -2001,6 +2024,17 @@ "safe-buffer": "^5.1.0" } }, + "node_modules/read": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/read/-/read-1.0.7.tgz", + "integrity": "sha512-rSOKNYUmaxy0om1BNjMN4ezNT6VKK+2xF4GBhc81mkH7L60i6dp8qPYrkndNLT3QPphoII3maL9PVC9XmhHwVQ==", + "dependencies": { + "mute-stream": "~0.0.4" + }, + "engines": { + "node": ">=0.8" + } + }, "node_modules/readable-stream": { "version": "3.6.2", "license": "MIT", @@ -2815,6 +2849,14 @@ "@types/node": { "version": "10.12.18" }, + "@types/promptly": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@types/promptly/-/promptly-3.0.5.tgz", + "integrity": "sha512-LbcnaRi5mQ/6neVJ+re9Zps5RT/HaiYFvdER+9eHUNSl3pHiIay4+8J6xHmhstkOZpfMuMf0AJrqvQM+JJ10lw==", + "requires": { + "@types/node": "*" + } + }, "acorn": { "version": "8.8.2" }, @@ -3489,6 +3531,11 @@ "resolved": "https://registry.npmjs.org/moment/-/moment-2.29.4.tgz", "integrity": "sha512-5LC9SOxjSc2HF6vO2CyuTDNivEdoz2IvyJJGj6X8DJ0eFyfszE0QiEd+iXmBvUP3WHxSjFH/vIsA0EN00cgr8w==" }, + "mute-stream": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.8.tgz", + "integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==" + }, "nan": { "version": "2.17.0" }, @@ -3556,6 +3603,14 @@ "sha.js": "^2.4.8" } }, + "promptly": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/promptly/-/promptly-3.2.0.tgz", + "integrity": "sha512-WnR9obtgW+rG4oUV3hSnNGl1pHm3V1H/qD9iJBumGSmVsSC5HpZOLuu8qdMb6yCItGfT7dcRszejr/5P3i9Pug==", + "requires": { + "read": "^1.0.4" + } + }, "protobufjs": { "version": "6.11.3", "requires": { @@ -3609,6 +3664,14 @@ "safe-buffer": "^5.1.0" } }, + "read": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/read/-/read-1.0.7.tgz", + "integrity": "sha512-rSOKNYUmaxy0om1BNjMN4ezNT6VKK+2xF4GBhc81mkH7L60i6dp8qPYrkndNLT3QPphoII3maL9PVC9XmhHwVQ==", + "requires": { + "mute-stream": "~0.0.4" + } + }, "readable-stream": { "version": "3.6.2", "requires": { diff --git a/scripts/package.json b/scripts/package.json index 1ca731b..2b07a98 100644 --- a/scripts/package.json +++ b/scripts/package.json @@ -1,11 +1,16 @@ { + "scripts": { + "release": "bash build_release.sh" + }, "dependencies": { "@terra-money/feather.js": "2.0.0-beta.6", "@types/lodash": "^4.14.196", + "@types/promptly": "^3.0.5", "csv-parser": "^3.0.0", "dotenv": "^16.1.4", "lodash": "^4.17.21", "moment": "^2.29.4", + "promptly": "^3.2.0", "ts-node": "^10.9.1" } } diff --git a/scripts/testing-migration-110.ts b/scripts/testing-migration-110.ts new file mode 100644 index 0000000..2b5c0b5 --- /dev/null +++ b/scripts/testing-migration-110.ts @@ -0,0 +1,212 @@ +import * as dotenv from "dotenv"; +import { + MnemonicKey, + MsgStoreCode, + LCDClient, + MsgMigrateCode, + MsgMigrateContract, + MsgExecuteContract, +} from "@terra-money/feather.js"; +import * as fs from "fs"; +import * as promptly from "promptly"; + +dotenv.config(); + +// ts-node scripts/testing-migration-110 + +try { + (async () => { + // LCD Configuration + const lcdConfig = { + "phoenix-1": { + lcd: "https://cradle-manager.ec1-prod.newmetric.xyz/cradle/proxy/6a619c39-c3e8-4e57-9df1-b20560dc4e50", + chainID: "phoenix-1", + gasAdjustment: 1.5, + gasPrices: { uluna: 0.015 }, + prefix: "terra", + }, + }; + + // Initialize LCD Client + const lcd = new LCDClient(lcdConfig); + + // Get all information from the deployer wallet + const mk = new MnemonicKey({ mnemonic: process.env.MNEMONIC }); + const wallet = lcd.wallet(mk); + const accAddress = wallet.key.accAddress("terra"); + console.log(`Wallet address: ${accAddress}`); + + let stakers = [ + "terra1yct7ls8kw3x2ucm66rxg7mgtyvw7kr5v4t4xsx", + "terra1ud0pnytt8nsmrupl9md40xpqp4w5ea3w4se5ht", + "terra13qjxhnw98lm36rxc9yjakkmulhpv7zctdc5zlz", + "terra1hr8zsfpch47qygc96c8e6rzkd2t7mafqx77ulw", + ]; + + // dao gov terra1pzhcmjz57s46kp2fldut5xe83vxjhmulj9w8rrf3dlm0gehxqp7sq3n5dr + // dao treasury terra1g0mfrpswewteaf9ky4rlj09wh5njp6u9xxk94uszplw4qz2f9mzq3k27fm + // alliance minter terra1m3ye6dl6s25el4xd8adg9lnquz88az9lur2ujztj9pfmzdyfz3xsm699r3 + let alliance_collection_addr = + "terra1phr9fngjv7a8an4dhmhd0u0f98wazxfnzccqtyheq4zqrrp4fpuqw3apw9"; + + let dao_gov_addr = + "terra1pzhcmjz57s46kp2fldut5xe83vxjhmulj9w8rrf3dlm0gehxqp7sq3n5dr"; + + let action = await promptly.prompt( + "create proposal (c), execute proposal (x), break NFT (b), claim rewards (r)" + ); + + if (action === "c") { + // Create the message and broadcast the transaction on chain + let tx = await wallet.createTx({ + msgs: [ + new MsgExecuteContract(stakers[0], dao_gov_addr, { + create_proposal: { + title: "Test", + description: "test", + proposal_actions: [ + { + execute_treasury_msgs: { + action_type: "execute", + msgs: [ + JSON.stringify({ + wasm: { + migrate: { + contract_addr: + "terra1m3ye6dl6s25el4xd8adg9lnquz88az9lur2ujztj9pfmzdyfz3xsm699r3", + new_code_id: 2717, + msg: Buffer.from( + JSON.stringify({ + version: "1.1.0", + nft_collection_code_id: 2718, + version110_data: { + dao_treasury_address: + "terra1g0mfrpswewteaf9ky4rlj09wh5njp6u9xxk94uszplw4qz2f9mzq3k27fm", + dao_treasury_share: "0.1", + lst_hub: + "terra10788fkzah89xrdm27zkj5yvhj9x3494lxawzm5qq3vvxcqz2yzaqyd3enk", + lst_asset_info: { + cw20: "terra1ecgazyd0waaj3g7l9cmy5gulhxkps2gmxu9ghducvuypjq68mq2s5lvsct", + }, + }, + }) + ).toString("base64"), + }, + }, + }), + ], + remote_treasury_target: null, + }, + }, + ], + deposit_owner: stakers[0], + }, + }), + + ...stakers.map( + (a) => + new MsgExecuteContract(a, dao_gov_addr, { + cast_vote: { + proposal_id: 47, + outcome: "yes", + }, + }) + ), + ], + memo: "Alliance NFT Migration to 1.1.0", + chainID: "phoenix-1", + }); + + console.log("TX", JSON.stringify(tx)); + + let result = await lcd.tx.broadcastSync(tx, "phoenix-1"); + console.log(`Transaction executed with hash ${result.txhash}`); + } + + if (action === "x") { + let txExecute = await wallet.createTx({ + msgs: [ + new MsgExecuteContract(stakers[0], dao_gov_addr, { + execute_proposal: { + proposal_id: 47, + }, + }), + ], + memo: "Alliance NFT Migration to 1.1.0", + chainID: "phoenix-1", + }); + + console.log("TX EXECUTE", JSON.stringify(txExecute)); + + let resultExecute = await lcd.tx.broadcastSync(txExecute, "phoenix-1"); + console.log( + `TX EXECUTE Transaction executed with hash ${resultExecute.txhash}` + ); + + let txInfo = await lcd.tx.txInfo(resultExecute.txhash, "phoenix-1"); + + console.log("raw", txInfo.raw_log); + console.log("logs", txInfo.logs); + } + + if (action === "b") { + // "1116", + // "1416", + // "185", + // "2561", + // "3049", + // "3050", + // "3070", + // "3491", + // "371", + // "4035" + + let txExecute = await wallet.createTx({ + msgs: [ + new MsgExecuteContract(stakers[0], alliance_collection_addr, { + break_nft: "1416", + }), + ], + memo: "Alliance NFT Break NFT", + chainID: "phoenix-1", + }); + + console.log("TX BREAK", JSON.stringify(txExecute)); + + let resultExecute = await lcd.tx.broadcastSync(txExecute, "phoenix-1"); + console.log( + `TX BREAK Transaction executed with hash ${resultExecute.txhash}` + ); + + let txInfo = await lcd.tx.txInfo(resultExecute.txhash, "phoenix-1"); + + console.log("raw", txInfo.raw_log); + console.log("logs", txInfo.logs); + } + if (action === "r") { + let txExecute = await wallet.createTx({ + msgs: [ + new MsgExecuteContract(stakers[0], alliance_collection_addr, { + alliance_claim_rewards: {}, + }), + ], + memo: "Alliance NFT REWARDS", + chainID: "phoenix-1", + }); + + console.log("TX REWARDS", JSON.stringify(txExecute)); + + let resultExecute = await lcd.tx.broadcastSync(txExecute, "phoenix-1"); + console.log( + `TX REWARDS Transaction executed with hash ${resultExecute.txhash}` + ); + + let txInfo = await lcd.tx.txInfo(resultExecute.txhash, "phoenix-1"); + + console.log("raw", txInfo.raw_log); + console.log("logs", txInfo.logs); + } + })().catch((e) => console.log(e)); +} catch (e) { + console.log(e); +}