Repository: public-awesome/launchpad
Pull request #631 (2023-12-12 10:39:40)

Royalty increase update for v1 collections

codecov[bot]: ## [Codecov](https://app.codecov.io/gh/public-awesome/launchpad/pull/631?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=public-awesome) Report
Attention: `2 lines` in your changes are missing coverage. Please review.
> Comparison is base [(`f9b1c54`)](https://app.codecov.io/gh/public-awesome/launchpad/commit/f9b1c546d8fc361de80ffb888b7caaa56d30900f?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=public-awesome) 79.40% compared to head [(`177bf66`)](https://app.codecov.io/gh/public-awesome/launchpad/pull/631?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=public-awesome) 79.48%.

| [Files](https://app.codecov.io/gh/public-awesome/launchpad/pull/631?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=public-awesome) | Patch % | Lines |
|---|---|---|
| [contracts/sg721-updatable/src/msg.rs](https://app.codecov.io/gh/public-awesome/launchpad/pull/631?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=public-awesome#diff-Y29udHJhY3RzL3NnNzIxLXVwZGF0YWJsZS9zcmMvbXNnLnJz) | 0.00% | [2 Missing :warning: ](https://app.codecov.io/gh/public-awesome/launchpad/pull/631?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=public-awesome) |

<details><summary>Additional details and impacted files</summary>


```diff
@@               Coverage Diff                @@
##           release/v1.x     #631      +/-   ##
================================================
+ Coverage         79.40%   79.48%   +0.08%     
================================================
  Files                14       14              
  Lines               971      975       +4     
================================================
+ Hits                771      775       +4     
  Misses              200      200              
```



</details>

[:umbrella: View full report in Codecov by Sentry](https://app.codecov.io/gh/public-awesome/launchpad/pull/631?src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=public-awesome).   
:loudspeaker: Have feedback on the report? [Share it here](https://about.codecov.io/codecov-pr-comment-feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=public-awesome).


Patch:
diff --git a/.circleci/config.yml b/.circleci/config.yml
index b9b57744..7f97cfa6 100644
--- a/.circleci/config.yml
+++ b/.circleci/config.yml
@@ -24,7 +24,7 @@ workflows:
 jobs:
   contract_sg721:
     docker:
-      - image: rust:1.64.0
+      - image: rust:1.68.0
     working_directory: ~/project/contracts/sg721
     steps:
       - checkout:
@@ -34,7 +34,7 @@ jobs:
           command: rustc --version; cargo --version; rustup --version
       - restore_cache:
           keys:
-            - cargocache-sg721-rust:1.64.0-{{ checksum "~/project/Cargo.lock" }}
+            - cargocache-sg721-rust:1.68.0-{{ checksum "~/project/Cargo.lock" }}
       - run:
           name: Unit Tests
           environment:
@@ -56,10 +56,10 @@ jobs:
           paths:
             - /usr/local/cargo/registry
             - target
-          key: cargocache-sg721-rust:1.64.0-{{ checksum "~/project/Cargo.lock" }}
+          key: cargocache-sg721-rust:1.68.0-{{ checksum "~/project/Cargo.lock" }}
   contract_sg721_updatable:
     docker:
-      - image: rust:1.64.0
+      - image: rust:1.68.0
     working_directory: ~/project/contracts/sg721-updatable
     steps:
       - checkout:
@@ -69,7 +69,7 @@ jobs:
           command: rustc --version; cargo --version; rustup --version
       - restore_cache:
           keys:
-            - cargocache-sg721-rust:1.64.0-{{ checksum "~/project/Cargo.lock" }}
+            - cargocache-sg721-rust:1.68.0-{{ checksum "~/project/Cargo.lock" }}
       - run:
           name: Unit Tests
           environment:
@@ -91,11 +91,11 @@ jobs:
           paths:
             - /usr/local/cargo/registry
             - target
-          key: cargocache-sg721-updatable-rust:1.64.0-{{ checksum "~/project/Cargo.lock" }}
+          key: cargocache-sg721-updatable-rust:1.68.0-{{ checksum "~/project/Cargo.lock" }}
 
   contract_minter:
     docker:
-      - image: rust:1.64.0
+      - image: rust:1.68.0
     working_directory: ~/project/contracts/minter
     steps:
       - checkout:
@@ -105,7 +105,7 @@ jobs:
           command: rustc --version; cargo --version; rustup --version
       - restore_cache:
           keys:
-            - cargocache-minter-rust:1.64.0-{{ checksum "~/project/Cargo.lock" }}
+            - cargocache-minter-rust:1.68.0-{{ checksum "~/project/Cargo.lock" }}
       - run:
           name: Unit Tests
           environment:
@@ -127,11 +127,11 @@ jobs:
           paths:
             - /usr/local/cargo/registry
             - target
-          key: cargocache-minter-rust:1.64.0-{{ checksum "~/project/Cargo.lock" }}
+          key: cargocache-minter-rust:1.68.0-{{ checksum "~/project/Cargo.lock" }}
 
   contract_whitelist:
     docker:
-      - image: rust:1.64.0
+      - image: rust:1.68.0
     working_directory: ~/project/contracts/whitelist
     steps:
       - checkout:
@@ -141,7 +141,7 @@ jobs:
           command: rustc --version; cargo --version; rustup --version
       - restore_cache:
           keys:
-            - cargocache-whitelist-rust:1.64.0-{{ checksum "~/project/Cargo.lock" }}
+            - cargocache-whitelist-rust:1.68.0-{{ checksum "~/project/Cargo.lock" }}
       - run:
           name: Unit Tests
           environment:
@@ -163,11 +163,11 @@ jobs:
           paths:
             - /usr/local/cargo/registry
             - target
-          key: cargocache-whitelist-rust:1.64.0-{{ checksum "~/project/Cargo.lock" }}
+          key: cargocache-whitelist-rust:1.68.0-{{ checksum "~/project/Cargo.lock" }}
 
   contract_royalty_group:
     docker:
-      - image: rust:1.64.0
+      - image: rust:1.68.0
     working_directory: ~/project/contracts/royalty-group
     steps:
       - checkout:
@@ -177,7 +177,7 @@ jobs:
           command: rustc --version; cargo --version; rustup --version
       - restore_cache:
           keys:
-            - cargocache-royalty-rust:1.64.0-{{ checksum "~/project/Cargo.lock" }}
+            - cargocache-royalty-rust:1.68.0-{{ checksum "~/project/Cargo.lock" }}
       - run:
           name: Unit Tests
           environment:
@@ -199,11 +199,11 @@ jobs:
           paths:
             - /usr/local/cargo/registry
             - target
-          key: cargocache-royalty-rust:1.64.0-{{ checksum "~/project/Cargo.lock" }}
+          key: cargocache-royalty-rust:1.68.0-{{ checksum "~/project/Cargo.lock" }}
 
   package_sg_std:
     docker:
-      - image: rust:1.64.0
+      - image: rust:1.68.0
     working_directory: ~/project/package/sg-std
     steps:
       - checkout:
@@ -213,7 +213,7 @@ jobs:
           command: rustc --version; cargo --version; rustup --version
       - restore_cache:
           keys:
-            - cargocache-sg-std-rust:1.64.0-{{ checksum "~/project/Cargo.lock" }}
+            - cargocache-sg-std-rust:1.68.0-{{ checksum "~/project/Cargo.lock" }}
       - run:
           name: Unit Tests
           environment:
@@ -223,11 +223,11 @@ jobs:
           paths:
             - /usr/local/cargo/registry
             - target
-          key: cargocache-sg-std-rust:1.64.0-{{ checksum "~/project/Cargo.lock" }}
+          key: cargocache-sg-std-rust:1.68.0-{{ checksum "~/project/Cargo.lock" }}
 
   package_sg_utils:
     docker:
-      - image: rust:1.64.0
+      - image: rust:1.68.0
     working_directory: ~/project/package/sg-utils
     steps:
       - checkout:
@@ -237,7 +237,7 @@ jobs:
           command: rustc --version; cargo --version; rustup --version
       - restore_cache:
           keys:
-            - cargocache-sg-utils-rust:1.64.0-{{ checksum "~/project/Cargo.lock" }}
+            - cargocache-sg-utils-rust:1.68.0-{{ checksum "~/project/Cargo.lock" }}
       - run:
           name: Unit Tests
           environment:
@@ -247,11 +247,11 @@ jobs:
           paths:
             - /usr/local/cargo/registry
             - target
-          key: cargocache-sg-utils-rust:1.64.0-{{ checksum "~/project/Cargo.lock" }}
+          key: cargocache-sg-utils-rust:1.68.0-{{ checksum "~/project/Cargo.lock" }}
 
   lint:
     docker:
-      - image: rust:1.64.0
+      - image: rust:1.68.0
     steps:
       - checkout
       - run:
@@ -259,7 +259,7 @@ jobs:
           command: rustc --version; cargo --version; rustup --version; rustup target list --installed
       - restore_cache:
           keys:
-            - cargocache-v2-lint-rust:1.64.0-{{ checksum "Cargo.lock" }}
+            - cargocache-v2-lint-rust:1.68.0-{{ checksum "Cargo.lock" }}
       - run:
           name: Add rustfmt component
           command: rustup component add rustfmt
@@ -278,7 +278,7 @@ jobs:
             - target/debug/.fingerprint
             - target/debug/build
             - target/debug/deps
-          key: cargocache-v2-lint-rust:1.64.0-{{ checksum "Cargo.lock" }}
+          key: cargocache-v2-lint-rust:1.68.0-{{ checksum "Cargo.lock" }}
 
   # This runs one time on the top level to ensure all contracts compile properly into wasm.
   # We don't run the wasm build per contract build, and then reuse a lot of the same dependencies, so this speeds up CI time
@@ -286,7 +286,7 @@ jobs:
   # We also sanity-check the resultant wasm files.
   wasm-build:
     docker:
-      - image: rust:1.64.0
+      - image: rust:1.68.0
     steps:
       - checkout:
           path: ~/project
@@ -295,7 +295,7 @@ jobs:
           command: rustc --version; cargo --version; rustup --version
       - restore_cache:
           keys:
-            - cargocache-wasm-rust:1.64.0-{{ checksum "~/project/Cargo.lock" }}
+            - cargocache-wasm-rust:1.68.0-{{ checksum "~/project/Cargo.lock" }}
       - run:
           name: Add wasm32 target
           command: rustup target add wasm32-unknown-unknown
@@ -315,7 +315,7 @@ jobs:
           paths:
             - /usr/local/cargo/registry
             - target
-          key: cargocache-wasm-rust:1.64.0-{{ checksum "~/project/Cargo.lock" }}
+          key: cargocache-wasm-rust:1.68.0-{{ checksum "~/project/Cargo.lock" }}
       - run:
           name: Check wasm contracts
           command: |
diff --git a/Cargo.lock b/Cargo.lock
index 55cea4f3..221edce8 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -915,7 +915,7 @@ dependencies = [
 
 [[package]]
 name = "sg721-updatable"
-version = "0.8.4"
+version = "0.8.5"
 dependencies = [
  "cosmwasm-schema",
  "cosmwasm-std",
diff --git a/contracts/sg721-updatable/Cargo.toml b/contracts/sg721-updatable/Cargo.toml
index f0a28ab9..3e2fb578 100644
--- a/contracts/sg721-updatable/Cargo.toml
+++ b/contracts/sg721-updatable/Cargo.toml
@@ -1,6 +1,6 @@
 [package]
 name    = "sg721-updatable"
-version = "0.8.4"
+version = "0.8.5"
 authors = ["John Y <j@noreply.publicawesome.com>"]
 edition = "2018"
 
diff --git a/contracts/sg721-updatable/schema/collection_info_response.json b/contracts/sg721-updatable/schema/collection_info_response.json
index 326778c7..d285d111 100644
--- a/contracts/sg721-updatable/schema/collection_info_response.json
+++ b/contracts/sg721-updatable/schema/collection_info_response.json
@@ -51,30 +51,8 @@
         },
         "share": {
           "$ref": "#/definitions/Decimal"
-        },
-        "updated_at": {
-          "anyOf": [
-            {
-              "$ref": "#/definitions/Timestamp"
-            },
-            {
-              "type": "null"
-            }
-          ]
         }
       }
-    },
-    "Timestamp": {
-      "description": "A point in time in nanosecond precision.\n\nThis type can represent times from 1970-01-01T00:00:00Z to 2554-07-21T23:34:33Z.\n\n## Examples\n\n``` # use cosmwasm_std::Timestamp; let ts = Timestamp::from_nanos(1_000_000_202); assert_eq!(ts.nanos(), 1_000_000_202); assert_eq!(ts.seconds(), 1); assert_eq!(ts.subsec_nanos(), 202);\n\nlet ts = ts.plus_seconds(2); assert_eq!(ts.nanos(), 3_000_000_202); assert_eq!(ts.seconds(), 3); assert_eq!(ts.subsec_nanos(), 202); ```",
-      "allOf": [
-        {
-          "$ref": "#/definitions/Uint64"
-        }
-      ]
-    },
-    "Uint64": {
-      "description": "A thin wrapper around u64 that is using strings for JSON encoding/decoding, such that the full u64 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u64` to get the value out:\n\n``` # use cosmwasm_std::Uint64; let a = Uint64::from(42u64); assert_eq!(a.u64(), 42);\n\nlet b = Uint64::from(70u32); assert_eq!(b.u64(), 70); ```",
-      "type": "string"
     }
   }
 }
diff --git a/contracts/sg721-updatable/schema/instantiate_msg.json b/contracts/sg721-updatable/schema/instantiate_msg.json
index f187ad52..fb28071b 100644
--- a/contracts/sg721-updatable/schema/instantiate_msg.json
+++ b/contracts/sg721-updatable/schema/instantiate_msg.json
@@ -74,30 +74,8 @@
         },
         "share": {
           "$ref": "#/definitions/Decimal"
-        },
-        "updated_at": {
-          "anyOf": [
-            {
-              "$ref": "#/definitions/Timestamp"
-            },
-            {
-              "type": "null"
-            }
-          ]
         }
       }
-    },
-    "Timestamp": {
-      "description": "A point in time in nanosecond precision.\n\nThis type can represent times from 1970-01-01T00:00:00Z to 2554-07-21T23:34:33Z.\n\n## Examples\n\n``` # use cosmwasm_std::Timestamp; let ts = Timestamp::from_nanos(1_000_000_202); assert_eq!(ts.nanos(), 1_000_000_202); assert_eq!(ts.seconds(), 1); assert_eq!(ts.subsec_nanos(), 202);\n\nlet ts = ts.plus_seconds(2); assert_eq!(ts.nanos(), 3_000_000_202); assert_eq!(ts.seconds(), 3); assert_eq!(ts.subsec_nanos(), 202); ```",
-      "allOf": [
-        {
-          "$ref": "#/definitions/Uint64"
-        }
-      ]
-    },
-    "Uint64": {
-      "description": "A thin wrapper around u64 that is using strings for JSON encoding/decoding, such that the full u64 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u64` to get the value out:\n\n``` # use cosmwasm_std::Uint64; let a = Uint64::from(42u64); assert_eq!(a.u64(), 42);\n\nlet b = Uint64::from(70u32); assert_eq!(b.u64(), 70); ```",
-      "type": "string"
     }
   }
 }
diff --git a/contracts/sg721-updatable/src/contract.rs b/contracts/sg721-updatable/src/contract.rs
index 72e372d8..3234d8e1 100644
--- a/contracts/sg721-updatable/src/contract.rs
+++ b/contracts/sg721-updatable/src/contract.rs
@@ -3,8 +3,8 @@ use semver::Version;
 #[cfg(not(feature = "library"))]
 use cosmwasm_std::entry_point;
 use cosmwasm_std::{
-    to_binary, Binary, Decimal, Deps, DepsMut, Empty, Env, Event, MessageInfo, StdError, StdResult,
-    Uint128,
+    ensure, to_binary, Binary, Decimal, Deps, DepsMut, Empty, Env, Event, MessageInfo, StdError,
+    StdResult, Uint128,
 };
 use cw2::set_contract_version;
 use cw_utils::{nonpayable, Expiration, DAY};
@@ -18,7 +18,9 @@ use url::Url;
 use crate::msg::{
     CollectionInfoResponse, ExecuteMsg, InstantiateMsg, QueryMsg, RoyaltyInfoResponse,
 };
-use crate::state::{CollectionInfo, RoyaltyInfo, COLLECTION_INFO, FROZEN_TOKEN_METADATA};
+use crate::state::{
+    CollectionInfo, RoyaltyInfo, COLLECTION_INFO, FROZEN_TOKEN_METADATA, ROYALTY_UPDATED_AT,
+};
 
 // version info for migration info
 const COMPATIBLE_MIGRATION_CONTRACT_NAME: &str = "crates.io:sg-721";
@@ -27,6 +29,7 @@ const CONTRACT_VERSION: &str = env!("CARGO_PKG_VERSION");
 
 const CREATION_FEE: u128 = 1_000_000_000;
 const MAX_ROYALTY_SHARE_BPS: u64 = 1_000;
+const MAX_ROYALTY_SHARE_DELTA_BPS: u64 = 200;
 
 // Disable instantiation for production build
 #[cfg(target = "wasm32-unknown-unknown")]
@@ -81,7 +84,6 @@ pub fn instantiate(
         Some(royalty_info) => Some(RoyaltyInfo {
             payment_address: deps.api.addr_validate(&royalty_info.payment_address)?,
             share: royalty_info.share_validate()?,
-            updated_at: Some(env.block.time),
         }),
         None => None,
     };
@@ -97,6 +99,7 @@ pub fn instantiate(
     };
 
     FROZEN_TOKEN_METADATA.save(deps.storage, &false)?;
+    ROYALTY_UPDATED_AT.save(deps.storage, &env.block.time)?;
     COLLECTION_INFO.save(deps.storage, &collection_info)?;
 
     Ok(Response::default()
@@ -147,40 +150,59 @@ pub fn execute_update_royalty_info(
     let payment_addr = deps.api.addr_validate(&payment_address)?;
     let share = Decimal::percent(share_bps) / Uint128::from(100u128);
     let max_share_limit = Decimal::percent(MAX_ROYALTY_SHARE_BPS) / Uint128::from(100u128);
-
-    if share > max_share_limit {
-        return Err(ContractError::RoyaltyShareIncreasedTooMuch {});
-    }
-
-    let current_royalty_info = collection_info.royalty_info;
-
-    if let Some(curr_royalty_info) = current_royalty_info {
-        let updated_at = match curr_royalty_info.updated_at {
-            Some(updated_at) => updated_at,
-            // If current royalty info updated_at is None, it might be an older version that has not been migrated properly.
-            None => return Err(ContractError::RoyaltyInfoInvalid {}),
-        };
-
-        // make sure the update time is after 24 hours
+    let max_share_delta = Decimal::percent(MAX_ROYALTY_SHARE_DELTA_BPS) / Uint128::from(100u128);
+    ensure!(
+        share <= max_share_limit,
+        // multiply by 100 to get the percentage for error message
+        ContractError::InvalidRoyalties(format!(
+            "Share percentage cannot be greater than {}%",
+            max_share_limit * Uint128::from(100u128)
+        ))
+    );
+
+    let updated_at = ROYALTY_UPDATED_AT.load(deps.storage)?;
+
+    if let Some(old_royalty_info) = collection_info.royalty_info {
+        // make sure the update time is at least 24 hours after the last one
         let end = (Expiration::AtTime(updated_at) + DAY)?;
-        let update_too_soon = !end.is_expired(&env.block);
-
-        if share > curr_royalty_info.share && update_too_soon {
-            return Err(ContractError::RoyaltyUpdateTooSoon {});
+        ensure!(
+            end.is_expired(&env.block),
+            ContractError::RoyaltyUpdateTooSoon {}
+        );
+        // Make sure the share is not increased more than MAX_ROYALTY_SHARE_DELTA_BPS at a time
+        if share > old_royalty_info.share {
+            let share_delta = share - old_royalty_info.share;
+            ensure!(
+                share_delta <= max_share_delta,
+                ContractError::InvalidRoyalties(format!(
+                    "Share increase cannot be greater than {}%",
+                    max_share_delta * Uint128::from(100u128)
+                ))
+            );
         }
+    } else {
+        ensure!(
+            share <= max_share_delta,
+            ContractError::InvalidRoyalties(format!(
+                "Share increase cannot be greater than {}%",
+                max_share_delta * Uint128::from(100u128)
+            ))
+        );
     }
+
     collection_info.royalty_info = Some(RoyaltyInfo {
         payment_address: payment_addr,
         share,
-        updated_at: Some(env.block.time),
     });
 
     COLLECTION_INFO.save(deps.storage, &collection_info)?;
+    ROYALTY_UPDATED_AT.save(deps.storage, &env.block.time)?;
 
     let event = Event::new("update_royalty_info")
         .add_attribute("sender", info.sender)
         .add_attribute("payment_address", payment_address)
-        .add_attribute("share", share.to_string());
+        .add_attribute("share", share.to_string())
+        .add_attribute("updated_at", env.block.time.to_string());
     Ok(Response::new().add_event(event))
 }
 
@@ -256,7 +278,6 @@ fn query_config(deps: Deps) -> StdResult<CollectionInfoResponse> {
         Some(royalty_info) => Some(RoyaltyInfoResponse {
             payment_address: royalty_info.payment_address.to_string(),
             share: royalty_info.share,
-            updated_at: royalty_info.updated_at,
         }),
         None => None,
     };
@@ -271,7 +292,7 @@ fn query_config(deps: Deps) -> StdResult<CollectionInfoResponse> {
 }
 
 #[cfg_attr(not(feature = "library"), entry_point)]
-pub fn migrate(deps: DepsMut, _env: Env, _msg: Empty) -> Result<Response, ContractError> {
+pub fn migrate(deps: DepsMut, env: Env, _msg: Empty) -> Result<Response, ContractError> {
     let current_version = cw2::get_contract_version(deps.storage)?;
     if ![CONTRACT_NAME, COMPATIBLE_MIGRATION_CONTRACT_NAME]
         .contains(&current_version.contract.as_str())
@@ -295,19 +316,8 @@ pub fn migrate(deps: DepsMut, _env: Env, _msg: Empty) -> Result<Response, Contra
     }
 
     FROZEN_TOKEN_METADATA.save(deps.storage, &false)?;
-
-    let mut collection_info = COLLECTION_INFO.load(deps.storage)?;
-    let current_royalty_info = collection_info.royalty_info;
-    if let Some(royalty_info) = current_royalty_info {
-        let new_royalty_info = RoyaltyInfo {
-            payment_address: royalty_info.payment_address,
-            share: royalty_info.share,
-            // setting updated_at to 1 day ago to make updating royalties possible right after a migration
-            updated_at: Some(_env.block.time.minus_seconds(86400)),
-        };
-        collection_info.royalty_info = Some(new_royalty_info);
-        COLLECTION_INFO.save(deps.storage, &collection_info)?;
-    }
+    // setting updated_at to 1 day ago to make updating royalties possible right after a migration
+    ROYALTY_UPDATED_AT.save(deps.storage, &env.block.time.minus_seconds(86400))?;
     // set new contract version
     set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?;
     Ok(Response::new())
@@ -375,7 +385,6 @@ mod tests {
                 royalty_info: Some(RoyaltyInfoResponse {
                     payment_address: creator.clone(),
                     share: Decimal::percent(10),
-                    updated_at: None,
                 }),
             },
         };
@@ -392,7 +401,6 @@ mod tests {
             Some(RoyaltyInfoResponse {
                 payment_address: creator,
                 share: Decimal::percent(10),
-                updated_at: Some(mock_env().block.time),
             }),
             value.royalty_info
         );
diff --git a/contracts/sg721-updatable/src/error.rs b/contracts/sg721-updatable/src/error.rs
index 041f68c9..ca66a42c 100644
--- a/contracts/sg721-updatable/src/error.rs
+++ b/contracts/sg721-updatable/src/error.rs
@@ -30,16 +30,13 @@ pub enum ContractError {
     #[error("Approval not found for: {spender}")]
     ApprovalNotFound { spender: String },
 
-    #[error("Invalid Royalties")]
-    InvalidRoyalties {},
-
     #[error("Description too long")]
     DescriptionTooLong {},
 
-    #[error("Royalty share percentage cannot exceed 10%")]
-    RoyaltyShareIncreasedTooMuch {},
+    #[error("InvalidRoyalties: {0}")]
+    InvalidRoyalties(String),
 
-    #[error("Royalty share percentage can only be increased a day after the last update")]
+    #[error("Royalty share percentage can only be altered a day after the last update")]
     RoyaltyUpdateTooSoon {},
 
     #[error("Royalty Info invalid")]
diff --git a/contracts/sg721-updatable/src/msg.rs b/contracts/sg721-updatable/src/msg.rs
index 34c0b3ce..821f951b 100644
--- a/contracts/sg721-updatable/src/msg.rs
+++ b/contracts/sg721-updatable/src/msg.rs
@@ -19,13 +19,14 @@ pub type Cw721ExecuteMsg = cw721_base::msg::ExecuteMsg<Empty>;
 pub struct RoyaltyInfoResponse {
     pub payment_address: String,
     pub share: Decimal,
-    pub updated_at: Option<Timestamp>,
 }
 
 impl RoyaltyInfoResponse {
     pub fn share_validate(&self) -> Result<Decimal, ContractError> {
         if self.share > Decimal::one() {
-            return Err(ContractError::InvalidRoyalties {});
+            return Err(ContractError::InvalidRoyalties(
+                "Invalid royalty percentage".to_string(),
+            ));
         }
 
         Ok(self.share)
diff --git a/contracts/sg721-updatable/src/state.rs b/contracts/sg721-updatable/src/state.rs
index 227694e1..3386f475 100644
--- a/contracts/sg721-updatable/src/state.rs
+++ b/contracts/sg721-updatable/src/state.rs
@@ -16,8 +16,8 @@ pub struct CollectionInfo<T> {
 pub struct RoyaltyInfo {
     pub payment_address: Addr,
     pub share: Decimal,
-    pub updated_at: Option<Timestamp>,
 }
 
 pub const FROZEN_TOKEN_METADATA: Item<bool> = Item::new("frozen_token_metadata");
 pub const COLLECTION_INFO: Item<CollectionInfo<RoyaltyInfo>> = Item::new("collection_info");
+pub const ROYALTY_UPDATED_AT: Item<Timestamp> = Item::new("royalty_updated_at");


Test patch:
diff --git a/contracts/minter/src/contract_updatable_tests.rs b/contracts/minter/src/contract_updatable_tests.rs
index 3e1c2f08..8eee2d66 100644
--- a/contracts/minter/src/contract_updatable_tests.rs
+++ b/contracts/minter/src/contract_updatable_tests.rs
@@ -1538,7 +1538,10 @@ fn test_update_none_royalties() {
 
     assert_eq!(
         err.source().unwrap().to_string(),
-        sg721_updatable::ContractError::RoyaltyShareIncreasedTooMuch {}.to_string()
+        sg721_updatable::ContractError::InvalidRoyalties(
+            "Share percentage cannot be greater than 10%".to_string()
+        )
+        .to_string()
     );
 
     let update_royalty_msg: Sg721UpdatableExecuteMsg<Extension> =
@@ -1547,6 +1550,29 @@ fn test_update_none_royalties() {
             share_bps: 400,
         };
 
+    let res = router.execute_contract(
+        creator.clone(),
+        Addr::unchecked(sg71_address.clone()),
+        &update_royalty_msg,
+        &[],
+    );
+
+    let err = res.unwrap_err();
+
+    assert_eq!(
+        err.source().unwrap().to_string(),
+        sg721_updatable::ContractError::InvalidRoyalties(
+            "Share increase cannot be greater than 2%".to_string()
+        )
+        .to_string()
+    );
+
+    let update_royalty_msg: Sg721UpdatableExecuteMsg<Extension> =
+        Sg721UpdatableExecuteMsg::UpdateRoyaltyInfo {
+            payment_address: "creator2".to_string(),
+            share_bps: 100,
+        };
+
     let res = router
         .execute_contract(
             creator.clone(),
@@ -1563,7 +1589,7 @@ fn test_update_none_royalties() {
     assert_eq!(res.events[1].attributes[2].key, "payment_address");
     assert_eq!(res.events[1].attributes[2].value, "creator2");
     assert_eq!(res.events[1].attributes[3].key, "share");
-    assert_eq!(res.events[1].attributes[3].value, "0.04");
+    assert_eq!(res.events[1].attributes[3].value, "0.01");
 
     let update_royalty_msg: Sg721UpdatableExecuteMsg<Extension> =
         Sg721UpdatableExecuteMsg::UpdateRoyaltyInfo {
@@ -1586,6 +1612,29 @@ fn test_update_none_royalties() {
 
     setup_block_time(&mut router, GENESIS_MINT_START_TIME + 86400000000000);
 
+    let res = router.execute_contract(
+        creator.clone(),
+        Addr::unchecked(sg71_address.clone()),
+        &update_royalty_msg,
+        &[],
+    );
+
+    let err = res.unwrap_err();
+
+    assert_eq!(
+        err.source().unwrap().to_string(),
+        sg721_updatable::ContractError::InvalidRoyalties(
+            "Share increase cannot be greater than 2%".to_string()
+        )
+        .to_string()
+    );
+
+    let update_royalty_msg: Sg721UpdatableExecuteMsg<Extension> =
+        Sg721UpdatableExecuteMsg::UpdateRoyaltyInfo {
+            payment_address: "creator2".to_string(),
+            share_bps: 200,
+        };
+
     let res = router
         .execute_contract(
             creator.clone(),
@@ -1602,9 +1651,9 @@ fn test_update_none_royalties() {
     assert_eq!(res.events[1].attributes[2].key, "payment_address");
     assert_eq!(res.events[1].attributes[2].value, "creator2");
     assert_eq!(res.events[1].attributes[3].key, "share");
-    assert_eq!(res.events[1].attributes[3].value, "0.05");
+    assert_eq!(res.events[1].attributes[3].value, "0.02");
 
-    setup_block_time(&mut router, GENESIS_MINT_START_TIME + 86500000000000);
+    setup_block_time(&mut router, GENESIS_MINT_START_TIME + 186500000000000);
 
     let update_royalty_msg: Sg721UpdatableExecuteMsg<Extension> =
         Sg721UpdatableExecuteMsg::UpdateRoyaltyInfo {
@@ -1699,13 +1748,16 @@ fn test_update_royalties() {
 
     assert_eq!(
         err.source().unwrap().to_string(),
-        sg721_updatable::ContractError::RoyaltyShareIncreasedTooMuch {}.to_string()
+        sg721_updatable::ContractError::InvalidRoyalties(
+            "Share percentage cannot be greater than 10%".to_string()
+        )
+        .to_string()
     );
 
     let update_royalty_msg: sg721_updatable::msg::ExecuteMsg<Extension> =
         sg721_updatable::msg::ExecuteMsg::UpdateRoyaltyInfo {
             payment_address: "creator2".to_string(),
-            share_bps: 900,
+            share_bps: 200,
         };
     let res = router.execute_contract(
         creator.clone(),
@@ -1731,12 +1783,12 @@ fn test_update_royalties() {
     );
     assert!(res.is_ok());
 
-    setup_block_time(&mut router, GENESIS_MINT_START_TIME + 86500000000000);
+    setup_block_time(&mut router, GENESIS_MINT_START_TIME + 186500000000000);
 
     let update_royalty_msg: sg721_updatable::msg::ExecuteMsg<Extension> =
         sg721_updatable::msg::ExecuteMsg::UpdateRoyaltyInfo {
             payment_address: "creator2".to_string(),
-            share_bps: 800,
+            share_bps: 300,
         };
     let res = router.execute_contract(
         creator,
@@ -1860,7 +1912,7 @@ fn try_migrate() {
     );
 
     // // no op when same version
-    set_contract_version(&mut deps.storage, "crates.io:sg-721", "0.8.4").unwrap();
+    set_contract_version(&mut deps.storage, "crates.io:sg-721", "0.8.5").unwrap();
     sg721_updatable::contract::migrate(deps.as_mut(), env, Empty {}).unwrap();
 
     // Migration happy path
@@ -1915,29 +1967,12 @@ fn try_migrate() {
 
     let sg721_base_address = config.sg721_address;
 
-    let collection_info: CollectionInfo<sg721_updatable::msg::RoyaltyInfoResponse> = router
-        .wrap()
-        .query_wasm_smart(
-            sg721_base_address.clone(),
-            &sg721::msg::QueryMsg::CollectionInfo {},
-        )
-        .unwrap();
-
-    assert!(collection_info.royalty_info.unwrap().updated_at.is_none());
-
-    router
-        .migrate_contract(
-            creator,
-            Addr::unchecked(sg721_base_address.clone()),
-            &Empty {},
-            sg721_code_id,
-        )
-        .unwrap();
-
-    let collection_info: CollectionInfo<sg721_updatable::msg::RoyaltyInfoResponse> = router
-        .wrap()
-        .query_wasm_smart(sg721_base_address, &sg721::msg::QueryMsg::CollectionInfo {})
-        .unwrap();
+    let res = router.migrate_contract(
+        creator,
+        Addr::unchecked(sg721_base_address),
+        &Empty {},
+        sg721_code_id,
+    );
 
-    assert!(collection_info.royalty_info.unwrap().updated_at.is_some(),);
+    assert!(res.is_ok());
 }