Repository: public-awesome/launchpad
Pull request #507 (2023-02-17 21:34:46)

SG-506 royalties add math tests


Patch:
diff --git a/Cargo.lock b/Cargo.lock
index c4cc98fd..801fff38 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -277,7 +277,7 @@ dependencies = [
  "cw2 0.16.0",
  "schemars",
  "serde",
- "sg-std",
+ "sg-std 0.22.9 (registry+https://github.com/rust-lang/crates.io-index)",
  "sg1",
  "sg2",
  "thiserror",
@@ -297,7 +297,7 @@ dependencies = [
  "cw721-base",
  "schemars",
  "serde",
- "sg-std",
+ "sg-std 0.22.9 (registry+https://github.com/rust-lang/crates.io-index)",
  "sg1",
  "sg2",
  "sg4",
@@ -3080,7 +3080,7 @@ dependencies = [
  "cw-utils 0.16.0",
  "schemars",
  "serde",
- "sg-std",
+ "sg-std 0.22.9 (registry+https://github.com/rust-lang/crates.io-index)",
  "thiserror",
 ]
 
@@ -3106,7 +3106,7 @@ dependencies = [
  "schemars",
  "serde",
  "sg-multi-test",
- "sg-std",
+ "sg-std 0.22.9 (registry+https://github.com/rust-lang/crates.io-index)",
  "sg-whitelist",
  "sg1",
  "sg2",
@@ -3137,7 +3137,7 @@ dependencies = [
  "cw-multi-test",
  "schemars",
  "serde",
- "sg-std",
+ "sg-std 0.22.9 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
@@ -3154,7 +3154,7 @@ dependencies = [
  "schemars",
  "serde",
  "sg-controllers",
- "sg-std",
+ "sg-std 0.22.9 (registry+https://github.com/rust-lang/crates.io-index)",
  "thiserror",
 ]
 
@@ -3171,6 +3171,21 @@ dependencies = [
  "thiserror",
 ]
 
+[[package]]
+name = "sg-std"
+version = "0.22.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3fc0a91b72ca7490db1cbccafee08d3a4d633d565077a894d5954550d5d86747"
+dependencies = [
+ "cosmwasm-schema",
+ "cosmwasm-std",
+ "cw-utils 0.16.0",
+ "cw721",
+ "schemars",
+ "serde",
+ "thiserror",
+]
+
 [[package]]
 name = "sg-utils"
 version = "0.22.9"
@@ -3191,7 +3206,7 @@ dependencies = [
  "rust_decimal",
  "schemars",
  "serde",
- "sg-std",
+ "sg-std 0.22.9 (registry+https://github.com/rust-lang/crates.io-index)",
  "sg1",
  "thiserror",
 ]
@@ -3203,7 +3218,7 @@ dependencies = [
  "cosmwasm-std",
  "cw-utils 0.16.0",
  "serde",
- "sg-std",
+ "sg-std 0.22.9 (registry+https://github.com/rust-lang/crates.io-index)",
  "thiserror",
 ]
 
@@ -3256,7 +3271,7 @@ dependencies = [
  "cw721",
  "cw721-base",
  "serde",
- "sg-std",
+ "sg-std 0.22.9 (registry+https://github.com/rust-lang/crates.io-index)",
  "sg721",
  "thiserror",
  "url",
@@ -3274,7 +3289,7 @@ dependencies = [
  "schemars",
  "serde",
  "sg-metadata",
- "sg-std",
+ "sg-std 0.22.9 (registry+https://github.com/rust-lang/crates.io-index)",
  "sg721",
  "sg721-base",
 ]
@@ -3291,7 +3306,7 @@ dependencies = [
  "cw721-base",
  "schemars",
  "serde",
- "sg-std",
+ "sg-std 0.22.9 (registry+https://github.com/rust-lang/crates.io-index)",
  "sg721",
  "sg721-base",
 ]
@@ -3642,7 +3657,7 @@ dependencies = [
  "sg-eth-airdrop",
  "sg-multi-test",
  "sg-splits",
- "sg-std",
+ "sg-std 0.22.9 (registry+https://github.com/rust-lang/crates.io-index)",
  "sg-whitelist",
  "sg1",
  "sg2",
@@ -4068,7 +4083,7 @@ dependencies = [
  "cw2 0.16.0",
  "schemars",
  "serde",
- "sg-std",
+ "sg-std 0.22.9 (registry+https://github.com/rust-lang/crates.io-index)",
  "sg1",
  "sg2",
  "sg721",
@@ -4090,7 +4105,7 @@ dependencies = [
  "rand_xoshiro",
  "schemars",
  "serde",
- "sg-std",
+ "sg-std 0.22.9 (registry+https://github.com/rust-lang/crates.io-index)",
  "sg-whitelist",
  "sg1",
  "sg2",
@@ -4258,7 +4273,7 @@ dependencies = [
  "cw2 0.16.0",
  "schemars",
  "serde",
- "sg-std",
+ "sg-std 0.22.9 (registry+https://github.com/rust-lang/crates.io-index)",
  "sg-whitelist",
  "thiserror",
 ]
diff --git a/Cargo.toml b/Cargo.toml
index 6755cdf7..4cd414cb 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -35,7 +35,7 @@ sg-controllers      = { version = "0.22.9", path = "packages/controllers" }
 sg-metadata         = { version = "0.22.9", path = "packages/sg-metadata" }
 sg-multi-test       = { version = "0.22.9", path = "packages/sg-multi-test" }
 sg-splits           = { version = "0.22.9", path = "contracts/splits" }
-sg-std              = { version = "0.22.9", path = "packages/sg-std" }
+sg-std              = { version = "0.22.9" }
 sg-whitelist        = { version = "0.22.9", path = "contracts/whitelist" }
 thiserror           = "1.0.31"
 url                 = "2.2.2"
diff --git a/contracts/sg721-base/src/msg.rs b/contracts/sg721-base/src/msg.rs
index b87f1940..3a63214b 100644
--- a/contracts/sg721-base/src/msg.rs
+++ b/contracts/sg721-base/src/msg.rs
@@ -1,8 +1,11 @@
 use cosmwasm_schema::cw_serde;
-use cosmwasm_std::{Binary, Empty, Timestamp};
+use cosmwasm_std::{
+    coin, Addr, BankMsg, Binary, Empty, Event, StdError, StdResult, Timestamp, Uint128,
+};
 use cw721_base::msg::{MintMsg, QueryMsg as Cw721QueryMsg};
 use cw_utils::Expiration;
 use sg721::RoyaltyInfoResponse;
+use sg_std::{Response, SubMsg, NATIVE_DENOM};
 
 #[cw_serde]
 pub enum ExecuteMsg<T, E> {
@@ -161,3 +164,38 @@ pub struct CollectionInfoResponse {
     pub start_trading_time: Option<Timestamp>,
     pub royalty_info: Option<RoyaltyInfoResponse>,
 }
+
+impl CollectionInfoResponse {
+    pub fn royalty_payout(
+        &self,
+        collection: Addr,
+        payment: Uint128,
+        protocol_fee: Uint128,
+        finders_fee: Option<Uint128>,
+        res: &mut Response,
+    ) -> StdResult<Uint128> {
+        if let Some(royalty_info) = self.royalty_info.as_ref() {
+            if royalty_info.share.is_zero() {
+                return Ok(Uint128::zero());
+            }
+            let royalty = coin((payment * royalty_info.share).u128(), NATIVE_DENOM);
+            if payment < (protocol_fee + finders_fee.unwrap_or(Uint128::zero()) + royalty.amount) {
+                return Err(StdError::generic_err("Fees exceed payment"));
+            }
+            res.messages.push(SubMsg::new(BankMsg::Send {
+                to_address: royalty_info.payment_address.to_string(),
+                amount: vec![royalty.clone()],
+            }));
+
+            let event = Event::new("royalty-payout")
+                .add_attribute("collection", collection.to_string())
+                .add_attribute("amount", royalty.to_string())
+                .add_attribute("recipient", royalty_info.payment_address.to_string());
+            res.events.push(event);
+
+            Ok(royalty.amount)
+        } else {
+            Ok(Uint128::zero())
+        }
+    }
+}


Test patch:
diff --git a/test-suite/src/sg721_base/tests/integration_tests.rs b/test-suite/src/sg721_base/tests/integration_tests.rs
index 711a5f17..c7e477c3 100644
--- a/test-suite/src/sg721_base/tests/integration_tests.rs
+++ b/test-suite/src/sg721_base/tests/integration_tests.rs
@@ -396,4 +396,200 @@ mod tests {
             assert!(res.is_err());
         }
     }
+
+    mod royalty_payout {
+        use super::*;
+
+        use crate::common_setup::setup_minter::vending_minter::mock_params::mock_create_minter_init_msg;
+        use cosmwasm_std::{Decimal, Response, Uint128};
+        use sg2::msg::CollectionParams;
+        use sg721::RoyaltyInfoResponse;
+        use sg721_base::msg::{CollectionInfoResponse, QueryMsg};
+
+        #[test]
+        fn standard_payout() {
+            let (app, contract) = proper_instantiate();
+
+            let res: CollectionInfoResponse = app
+                .wrap()
+                .query_wasm_smart(contract.clone(), &QueryMsg::CollectionInfo {})
+                .unwrap();
+
+            // payout 100stars, royalty share 10%, royalty payout 10stars
+            let payment = Uint128::from(100000000u128);
+            let royalty_share = res.clone().royalty_info.unwrap().share;
+            let royalty_payout = res
+                .royalty_payout(
+                    contract,
+                    payment,
+                    Uint128::from(10000000u128),
+                    None,
+                    &mut Response::default(),
+                )
+                .unwrap();
+            assert_eq!(royalty_payout, payment * royalty_share);
+        }
+
+        #[test]
+        fn payout_0_royalties() {
+            let init_msg = mock_init_extension(None, None);
+            let custom_collection_params = CollectionParams {
+                info: CollectionInfo {
+                    creator: "creator".to_string(),
+                    description: String::from("Stargaze Monkeys"),
+                    image: "https://example.com/image.png".to_string(),
+                    external_link: Some("https://example.com/external.html".to_string()),
+                    start_trading_time: None,
+                    explicit_content: Some(false),
+                    royalty_info: Some(RoyaltyInfoResponse {
+                        payment_address: "creator".to_string(),
+                        share: Decimal::percent(0),
+                    }),
+                },
+                ..mock_collection_params()
+            };
+            let custom_create_minter_msg =
+                mock_create_minter_init_msg(custom_collection_params, init_msg);
+            let (app, contract) = custom_proper_instantiate(custom_create_minter_msg);
+
+            let res: CollectionInfoResponse = app
+                .wrap()
+                .query_wasm_smart(contract.clone(), &QueryMsg::CollectionInfo {})
+                .unwrap();
+
+            // payout 100stars, royalty share 0%, royalty payout 0stars
+            let payment = Uint128::from(100000000u128);
+            let royalty_payout = res
+                .royalty_payout(
+                    contract,
+                    payment,
+                    Uint128::from(10000000u128),
+                    None,
+                    &mut Response::default(),
+                )
+                .unwrap();
+            assert_eq!(royalty_payout, Uint128::zero());
+        }
+
+        #[test]
+        fn payout_too_much_royalties() {
+            let init_msg = mock_init_extension(None, None);
+            let custom_collection_params = CollectionParams {
+                info: CollectionInfo {
+                    creator: "creator".to_string(),
+                    description: String::from("Stargaze Monkeys"),
+                    image: "https://example.com/image.png".to_string(),
+                    external_link: Some("https://example.com/external.html".to_string()),
+                    start_trading_time: None,
+                    explicit_content: Some(false),
+                    royalty_info: Some(RoyaltyInfoResponse {
+                        payment_address: "creator".to_string(),
+                        share: Decimal::percent(91),
+                    }),
+                },
+                ..mock_collection_params()
+            };
+            let custom_create_minter_msg =
+                mock_create_minter_init_msg(custom_collection_params, init_msg);
+            let (app, contract) = custom_proper_instantiate(custom_create_minter_msg);
+
+            let res: CollectionInfoResponse = app
+                .wrap()
+                .query_wasm_smart(contract.clone(), &QueryMsg::CollectionInfo {})
+                .unwrap();
+
+            // payout 100stars, royalty share 91%, royalty payout fails
+            // fees exceed payment
+            let payment = Uint128::from(100000000u128);
+            let res = res.royalty_payout(
+                contract,
+                payment,
+                Uint128::from(10000000u128),
+                None,
+                &mut Response::default(),
+            );
+            assert!(res.is_err());
+        }
+
+        #[test]
+        fn payout_odd_royalties() {
+            // uint * decimal::percent
+            let init_msg = mock_init_extension(None, None);
+            let custom_collection_params = CollectionParams {
+                info: CollectionInfo {
+                    creator: "creator".to_string(),
+                    description: String::from("Stargaze Monkeys"),
+                    image: "https://example.com/image.png".to_string(),
+                    external_link: Some("https://example.com/external.html".to_string()),
+                    start_trading_time: None,
+                    explicit_content: Some(false),
+                    royalty_info: Some(RoyaltyInfoResponse {
+                        payment_address: "creator".to_string(),
+                        share: Decimal::percent(3),
+                    }),
+                },
+                ..mock_collection_params()
+            };
+            let custom_create_minter_msg =
+                mock_create_minter_init_msg(custom_collection_params, init_msg);
+            let (app, contract) = custom_proper_instantiate(custom_create_minter_msg);
+
+            let res: CollectionInfoResponse = app
+                .wrap()
+                .query_wasm_smart(contract.clone(), &QueryMsg::CollectionInfo {})
+                .unwrap();
+
+            // payout 100stars, royalty share 1%, royalty payout 10stars
+            let payment = Uint128::from(1111111111121111111u128);
+            let royalty_share = res.clone().royalty_info.unwrap().share;
+            let royalty_payout = res
+                .royalty_payout(
+                    contract,
+                    payment,
+                    Uint128::from(10000000u128),
+                    None,
+                    &mut Response::default(),
+                )
+                .unwrap();
+            assert_eq!(royalty_payout, payment * royalty_share);
+        }
+
+        #[test]
+        fn payout_royalties_none() {
+            let init_msg = mock_init_extension(None, None);
+            let custom_collection_params = CollectionParams {
+                info: CollectionInfo {
+                    creator: "creator".to_string(),
+                    description: String::from("Stargaze Monkeys"),
+                    image: "https://example.com/image.png".to_string(),
+                    external_link: Some("https://example.com/external.html".to_string()),
+                    start_trading_time: None,
+                    explicit_content: Some(false),
+                    royalty_info: None,
+                },
+                ..mock_collection_params()
+            };
+            let custom_create_minter_msg =
+                mock_create_minter_init_msg(custom_collection_params, init_msg);
+            let (app, contract) = custom_proper_instantiate(custom_create_minter_msg);
+
+            let res: CollectionInfoResponse = app
+                .wrap()
+                .query_wasm_smart(contract.clone(), &QueryMsg::CollectionInfo {})
+                .unwrap();
+
+            // payout 100stars, royalty share none, royalty payout 0stars
+            let payment = Uint128::from(100000000u128);
+            let royalty_payout = res
+                .royalty_payout(
+                    contract,
+                    payment,
+                    Uint128::from(10000000u128),
+                    None,
+                    &mut Response::default(),
+                )
+                .unwrap();
+            assert_eq!(royalty_payout, Uint128::zero());
+        }
+    }
 }