Skip to content

Commit ddcd300

Browse files
authored
Merge pull request #1944 from CosmWasm/1835-checksum-type
Use `Checksum` for `CodeInfoResponse`
2 parents 9907b49 + 80e6886 commit ddcd300

File tree

17 files changed

+310
-137
lines changed

17 files changed

+310
-137
lines changed

CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,8 @@ and this project adheres to
5353
nested msg struct like in other messages. ([#1926])
5454
- cosmwasm-vm: Add `AnalysisReport::entrypoints` and make
5555
`AnalysisReport::required_capabilities` a `BTreeSet`. ([#1949])
56+
- cosmwasm-std: Add `Checksum` type and change type of
57+
`CodeInfoResponse::checksum` to that. ([#1944])
5658

5759
[#1874]: https://github.com/CosmWasm/cosmwasm/pull/1874
5860
[#1876]: https://github.com/CosmWasm/cosmwasm/pull/1876
@@ -66,6 +68,7 @@ and this project adheres to
6668
[#1939]: https://github.com/CosmWasm/cosmwasm/pull/1939
6769
[#1940]: https://github.com/CosmWasm/cosmwasm/pull/1940
6870
[#1941]: https://github.com/CosmWasm/cosmwasm/pull/1941
71+
[#1944]: https://github.com/CosmWasm/cosmwasm/pull/1944
6972
[#1949]: https://github.com/CosmWasm/cosmwasm/pull/1949
7073

7174
### Removed
@@ -83,6 +86,8 @@ and this project adheres to
8386
using `Instance::set_debug_handler`. ([#1953])
8487
- cosmwasm-vm: Remove `allow_interface_version_7` feature and all related
8588
functionality. ([#1952])
89+
- cosmwasm-vm: Remove `Checksum`. Use `cosmwasm_std::Checksum` instead.
90+
([#1944])
8691

8792
[cw-storage-plus]: https://github.com/CosmWasm/cw-storage-plus
8893
[#1875]: https://github.com/CosmWasm/cosmwasm/pull/1875

contracts/virus/src/contract.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -60,9 +60,9 @@ pub fn execute_spread(
6060

6161
attributes.push(Attribute::new(format!("path{i}"), path.clone()));
6262

63-
let address = deps
64-
.api
65-
.addr_humanize(&instantiate2_address(&checksum, &creator, &salt)?)?;
63+
let address =
64+
deps.api
65+
.addr_humanize(&instantiate2_address(checksum.as_ref(), &creator, &salt)?)?;
6666
attributes.push(Attribute::new(
6767
format!("predicted_address{i}"),
6868
address.clone(),

packages/go-gen/src/main.rs

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -198,7 +198,7 @@ pub fn build_enum_variant(
198198
#[cfg(test)]
199199
mod tests {
200200
use cosmwasm_schema::cw_serde;
201-
use cosmwasm_std::{Binary, Empty, HexBinary, Uint128};
201+
use cosmwasm_std::{Binary, Checksum, Empty, HexBinary, Uint128};
202202

203203
use super::*;
204204

@@ -241,6 +241,7 @@ mod tests {
241241
binary: Binary,
242242
nested_binary: Vec<Option<Binary>>,
243243
hex_binary: HexBinary,
244+
checksum: Checksum,
244245
uint128: Uint128,
245246
}
246247

@@ -252,7 +253,8 @@ mod tests {
252253
r#"
253254
type SpecialTypes struct {
254255
Binary []byte `json:"binary"`
255-
HexBinary Checksum `json:"hex_binary"`
256+
Checksum Checksum `json:"checksum"`
257+
HexBinary string `json:"hex_binary"`
256258
NestedBinary []*[]byte `json:"nested_binary"`
257259
Uint128 string `json:"uint128"`
258260
}"#,
@@ -357,7 +359,7 @@ mod tests {
357359
compare_codes!(cosmwasm_std::DelegatorValidatorsResponse);
358360
// wasm
359361
compare_codes!(cosmwasm_std::ContractInfoResponse);
360-
// compare_codes!(cosmwasm_std::CodeInfoResponse); // TODO: Checksum type and "omitempty"
362+
compare_codes!(cosmwasm_std::CodeInfoResponse);
361363
}
362364

363365
#[test]

packages/go-gen/src/schema.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -261,7 +261,8 @@ pub fn custom_type_of(ty: &str) -> Option<&str> {
261261
match ty {
262262
"Uint128" => Some("string"),
263263
"Binary" => Some("[]byte"),
264-
"HexBinary" => Some("Checksum"),
264+
"HexBinary" => Some("string"),
265+
"Checksum" => Some("Checksum"),
265266
"Addr" => Some("string"),
266267
"Decimal" => Some("string"),
267268
"Decimal256" => Some("string"),
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
type CodeInfoResponse struct {
2-
Checksum Checksum `json:"checksum,omitempty"`
2+
Checksum Checksum `json:"checksum"` // before wasmvm 2.0.0, this was `omitempty` (https://github.com/CosmWasm/wasmvm/issues/471)
33
CodeID uint64 `json:"code_id"`
44
Creator string `json:"creator"`
55
}

packages/schema/src/idl.rs

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -146,10 +146,55 @@ pub enum EncodeError {
146146

147147
#[cfg(test)]
148148
mod tests {
149+
use crate::schema_for;
150+
149151
use super::*;
150152

151153
#[test]
152154
fn version_is_semver() {
153155
semver::Version::parse(IDL_VERSION).unwrap();
154156
}
157+
158+
#[test]
159+
fn to_schema_files_works() {
160+
let empty = Api {
161+
contract_name: "my_contract".to_string(),
162+
contract_version: "1.2.3".to_string(),
163+
instantiate: None,
164+
execute: None,
165+
query: None,
166+
migrate: None,
167+
sudo: None,
168+
responses: None,
169+
};
170+
171+
let files = empty.render().to_schema_files().unwrap();
172+
assert_eq!(files, []);
173+
174+
#[derive(schemars::JsonSchema)]
175+
struct TestMsg {}
176+
177+
let full = Api {
178+
contract_name: "my_contract".to_string(),
179+
contract_version: "1.2.3".to_string(),
180+
instantiate: Some(schema_for!(TestMsg)),
181+
execute: Some(schema_for!(TestMsg)),
182+
query: Some(schema_for!(TestMsg)),
183+
migrate: Some(schema_for!(TestMsg)),
184+
sudo: Some(schema_for!(TestMsg)),
185+
responses: Some(BTreeMap::from([(
186+
"TestMsg".to_string(),
187+
schema_for!(TestMsg),
188+
)])),
189+
};
190+
191+
let files = full.render().to_schema_files().unwrap();
192+
assert_eq!(files.len(), 6);
193+
assert_eq!(files[0].0, "instantiate.json");
194+
assert_eq!(files[1].0, "execute.json");
195+
assert_eq!(files[2].0, "query.json");
196+
assert_eq!(files[3].0, "migrate.json");
197+
assert_eq!(files[4].0, "sudo.json");
198+
assert_eq!(files[5].0, "response_to_TestMsg.json");
199+
}
155200
}

packages/std/src/checksum.rs

Lines changed: 229 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,229 @@
1+
use core::fmt;
2+
3+
use schemars::JsonSchema;
4+
use serde::{de, ser, Deserialize, Deserializer, Serialize};
5+
use sha2::{Digest, Sha256};
6+
use thiserror::Error;
7+
8+
use crate::{StdError, StdResult};
9+
10+
/// A SHA-256 checksum of a Wasm blob, used to identify a Wasm code.
11+
/// This must remain stable since this checksum is stored in the blockchain state.
12+
///
13+
/// This is often referred to as "code ID" in go-cosmwasm, even if code ID
14+
/// usually refers to an auto-incrementing number.
15+
#[derive(JsonSchema, Debug, Copy, Clone, PartialEq, Eq, Hash)]
16+
pub struct Checksum(#[schemars(with = "String")] [u8; 32]);
17+
18+
impl Checksum {
19+
pub fn generate(wasm: &[u8]) -> Self {
20+
Checksum(Sha256::digest(wasm).into())
21+
}
22+
23+
/// Tries to parse the given hex string into a checksum.
24+
/// Errors if the string contains non-hex characters or does not contain 32 bytes.
25+
pub fn from_hex(input: &str) -> StdResult<Self> {
26+
let mut binary = [0u8; 32];
27+
hex::decode_to_slice(input, &mut binary).map_err(StdError::invalid_hex)?;
28+
29+
Ok(Self(binary))
30+
}
31+
32+
/// Creates a lowercase hex encoded copy of this checksum.
33+
///
34+
/// This takes an owned `self` instead of a reference because `Checksum` is cheap to `Copy`.
35+
pub fn to_hex(self) -> String {
36+
self.to_string()
37+
}
38+
39+
/// Returns a reference to the inner bytes of this checksum as a slice.
40+
/// If you need a reference to the array, use [`AsRef::as_ref`].
41+
pub fn as_slice(&self) -> &[u8] {
42+
&self.0
43+
}
44+
}
45+
46+
impl fmt::Display for Checksum {
47+
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
48+
for byte in self.0.iter() {
49+
write!(f, "{byte:02x}")?;
50+
}
51+
Ok(())
52+
}
53+
}
54+
55+
impl From<[u8; 32]> for Checksum {
56+
fn from(data: [u8; 32]) -> Self {
57+
Checksum(data)
58+
}
59+
}
60+
61+
impl AsRef<[u8; 32]> for Checksum {
62+
fn as_ref(&self) -> &[u8; 32] {
63+
&self.0
64+
}
65+
}
66+
67+
/// Serializes as a hex string
68+
impl Serialize for Checksum {
69+
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
70+
where
71+
S: ser::Serializer,
72+
{
73+
serializer.serialize_str(&self.to_hex())
74+
}
75+
}
76+
77+
/// Deserializes as a hex string
78+
impl<'de> Deserialize<'de> for Checksum {
79+
fn deserialize<D>(deserializer: D) -> Result<Checksum, D::Error>
80+
where
81+
D: Deserializer<'de>,
82+
{
83+
deserializer.deserialize_str(ChecksumVisitor)
84+
}
85+
}
86+
87+
struct ChecksumVisitor;
88+
89+
impl<'de> de::Visitor<'de> for ChecksumVisitor {
90+
type Value = Checksum;
91+
92+
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
93+
formatter.write_str("valid hex encoded 32 byte checksum")
94+
}
95+
96+
fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
97+
where
98+
E: de::Error,
99+
{
100+
match Checksum::from_hex(v) {
101+
Ok(data) => Ok(data),
102+
Err(_) => Err(E::custom(format!("invalid checksum: {v}"))),
103+
}
104+
}
105+
}
106+
107+
#[derive(Error, Debug)]
108+
#[error("Checksum not of length 32")]
109+
pub struct ChecksumError;
110+
111+
impl TryFrom<&[u8]> for Checksum {
112+
type Error = ChecksumError;
113+
114+
fn try_from(value: &[u8]) -> Result<Self, Self::Error> {
115+
if value.len() != 32 {
116+
return Err(ChecksumError);
117+
}
118+
let mut data = [0u8; 32];
119+
data.copy_from_slice(value);
120+
Ok(Checksum(data))
121+
}
122+
}
123+
124+
impl From<Checksum> for Vec<u8> {
125+
fn from(original: Checksum) -> Vec<u8> {
126+
original.0.into()
127+
}
128+
}
129+
130+
#[cfg(test)]
131+
mod tests {
132+
use super::*;
133+
134+
use crate::to_json_string;
135+
136+
#[test]
137+
fn generate_works() {
138+
let wasm = vec![0x68, 0x69, 0x6a];
139+
let checksum = Checksum::generate(&wasm);
140+
141+
// echo -n "hij" | sha256sum
142+
let expected = [
143+
0x72, 0x2c, 0x8c, 0x99, 0x3f, 0xd7, 0x5a, 0x76, 0x27, 0xd6, 0x9e, 0xd9, 0x41, 0x34,
144+
0x4f, 0xe2, 0xa1, 0x42, 0x3a, 0x3e, 0x75, 0xef, 0xd3, 0xe6, 0x77, 0x8a, 0x14, 0x28,
145+
0x84, 0x22, 0x71, 0x04,
146+
];
147+
assert_eq!(checksum.0, expected);
148+
}
149+
150+
#[test]
151+
fn implemented_display() {
152+
let wasm = vec![0x68, 0x69, 0x6a];
153+
let checksum = Checksum::generate(&wasm);
154+
// echo -n "hij" | sha256sum
155+
let embedded = format!("Check: {checksum}");
156+
assert_eq!(
157+
embedded,
158+
"Check: 722c8c993fd75a7627d69ed941344fe2a1423a3e75efd3e6778a142884227104"
159+
);
160+
assert_eq!(
161+
checksum.to_string(),
162+
"722c8c993fd75a7627d69ed941344fe2a1423a3e75efd3e6778a142884227104"
163+
);
164+
}
165+
166+
#[test]
167+
fn from_hex_works() {
168+
// echo -n "hij" | sha256sum
169+
let checksum = "722c8c993fd75a7627d69ed941344fe2a1423a3e75efd3e6778a142884227104";
170+
let parsed = Checksum::from_hex(checksum).unwrap();
171+
assert_eq!(parsed, Checksum::generate(b"hij"));
172+
// should be inverse of `to_hex`
173+
assert_eq!(parsed.to_hex(), checksum);
174+
175+
// invalid hex
176+
let too_short = "722c8c993fd75a7627d69ed941344fe2a1423a3e75efd3e6778a1428842271";
177+
assert!(Checksum::from_hex(too_short).is_err());
178+
let invalid_char = "722c8c993fd75a7627d69ed941344fe2a1423a3e75efd3e6778a1428842271g4";
179+
assert!(Checksum::from_hex(invalid_char).is_err());
180+
let too_long = "722c8c993fd75a7627d69ed941344fe2a1423a3e75efd3e6778a14288422710400";
181+
assert!(Checksum::from_hex(too_long).is_err());
182+
}
183+
184+
#[test]
185+
fn to_hex_works() {
186+
let wasm = vec![0x68, 0x69, 0x6a];
187+
let checksum = Checksum::generate(&wasm);
188+
// echo -n "hij" | sha256sum
189+
assert_eq!(
190+
checksum.to_hex(),
191+
"722c8c993fd75a7627d69ed941344fe2a1423a3e75efd3e6778a142884227104"
192+
);
193+
}
194+
195+
#[test]
196+
fn into_vec_works() {
197+
let checksum = Checksum::generate(&[12u8; 17]);
198+
let as_vec: Vec<u8> = checksum.into();
199+
assert_eq!(as_vec, checksum.0);
200+
}
201+
202+
#[test]
203+
fn ref_conversions_work() {
204+
let checksum = Checksum::generate(&[12u8; 17]);
205+
// as_ref
206+
let _: &[u8; 32] = checksum.as_ref();
207+
let _: &[u8] = checksum.as_ref();
208+
// as_slice
209+
let _: &[u8; 32] = checksum.as_ref();
210+
let _: &[u8] = checksum.as_ref();
211+
}
212+
213+
#[test]
214+
fn serde_works() {
215+
// echo -n "hij" | sha256sum
216+
let checksum =
217+
Checksum::from_hex("722c8c993fd75a7627d69ed941344fe2a1423a3e75efd3e6778a142884227104")
218+
.unwrap();
219+
220+
let serialized = to_json_string(&checksum).unwrap();
221+
assert_eq!(
222+
serialized,
223+
"\"722c8c993fd75a7627d69ed941344fe2a1423a3e75efd3e6778a142884227104\""
224+
);
225+
226+
let deserialized: Checksum = serde_json::from_str(&serialized).unwrap();
227+
assert_eq!(deserialized, checksum);
228+
}
229+
}

packages/std/src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ extern crate alloc;
88
mod addresses;
99
mod assertions;
1010
mod binary;
11+
mod checksum;
1112
mod coin;
1213
mod coins;
1314
mod conversion;
@@ -41,6 +42,7 @@ pub mod storage_keys;
4142

4243
pub use crate::addresses::{instantiate2_address, Addr, CanonicalAddr, Instantiate2AddressError};
4344
pub use crate::binary::Binary;
45+
pub use crate::checksum::{Checksum, ChecksumError};
4446
pub use crate::coin::{coin, coins, has_coins, Coin};
4547
pub use crate::coins::Coins;
4648
pub use crate::deps::{Deps, DepsMut, OwnedDeps};

0 commit comments

Comments
 (0)