Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use Checksum for CodeInfoResponse #1944

Merged
merged 15 commits into from
Nov 21, 2023
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])
chipshort marked this conversation as resolved.
Show resolved Hide resolved

[#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
Loading