Repository: CosmWasm/cosmwasm
Pull request #906 (2021-04-28 14:44:45)

Make BlockInfo::time a Timestamp


Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index f5b2ce5e6..b7756f65b 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -198,6 +198,8 @@ and this project adheres to
   ([#879])
 - cosmwasm-schema: Make first argument non-mutable in `export_schema_with_title`
   for consistency with `export_schema`.
+- cosmwasm-std: The block time in `BlockInfo::time` is now a `Timestamp`.
+  `BlockInfo::time_nanos` was removed.
 
 [#696]: https://github.com/CosmWasm/cosmwasm/issues/696
 [#697]: https://github.com/CosmWasm/cosmwasm/issues/697
diff --git a/MIGRATING.md b/MIGRATING.md
index 495a0091b..920650548 100644
--- a/MIGRATING.md
+++ b/MIGRATING.md
@@ -323,6 +323,14 @@ major releases of `cosmwasm`. Note that you can also view the
 
   [distribution docs]: https://docs.cosmos.network/v0.42/modules/distribution/
 
+- The block time in `env.block.time` is now a `Timestamp` which stores
+  nanosecond precision. `env.block.time_nanos` was removed. If you need the
+  compnents as before, use
+  ```rust
+  let seconds = env.block.time.nanos() / 1_000_000_000;
+  let nsecs = env.block.time.nanos() % 1_000_000_000;
+  ```
+
 ## 0.12 -> 0.13
 
 - The minimum Rust supported version for 0.13 is 1.47.0. Verify your Rust
diff --git a/contracts/ibc-reflect-send/schema/account_response.json b/contracts/ibc-reflect-send/schema/account_response.json
index 83009f9c3..8e57ee5ac 100644
--- a/contracts/ibc-reflect-send/schema/account_response.json
+++ b/contracts/ibc-reflect-send/schema/account_response.json
@@ -9,9 +9,11 @@
   "properties": {
     "last_update_time": {
       "description": "last block balance was updated (0 is never)",
-      "type": "integer",
-      "format": "uint64",
-      "minimum": 0.0
+      "allOf": [
+        {
+          "$ref": "#/definitions/Timestamp"
+        }
+      ]
     },
     "remote_addr": {
       "description": "in normal cases, it should be set, but there is a delay between binding the channel and making a query and in that time it is empty",
@@ -43,9 +45,21 @@
         }
       }
     },
+    "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.",
+      "allOf": [
+        {
+          "$ref": "#/definitions/Uint64"
+        }
+      ]
+    },
     "Uint128": {
       "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 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 `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```",
       "type": "string"
+    },
+    "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/ibc-reflect-send/schema/list_accounts_response.json b/contracts/ibc-reflect-send/schema/list_accounts_response.json
index ec9921118..b648789fb 100644
--- a/contracts/ibc-reflect-send/schema/list_accounts_response.json
+++ b/contracts/ibc-reflect-send/schema/list_accounts_response.json
@@ -27,9 +27,11 @@
         },
         "last_update_time": {
           "description": "last block balance was updated (0 is never)",
-          "type": "integer",
-          "format": "uint64",
-          "minimum": 0.0
+          "allOf": [
+            {
+              "$ref": "#/definitions/Timestamp"
+            }
+          ]
         },
         "remote_addr": {
           "description": "in normal cases, it should be set, but there is a delay between binding the channel and making a query and in that time it is empty",
@@ -61,9 +63,21 @@
         }
       }
     },
+    "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.",
+      "allOf": [
+        {
+          "$ref": "#/definitions/Uint64"
+        }
+      ]
+    },
     "Uint128": {
       "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 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 `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```",
       "type": "string"
+    },
+    "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/ibc-reflect-send/src/contract.rs b/contracts/ibc-reflect-send/src/contract.rs
index 4dd923256..266ee6b49 100644
--- a/contracts/ibc-reflect-send/src/contract.rs
+++ b/contracts/ibc-reflect-send/src/contract.rs
@@ -91,7 +91,7 @@ pub fn handle_send_msgs(
     let msg = IbcMsg::SendPacket {
         channel_id,
         data: to_binary(&packet)?,
-        timeout: env.block.timestamp().plus_seconds(PACKET_LIFETIME).into(),
+        timeout: env.block.time.plus_seconds(PACKET_LIFETIME).into(),
     };
 
     Ok(Response {
@@ -121,7 +121,7 @@ pub fn handle_check_remote_balance(
     let msg = IbcMsg::SendPacket {
         channel_id,
         data: to_binary(&packet)?,
-        timeout: env.block.timestamp().plus_seconds(PACKET_LIFETIME).into(),
+        timeout: env.block.time.plus_seconds(PACKET_LIFETIME).into(),
     };
 
     Ok(Response {
@@ -171,7 +171,7 @@ pub fn handle_send_funds(
         channel_id: transfer_channel_id,
         to_address: remote_addr,
         amount,
-        timeout: env.block.timestamp().plus_seconds(PACKET_LIFETIME).into(),
+        timeout: env.block.time.plus_seconds(PACKET_LIFETIME).into(),
     };
 
     Ok(Response {
diff --git a/contracts/ibc-reflect-send/src/ibc.rs b/contracts/ibc-reflect-send/src/ibc.rs
index 615ecdc4a..2be0b1ed5 100644
--- a/contracts/ibc-reflect-send/src/ibc.rs
+++ b/contracts/ibc-reflect-send/src/ibc.rs
@@ -58,7 +58,7 @@ pub fn ibc_channel_connect(
     let msg = IbcMsg::SendPacket {
         channel_id: channel_id.clone(),
         data: to_binary(&packet)?,
-        timeout: env.block.timestamp().plus_seconds(PACKET_LIFETIME).into(),
+        timeout: env.block.time.plus_seconds(PACKET_LIFETIME).into(),
     };
 
     Ok(IbcBasicResponse {
@@ -334,7 +334,7 @@ mod tests {
         let acct: AccountResponse = from_slice(&r).unwrap();
         assert!(acct.remote_addr.is_none());
         assert!(acct.remote_balance.is_empty());
-        assert_eq!(0, acct.last_update_time);
+        assert_eq!(0, acct.last_update_time.nanos());
 
         // now get feedback from WhoAmI packet
         let remote_addr = "account-789";
@@ -348,7 +348,7 @@ mod tests {
         let acct: AccountResponse = from_slice(&r).unwrap();
         assert_eq!(acct.remote_addr.unwrap(), remote_addr);
         assert!(acct.remote_balance.is_empty());
-        assert_eq!(0, acct.last_update_time);
+        assert_eq!(0, acct.last_update_time.nanos());
     }
 
     #[test]
diff --git a/contracts/ibc-reflect-send/src/msg.rs b/contracts/ibc-reflect-send/src/msg.rs
index 2bc47c430..ac69b36d6 100644
--- a/contracts/ibc-reflect-send/src/msg.rs
+++ b/contracts/ibc-reflect-send/src/msg.rs
@@ -1,4 +1,4 @@
-use cosmwasm_std::{Coin, CosmosMsg, Empty};
+use cosmwasm_std::{Coin, CosmosMsg, Empty, Timestamp};
 use schemars::JsonSchema;
 use serde::{Deserialize, Serialize};
 
@@ -62,7 +62,7 @@ pub struct ListAccountsResponse {
 pub struct AccountInfo {
     pub channel_id: String,
     /// last block balance was updated (0 is never)
-    pub last_update_time: u64,
+    pub last_update_time: Timestamp,
     /// in normal cases, it should be set, but there is a delay between binding
     /// the channel and making a query and in that time it is empty
     pub remote_addr: Option<String>,
@@ -83,7 +83,7 @@ impl AccountInfo {
 #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
 pub struct AccountResponse {
     /// last block balance was updated (0 is never)
-    pub last_update_time: u64,
+    pub last_update_time: Timestamp,
     /// in normal cases, it should be set, but there is a delay between binding
     /// the channel and making a query and in that time it is empty
     pub remote_addr: Option<String>,
diff --git a/contracts/ibc-reflect-send/src/state.rs b/contracts/ibc-reflect-send/src/state.rs
index 7e8e5e165..56b2cf2ee 100644
--- a/contracts/ibc-reflect-send/src/state.rs
+++ b/contracts/ibc-reflect-send/src/state.rs
@@ -1,6 +1,6 @@
 use serde::{Deserialize, Serialize};
 
-use cosmwasm_std::{Addr, Coin, Storage};
+use cosmwasm_std::{Addr, Coin, Storage, Timestamp};
 use cosmwasm_storage::{
     bucket, bucket_read, singleton, singleton_read, Bucket, ReadonlyBucket, ReadonlySingleton,
     Singleton,
@@ -17,7 +17,7 @@ pub struct Config {
 #[derive(Serialize, Deserialize, Clone, Default, Debug, PartialEq)]
 pub struct AccountData {
     /// last block balance was updated (0 is never)
-    pub last_update_time: u64,
+    pub last_update_time: Timestamp,
     /// In normal cases, it should be set, but there is a delay between binding
     /// the channel and making a query and in that time it is empty.
     ///
diff --git a/packages/std/src/mock.rs b/packages/std/src/mock.rs
index 6e926edd0..33a2ad20c 100644
--- a/packages/std/src/mock.rs
+++ b/packages/std/src/mock.rs
@@ -21,6 +21,7 @@ use crate::query::{
 use crate::results::{ContractResult, Empty, SystemResult};
 use crate::serde::{from_slice, to_binary};
 use crate::storage::MemoryStorage;
+use crate::timestamp::Timestamp;
 use crate::traits::{Api, Querier, QuerierResult};
 use crate::types::{BlockInfo, ContractInfo, Env, MessageInfo};
 
@@ -189,8 +190,7 @@ pub fn mock_env() -> Env {
     Env {
         block: BlockInfo {
             height: 12_345,
-            time: 1_571_797_419,
-            time_nanos: 879305533,
+            time: Timestamp::from_nanos(1_571_797_419_879_305_533),
             chain_id: "cosmos-testnet-14002".to_string(),
         },
         contract: ContractInfo {
diff --git a/packages/std/src/timestamp.rs b/packages/std/src/timestamp.rs
index c0e92eeea..e9976b164 100644
--- a/packages/std/src/timestamp.rs
+++ b/packages/std/src/timestamp.rs
@@ -31,6 +31,11 @@ impl Timestamp {
         let nanos = Uint64::new(self.0.u64() + addition);
         Timestamp(nanos)
     }
+
+    /// Returns nanoseconds since epoch
+    pub fn nanos(&self) -> u64 {
+        self.0.u64()
+    }
 }
 
 #[cfg(test)]
@@ -68,4 +73,12 @@ mod tests {
         let sum = Timestamp::from_nanos(123).plus_nanos(0);
         assert_eq!(sum.0.u64(), 123);
     }
+
+    #[test]
+    fn timestamp_nanos() {
+        let sum = Timestamp::from_nanos(123);
+        assert_eq!(sum.nanos(), 123);
+        let sum = Timestamp::from_nanos(0);
+        assert_eq!(sum.nanos(), 0);
+    }
 }
diff --git a/packages/std/src/types.rs b/packages/std/src/types.rs
index d548a46da..78fb8c13a 100644
--- a/packages/std/src/types.rs
+++ b/packages/std/src/types.rs
@@ -16,22 +16,18 @@ pub struct BlockInfo {
     /// Absolute time of the block creation in seconds since the UNIX epoch (00:00:00 on 1970-01-01 UTC).
     ///
     /// The source of this is the [BFT Time in Tendermint](https://docs.tendermint.com/master/spec/consensus/bft-time.html),
-    /// converted from nanoseconds to second precision by truncating the fractioal part.
-    pub time: u64,
-    /// The fractional part of the block time in nanoseconds since `time` (0 to 999999999).
-    /// Add this to `time` if you need a high precision block time.
+    /// which has the same nanosecond precision as the `Timestamp` type.
     ///
     /// # Examples
     ///
     /// Using chrono:
     ///
     /// ```
-    /// # use cosmwasm_std::{Addr, BlockInfo, ContractInfo, Env, MessageInfo};
+    /// # use cosmwasm_std::{Addr, BlockInfo, ContractInfo, Env, MessageInfo, Timestamp};
     /// # let env = Env {
     /// #     block: BlockInfo {
     /// #         height: 12_345,
-    /// #         time: 1_571_797_419,
-    /// #         time_nanos: 879305533,
+    /// #         time: Timestamp::from_nanos(1_571_797_419_879_305_533),
     /// #         chain_id: "cosmos-testnet-14002".to_string(),
     /// #     },
     /// #     contract: ContractInfo {
@@ -40,38 +36,31 @@ pub struct BlockInfo {
     /// # };
     /// # extern crate chrono;
     /// use chrono::NaiveDateTime;
-    /// let dt = NaiveDateTime::from_timestamp(env.block.time as i64, env.block.time_nanos as u32);
+    /// let seconds = env.block.time.nanos() / 1_000_000_000;
+    /// let nsecs = env.block.time.nanos() % 1_000_000_000;
+    /// let dt = NaiveDateTime::from_timestamp(seconds as i64, nsecs as u32);
     /// ```
     ///
     /// Creating a simple millisecond-precision timestamp (as used in JavaScript):
     ///
     /// ```
-    /// # use cosmwasm_std::{Addr, BlockInfo, ContractInfo, Env, MessageInfo};
+    /// # use cosmwasm_std::{Addr, BlockInfo, ContractInfo, Env, MessageInfo, Timestamp};
     /// # let env = Env {
     /// #     block: BlockInfo {
     /// #         height: 12_345,
-    /// #         time: 1_571_797_419,
-    /// #         time_nanos: 879305533,
+    /// #         time: Timestamp::from_nanos(1_571_797_419_879_305_533),
     /// #         chain_id: "cosmos-testnet-14002".to_string(),
     /// #     },
     /// #     contract: ContractInfo {
     /// #         address: Addr::unchecked("contract"),
     /// #     },
     /// # };
-    /// let millis = (env.block.time * 1_000) + (env.block.time_nanos / 1_000_000);
+    /// let millis = env.block.time.nanos() / 1_000_000;
     /// ```
-    pub time_nanos: u64,
+    pub time: Timestamp,
     pub chain_id: String,
 }
 
-impl BlockInfo {
-    /// Returns the block creation time as a Timestamp in nanosecond precision
-    pub fn timestamp(&self) -> Timestamp {
-        let nanos_since_epoch = self.time * 1_000_000_000 + self.time_nanos;
-        Timestamp::from_nanos(nanos_since_epoch)
-    }
-}
-
 /// Additional information from [MsgInstantiateContract] and [MsgExecuteContract], which is passed
 /// along with the contract execution message into the `instantiate` and `execute` entry points.
 ///
diff --git a/packages/vm/src/instance.rs b/packages/vm/src/instance.rs
index 61de320d2..326ca5fdc 100644
--- a/packages/vm/src/instance.rs
+++ b/packages/vm/src/instance.rs
@@ -606,7 +606,7 @@ mod tests {
 
         let report2 = instance.create_gas_report();
         assert_eq!(report2.used_externally, 73);
-        assert_eq!(report2.used_internally, 39802);
+        assert_eq!(report2.used_internally, 38804);
         assert_eq!(report2.limit, LIMIT);
         assert_eq!(
             report2.remaining,
@@ -805,7 +805,7 @@ mod singlepass_tests {
             .unwrap();
 
         let init_used = orig_gas - instance.get_gas_left();
-        assert_eq!(init_used, 39875);
+        assert_eq!(init_used, 38877);
     }
 
     #[test]
@@ -828,7 +828,7 @@ mod singlepass_tests {
             .unwrap();
 
         let execute_used = gas_before_execute - instance.get_gas_left();
-        assert_eq!(execute_used, 158979);
+        assert_eq!(execute_used, 157981);
     }
 
     #[test]
@@ -862,6 +862,6 @@ mod singlepass_tests {
         assert_eq!(answer.as_slice(), b"{\"verifier\":\"verifies\"}");
 
         let query_used = gas_before_query - instance.get_gas_left();
-        assert_eq!(query_used, 29998);
+        assert_eq!(query_used, 28999);
     }
 }


Test patch:
diff --git a/contracts/ibc-reflect-send/tests/integration.rs b/contracts/ibc-reflect-send/tests/integration.rs
index a6d3fbd6a..97743f104 100644
--- a/contracts/ibc-reflect-send/tests/integration.rs
+++ b/contracts/ibc-reflect-send/tests/integration.rs
@@ -132,7 +132,7 @@ fn proper_handshake_flow() {
     let acct = get_account(&mut deps, channel_id);
     assert!(acct.remote_addr.is_none());
     assert!(acct.remote_balance.is_empty());
-    assert_eq!(0, acct.last_update_time);
+    assert_eq!(0, acct.last_update_time.nanos());
 
     // now get feedback from WhoAmI packet
     let remote_addr = "account-789";
@@ -142,7 +142,7 @@ fn proper_handshake_flow() {
     let acct = get_account(&mut deps, channel_id);
     assert_eq!(acct.remote_addr.unwrap(), remote_addr);
     assert!(acct.remote_balance.is_empty());
-    assert_eq!(0, acct.last_update_time);
+    assert_eq!(0, acct.last_update_time.nanos());
 }
 
 #[test]
diff --git a/packages/vm/src/testing/mock.rs b/packages/vm/src/testing/mock.rs
index 6bdbfbd7d..d0d8f85d5 100644
--- a/packages/vm/src/testing/mock.rs
+++ b/packages/vm/src/testing/mock.rs
@@ -1,5 +1,5 @@
 use cosmwasm_std::testing::{digit_sum, riffle_shuffle};
-use cosmwasm_std::{Addr, BlockInfo, Coin, ContractInfo, Env, MessageInfo};
+use cosmwasm_std::{Addr, BlockInfo, Coin, ContractInfo, Env, MessageInfo, Timestamp};
 
 use super::querier::MockQuerier;
 use super::storage::MockStorage;
@@ -144,8 +144,7 @@ pub fn mock_env() -> Env {
     Env {
         block: BlockInfo {
             height: 12_345,
-            time: 1_571_797_419,
-            time_nanos: 879305533,
+            time: Timestamp::from_nanos(1_571_797_419_879_305_533),
             chain_id: "cosmos-testnet-14002".to_string(),
         },
         contract: ContractInfo {