Skip to content

Commit

Permalink
Merge pull request #1944 from CosmWasm/1835-checksum-type
Browse files Browse the repository at this point in the history
Use `Checksum` for `CodeInfoResponse`
  • Loading branch information
chipshort authored Nov 21, 2023
2 parents 9907b49 + 80e6886 commit ddcd300
Show file tree
Hide file tree
Showing 17 changed files with 310 additions and 137 deletions.
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,8 @@ and this project adheres to
nested msg struct like in other messages. ([#1926])
- cosmwasm-vm: Add `AnalysisReport::entrypoints` and make
`AnalysisReport::required_capabilities` a `BTreeSet`. ([#1949])
- cosmwasm-std: Add `Checksum` type and change type of
`CodeInfoResponse::checksum` to that. ([#1944])

[#1874]: https://github.com/CosmWasm/cosmwasm/pull/1874
[#1876]: https://github.com/CosmWasm/cosmwasm/pull/1876
Expand All @@ -66,6 +68,7 @@ and this project adheres to
[#1939]: https://github.com/CosmWasm/cosmwasm/pull/1939
[#1940]: https://github.com/CosmWasm/cosmwasm/pull/1940
[#1941]: https://github.com/CosmWasm/cosmwasm/pull/1941
[#1944]: https://github.com/CosmWasm/cosmwasm/pull/1944
[#1949]: https://github.com/CosmWasm/cosmwasm/pull/1949

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

[cw-storage-plus]: https://github.com/CosmWasm/cw-storage-plus
[#1875]: https://github.com/CosmWasm/cosmwasm/pull/1875
Expand Down
6 changes: 3 additions & 3 deletions contracts/virus/src/contract.rs
Original file line number Diff line number Diff line change
Expand Up @@ -60,9 +60,9 @@ pub fn execute_spread(

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

let address = deps
.api
.addr_humanize(&instantiate2_address(&checksum, &creator, &salt)?)?;
let address =
deps.api
.addr_humanize(&instantiate2_address(checksum.as_ref(), &creator, &salt)?)?;
attributes.push(Attribute::new(
format!("predicted_address{i}"),
address.clone(),
Expand Down
8 changes: 5 additions & 3 deletions packages/go-gen/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -198,7 +198,7 @@ pub fn build_enum_variant(
#[cfg(test)]
mod tests {
use cosmwasm_schema::cw_serde;
use cosmwasm_std::{Binary, Empty, HexBinary, Uint128};
use cosmwasm_std::{Binary, Checksum, Empty, HexBinary, Uint128};

use super::*;

Expand Down Expand Up @@ -241,6 +241,7 @@ mod tests {
binary: Binary,
nested_binary: Vec<Option<Binary>>,
hex_binary: HexBinary,
checksum: Checksum,
uint128: Uint128,
}

Expand All @@ -252,7 +253,8 @@ mod tests {
r#"
type SpecialTypes struct {
Binary []byte `json:"binary"`
HexBinary Checksum `json:"hex_binary"`
Checksum Checksum `json:"checksum"`
HexBinary string `json:"hex_binary"`
NestedBinary []*[]byte `json:"nested_binary"`
Uint128 string `json:"uint128"`
}"#,
Expand Down Expand Up @@ -357,7 +359,7 @@ mod tests {
compare_codes!(cosmwasm_std::DelegatorValidatorsResponse);
// wasm
compare_codes!(cosmwasm_std::ContractInfoResponse);
// compare_codes!(cosmwasm_std::CodeInfoResponse); // TODO: Checksum type and "omitempty"
compare_codes!(cosmwasm_std::CodeInfoResponse);
}

#[test]
Expand Down
3 changes: 2 additions & 1 deletion packages/go-gen/src/schema.rs
Original file line number Diff line number Diff line change
Expand Up @@ -261,7 +261,8 @@ pub fn custom_type_of(ty: &str) -> Option<&str> {
match ty {
"Uint128" => Some("string"),
"Binary" => Some("[]byte"),
"HexBinary" => Some("Checksum"),
"HexBinary" => Some("string"),
"Checksum" => Some("Checksum"),
"Addr" => Some("string"),
"Decimal" => Some("string"),
"Decimal256" => Some("string"),
Expand Down
2 changes: 1 addition & 1 deletion packages/go-gen/tests/cosmwasm_std__CodeInfoResponse.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
type CodeInfoResponse struct {
Checksum Checksum `json:"checksum,omitempty"`
Checksum Checksum `json:"checksum"` // before wasmvm 2.0.0, this was `omitempty` (https://github.com/CosmWasm/wasmvm/issues/471)
CodeID uint64 `json:"code_id"`
Creator string `json:"creator"`
}
45 changes: 45 additions & 0 deletions packages/schema/src/idl.rs
Original file line number Diff line number Diff line change
Expand Up @@ -146,10 +146,55 @@ pub enum EncodeError {

#[cfg(test)]
mod tests {
use crate::schema_for;

use super::*;

#[test]
fn version_is_semver() {
semver::Version::parse(IDL_VERSION).unwrap();
}

#[test]
fn to_schema_files_works() {
let empty = Api {
contract_name: "my_contract".to_string(),
contract_version: "1.2.3".to_string(),
instantiate: None,
execute: None,
query: None,
migrate: None,
sudo: None,
responses: None,
};

let files = empty.render().to_schema_files().unwrap();
assert_eq!(files, []);

#[derive(schemars::JsonSchema)]
struct TestMsg {}

let full = Api {
contract_name: "my_contract".to_string(),
contract_version: "1.2.3".to_string(),
instantiate: Some(schema_for!(TestMsg)),
execute: Some(schema_for!(TestMsg)),
query: Some(schema_for!(TestMsg)),
migrate: Some(schema_for!(TestMsg)),
sudo: Some(schema_for!(TestMsg)),
responses: Some(BTreeMap::from([(
"TestMsg".to_string(),
schema_for!(TestMsg),
)])),
};

let files = full.render().to_schema_files().unwrap();
assert_eq!(files.len(), 6);
assert_eq!(files[0].0, "instantiate.json");
assert_eq!(files[1].0, "execute.json");
assert_eq!(files[2].0, "query.json");
assert_eq!(files[3].0, "migrate.json");
assert_eq!(files[4].0, "sudo.json");
assert_eq!(files[5].0, "response_to_TestMsg.json");
}
}
229 changes: 229 additions & 0 deletions packages/std/src/checksum.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,229 @@
use core::fmt;

use schemars::JsonSchema;
use serde::{de, ser, Deserialize, Deserializer, Serialize};
use sha2::{Digest, Sha256};
use thiserror::Error;

use crate::{StdError, StdResult};

/// A SHA-256 checksum of a Wasm blob, used to identify a Wasm code.
/// This must remain stable since this checksum is stored in the blockchain state.
///
/// This is often referred to as "code ID" in go-cosmwasm, even if code ID
/// usually refers to an auto-incrementing number.
#[derive(JsonSchema, Debug, Copy, Clone, PartialEq, Eq, Hash)]
pub struct Checksum(#[schemars(with = "String")] [u8; 32]);

impl Checksum {
pub fn generate(wasm: &[u8]) -> Self {
Checksum(Sha256::digest(wasm).into())
}

/// Tries to parse the given hex string into a checksum.
/// Errors if the string contains non-hex characters or does not contain 32 bytes.
pub fn from_hex(input: &str) -> StdResult<Self> {
let mut binary = [0u8; 32];
hex::decode_to_slice(input, &mut binary).map_err(StdError::invalid_hex)?;

Ok(Self(binary))
}

/// Creates a lowercase hex encoded copy of this checksum.
///
/// This takes an owned `self` instead of a reference because `Checksum` is cheap to `Copy`.
pub fn to_hex(self) -> String {
self.to_string()
}

/// Returns a reference to the inner bytes of this checksum as a slice.
/// If you need a reference to the array, use [`AsRef::as_ref`].
pub fn as_slice(&self) -> &[u8] {
&self.0
}
}

impl fmt::Display for Checksum {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
for byte in self.0.iter() {
write!(f, "{byte:02x}")?;
}
Ok(())
}
}

impl From<[u8; 32]> for Checksum {
fn from(data: [u8; 32]) -> Self {
Checksum(data)
}
}

impl AsRef<[u8; 32]> for Checksum {
fn as_ref(&self) -> &[u8; 32] {
&self.0
}
}

/// Serializes as a hex string
impl Serialize for Checksum {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: ser::Serializer,
{
serializer.serialize_str(&self.to_hex())
}
}

/// Deserializes as a hex string
impl<'de> Deserialize<'de> for Checksum {
fn deserialize<D>(deserializer: D) -> Result<Checksum, D::Error>
where
D: Deserializer<'de>,
{
deserializer.deserialize_str(ChecksumVisitor)
}
}

struct ChecksumVisitor;

impl<'de> de::Visitor<'de> for ChecksumVisitor {
type Value = Checksum;

fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
formatter.write_str("valid hex encoded 32 byte checksum")
}

fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
where
E: de::Error,
{
match Checksum::from_hex(v) {
Ok(data) => Ok(data),
Err(_) => Err(E::custom(format!("invalid checksum: {v}"))),
}
}
}

#[derive(Error, Debug)]
#[error("Checksum not of length 32")]
pub struct ChecksumError;

impl TryFrom<&[u8]> for Checksum {
type Error = ChecksumError;

fn try_from(value: &[u8]) -> Result<Self, Self::Error> {
if value.len() != 32 {
return Err(ChecksumError);
}
let mut data = [0u8; 32];
data.copy_from_slice(value);
Ok(Checksum(data))
}
}

impl From<Checksum> for Vec<u8> {
fn from(original: Checksum) -> Vec<u8> {
original.0.into()
}
}

#[cfg(test)]
mod tests {
use super::*;

use crate::to_json_string;

#[test]
fn generate_works() {
let wasm = vec![0x68, 0x69, 0x6a];
let checksum = Checksum::generate(&wasm);

// echo -n "hij" | sha256sum
let expected = [
0x72, 0x2c, 0x8c, 0x99, 0x3f, 0xd7, 0x5a, 0x76, 0x27, 0xd6, 0x9e, 0xd9, 0x41, 0x34,
0x4f, 0xe2, 0xa1, 0x42, 0x3a, 0x3e, 0x75, 0xef, 0xd3, 0xe6, 0x77, 0x8a, 0x14, 0x28,
0x84, 0x22, 0x71, 0x04,
];
assert_eq!(checksum.0, expected);
}

#[test]
fn implemented_display() {
let wasm = vec![0x68, 0x69, 0x6a];
let checksum = Checksum::generate(&wasm);
// echo -n "hij" | sha256sum
let embedded = format!("Check: {checksum}");
assert_eq!(
embedded,
"Check: 722c8c993fd75a7627d69ed941344fe2a1423a3e75efd3e6778a142884227104"
);
assert_eq!(
checksum.to_string(),
"722c8c993fd75a7627d69ed941344fe2a1423a3e75efd3e6778a142884227104"
);
}

#[test]
fn from_hex_works() {
// echo -n "hij" | sha256sum
let checksum = "722c8c993fd75a7627d69ed941344fe2a1423a3e75efd3e6778a142884227104";
let parsed = Checksum::from_hex(checksum).unwrap();
assert_eq!(parsed, Checksum::generate(b"hij"));
// should be inverse of `to_hex`
assert_eq!(parsed.to_hex(), checksum);

// invalid hex
let too_short = "722c8c993fd75a7627d69ed941344fe2a1423a3e75efd3e6778a1428842271";
assert!(Checksum::from_hex(too_short).is_err());
let invalid_char = "722c8c993fd75a7627d69ed941344fe2a1423a3e75efd3e6778a1428842271g4";
assert!(Checksum::from_hex(invalid_char).is_err());
let too_long = "722c8c993fd75a7627d69ed941344fe2a1423a3e75efd3e6778a14288422710400";
assert!(Checksum::from_hex(too_long).is_err());
}

#[test]
fn to_hex_works() {
let wasm = vec![0x68, 0x69, 0x6a];
let checksum = Checksum::generate(&wasm);
// echo -n "hij" | sha256sum
assert_eq!(
checksum.to_hex(),
"722c8c993fd75a7627d69ed941344fe2a1423a3e75efd3e6778a142884227104"
);
}

#[test]
fn into_vec_works() {
let checksum = Checksum::generate(&[12u8; 17]);
let as_vec: Vec<u8> = checksum.into();
assert_eq!(as_vec, checksum.0);
}

#[test]
fn ref_conversions_work() {
let checksum = Checksum::generate(&[12u8; 17]);
// as_ref
let _: &[u8; 32] = checksum.as_ref();
let _: &[u8] = checksum.as_ref();
// as_slice
let _: &[u8; 32] = checksum.as_ref();
let _: &[u8] = checksum.as_ref();
}

#[test]
fn serde_works() {
// echo -n "hij" | sha256sum
let checksum =
Checksum::from_hex("722c8c993fd75a7627d69ed941344fe2a1423a3e75efd3e6778a142884227104")
.unwrap();

let serialized = to_json_string(&checksum).unwrap();
assert_eq!(
serialized,
"\"722c8c993fd75a7627d69ed941344fe2a1423a3e75efd3e6778a142884227104\""
);

let deserialized: Checksum = serde_json::from_str(&serialized).unwrap();
assert_eq!(deserialized, checksum);
}
}
2 changes: 2 additions & 0 deletions packages/std/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ extern crate alloc;
mod addresses;
mod assertions;
mod binary;
mod checksum;
mod coin;
mod coins;
mod conversion;
Expand Down Expand Up @@ -41,6 +42,7 @@ pub mod storage_keys;

pub use crate::addresses::{instantiate2_address, Addr, CanonicalAddr, Instantiate2AddressError};
pub use crate::binary::Binary;
pub use crate::checksum::{Checksum, ChecksumError};
pub use crate::coin::{coin, coins, has_coins, Coin};
pub use crate::coins::Coins;
pub use crate::deps::{Deps, DepsMut, OwnedDeps};
Expand Down
Loading

0 comments on commit ddcd300

Please sign in to comment.