diff --git a/Cargo.lock b/Cargo.lock index 133f4d8..4b67a4e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -143,6 +143,12 @@ dependencies = [ "libc", ] +[[package]] +name = "crunchy" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" + [[package]] name = "crypto-bigint" version = "0.5.5" @@ -182,13 +188,16 @@ dependencies = [ name = "cw-fetch-std" version = "0.1.0" dependencies = [ + "base64", "bech32", "cosmwasm-std", "cosmwasm-storage", "cw-storage-plus", + "hex", "ripemd", "serde", "sha2 0.10.8", + "tiny-keccak", ] [[package]] @@ -664,6 +673,15 @@ dependencies = [ "syn 2.0.52", ] +[[package]] +name = "tiny-keccak" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c9d3793400a45f954c52e73d068316d76b6f4e36977e3fcebb13a2721e80237" +dependencies = [ + "crunchy", +] + [[package]] name = "typenum" version = "1.17.0" diff --git a/Cargo.toml b/Cargo.toml index 8187e7d..1286a6f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,4 +12,7 @@ cw-storage-plus = "1.0.1" bech32 = "0.9.1" sha2 = "0.10.6" ripemd = "0.1.3" -serde = { version = "1.0.145", default-features = false, features = ["derive"] } \ No newline at end of file +serde = { version = "1.0.145", default-features = false, features = ["derive"] } +tiny-keccak = { version = "2.0.2", features = ["keccak"] } +hex = "0.4.3" +base64 = "0.21.7" \ No newline at end of file diff --git a/src/crypto/cosmos.rs b/src/crypto/cosmos.rs new file mode 100644 index 0000000..5c99d9d --- /dev/null +++ b/src/crypto/cosmos.rs @@ -0,0 +1,45 @@ +use cosmwasm_std::Addr; + +use crate::crypto::encoding::encode_bech32; +use crate::crypto::hashing::{ripemd160, sha256}; + +pub type RawCosmosAddress = [u8; 20]; + +pub fn cosmos_raw_address(pubkey: &[u8]) -> RawCosmosAddress { + let hash = ripemd160(&sha256(pubkey)); + + let mut addr = [0u8; 20]; + addr.copy_from_slice(&hash[..]); + + addr +} + +pub fn cosmos_address(raw_address: &RawCosmosAddress, prefix: &str) -> Addr { + Addr::unchecked(encode_bech32(prefix, raw_address).unwrap()) +} + +pub fn cosmos_address_from_pubkey(pubkey: &[u8], prefix: &str) -> Addr { + cosmos_address(&cosmos_raw_address(pubkey), prefix) +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::crypto::encoding::parse_bech32; + + #[test] + fn test_pubkey_to_address() { + // Test if encoded pubkey can be decoded and converted to address + + let pubkey_str = + "pub1qv6wrktsr7hng9rmmjqa2yfqj0cg7w43n0qkq3xuqmgxu6ewnyyjykzgyam".to_string(); + let address = Addr::unchecked("fetch1967p3vkp0yngdfturv4ypq2p4g760ml705wcxy".to_string()); + + // Get pubkey in bytes + let pubkey_bytes = parse_bech32(&pubkey_str, "pub").unwrap(); + // Convert pubkey bytes to address + let recovered_addr = cosmos_address_from_pubkey(&pubkey_bytes, "fetch"); + + assert_eq!(recovered_addr, address); + } +} diff --git a/src/crypto/crypto_impl.rs b/src/crypto/crypto_impl.rs deleted file mode 100644 index 73cac82..0000000 --- a/src/crypto/crypto_impl.rs +++ /dev/null @@ -1,87 +0,0 @@ -use bech32::{self, FromBase32, ToBase32}; -use cosmwasm_std::{Addr, StdError}; - -use ripemd::Ripemd160; -use sha2::{Digest, Sha256}; - -pub const AGENT_ADDRESS_PREFIX: &str = "agent"; -pub const SIGNATURE_PREFIX: &str = "sig"; - -pub fn encode_length_prefixed(data: &[u8]) -> Vec { - // return u64_be_data_length: [u8] + data: [u8] - let mut result: Vec = Vec::new(); - result.append(&mut (data.len() as u64).to_be_bytes().to_vec()); - result.append(&mut data.to_vec()); - result -} - -pub fn parse_bech32(data: &str, expected_prefix: &str) -> Result, StdError> { - let (prefix, parsed_data, _variant) = match bech32::decode(data) { - Ok(parsed_data) => Ok(parsed_data), - Err(err) => Err(base32_parsing_error(&err)), - }?; - - if prefix != expected_prefix { - return Err(prefix_error(expected_prefix, &prefix)); - } - - match Vec::::from_base32(&parsed_data) { - Ok(res) => Ok(res), - Err(err) => Err(base32_parsing_error(&err)), - } -} - -pub fn encode_bech32(prefix: &str, data: &[u8]) -> Result { - let encoded = match bech32::encode(prefix, data.to_base32(), bech32::Variant::Bech32) { - Ok(encoded) => Ok(encoded), - Err(err) => Err(base32_parsing_error(&err)), - }?; - - Ok(encoded) -} - -pub fn pubkey_to_address(pubkey: &[u8]) -> Addr { - let mut hasher = Sha256::new(); - hasher.update(pubkey); - let hash = hasher.finalize(); - let mut ripemd160 = Ripemd160::new(); - ripemd160.update(hash); - let hash = ripemd160.finalize(); - let mut addr = [0u8; 20]; - addr.copy_from_slice(&hash[..]); - - Addr::unchecked(encode_bech32("fetch", &addr).unwrap()) -} - -// Errors -pub fn prefix_error(expected: &str, actual: &str) -> StdError { - StdError::generic_err(format!( - "Wrong prefix. Expected {}, got {}.", - expected, actual - )) -} - -pub fn base32_parsing_error(err: &T) -> StdError { - StdError::generic_err(format!("Base32 parsing failed: {}", err)) -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_pubkey_to_address() { - // Test if encoded pubkey can be decoded and converted to address - - let pubkey_str = - "pub1qv6wrktsr7hng9rmmjqa2yfqj0cg7w43n0qkq3xuqmgxu6ewnyyjykzgyam".to_string(); - let address = Addr::unchecked("fetch1967p3vkp0yngdfturv4ypq2p4g760ml705wcxy".to_string()); - - // Get pubkey in bytes - let pubkey_bytes = parse_bech32(&pubkey_str, "pub").unwrap(); - // Convert pubkey bytes to address - let recovered_addr = pubkey_to_address(&pubkey_bytes); - - assert_eq!(recovered_addr, address); - } -} diff --git a/src/crypto/encoding.rs b/src/crypto/encoding.rs new file mode 100644 index 0000000..c950ba4 --- /dev/null +++ b/src/crypto/encoding.rs @@ -0,0 +1,39 @@ +use bech32::{FromBase32, ToBase32}; +use cosmwasm_std::StdError; + +pub fn parse_bech32(data: &str, expected_prefix: &str) -> Result, StdError> { + let (prefix, parsed_data, _variant) = match bech32::decode(data) { + Ok(parsed_data) => Ok(parsed_data), + Err(err) => Err(base32_parsing_error(&err)), + }?; + + if prefix != expected_prefix { + return Err(prefix_error(expected_prefix, &prefix)); + } + + match Vec::::from_base32(&parsed_data) { + Ok(res) => Ok(res), + Err(err) => Err(base32_parsing_error(&err)), + } +} + +pub fn encode_bech32(prefix: &str, data: &[u8]) -> Result { + let encoded = match bech32::encode(prefix, data.to_base32(), bech32::Variant::Bech32) { + Ok(encoded) => Ok(encoded), + Err(err) => Err(base32_parsing_error(&err)), + }?; + + Ok(encoded) +} + +// Errors +pub fn prefix_error(expected: &str, actual: &str) -> StdError { + StdError::generic_err(format!( + "Wrong prefix. Expected {}, got {}.", + expected, actual + )) +} + +pub fn base32_parsing_error(err: &T) -> StdError { + StdError::generic_err(format!("Base32 parsing failed: {}", err)) +} diff --git a/src/crypto/ethereum.rs b/src/crypto/ethereum.rs new file mode 100644 index 0000000..0e486df --- /dev/null +++ b/src/crypto/ethereum.rs @@ -0,0 +1,294 @@ +use cosmwasm_std::{Addr, Api, StdError}; +use std::convert::TryInto; + +use crate::crypto::hashing::{keccak, KeccakDigest}; +use base64::{engine::general_purpose, Engine as _}; + +pub type EthAddress = [u8; 20]; + +/// Wraps and input message in the ETH personal message format +/// +/// # Arguments +/// +/// * `msg` - The message to wrap +/// +fn build_eth_msg_for_signing(canonical_msg: &str) -> String { + format!( + "\x19Ethereum Signed Message:\n{}{}", + canonical_msg.len(), + canonical_msg + ) +} + +/// Computes the message and digest that should be signed by the user +pub fn compute_eth_msg_digest(canonical_msg: &str) -> KeccakDigest { + let wrapped = build_eth_msg_for_signing(canonical_msg); + keccak(wrapped.as_bytes()) +} + +/// Checks if the slice is not all zeros +fn is_non_zero(data: &[u8]) -> bool { + for &v in data { + if v != 0 { + return true; + } + } + + false +} + +/// Parse the input eth address and generate a binary version +/// +/// # Arguments +/// +/// * `eth_address` - The ETH address to be parsed +/// +pub fn parse_eth_address(eth_address: &str) -> Result { + // decode and drop the 0x prefix + let decoded = if let Some(stripped) = eth_address.strip_prefix("0x") { + hex::decode(stripped) + } else { + hex::decode(eth_address) + }; + + let decoded = decoded.map_err(|err| addresses_error(&err))?; + let address: EthAddress = decoded + .try_into() + .map_err(|_| addresses_error(&"parsing error"))?; + + // do not allow an all zero address + if !is_non_zero(&address) { + return Err(addresses_error(&"is zero")); + } + + Ok(address) +} + +/// Parse the input ETH style signature and split into raw signature and recovery code +/// +/// # Arguments +/// +/// * `signature` - The base64 encoded ETH style signature +/// +fn parse_eth_signature(signature: &str) -> Result<(u8, Vec), StdError> { + let mut unpacked_signature = general_purpose::STANDARD + .decode(signature) + .map_err(|err| signature_error(&err))?; + if unpacked_signature.len() != 65 { + return Err(signature_error(&"Wrong length")); + } + + // extract the recovery code + let mut recovery_code = unpacked_signature + .pop() + .ok_or(signature_error(&"Wrong recovery code"))?; + if recovery_code >= 27u8 { + recovery_code -= 27u8; + } + + // validate the recovery code + let valid_recovery_code = recovery_code == 0 || recovery_code == 1; + if !valid_recovery_code { + return Err(signature_error(&"Unrecoverable signature")); + } + + Ok((recovery_code, unpacked_signature)) +} + +pub fn pubkey_to_eth_address(eth_pubkey: &[u8]) -> Result { + keccak(ð_pubkey[1..])[12..] + .try_into() + .map_err(|err| signature_error(&err)) +} + +/// Checks the provided signature matches the specified native and ETH addresses +/// +/// # Arguments +/// +/// * `destination_address` - The native address in this linking +/// * `eth_address` - The ETH address in this linking +/// +pub fn check_registration( + api: &dyn Api, + destination_address: &Addr, + eth_address: &str, + signature: &str, +) -> Result { + // compute the expected message and then the digest for it + + let msg = format!("Associate {} with {}", destination_address, eth_address); + let msg_hash = compute_eth_msg_digest(&msg); + + let recovered_public_key = recover_pubkey_secp256k1(api, &msg_hash, signature)?; + let recovered_address = pubkey_to_eth_address(&recovered_public_key)?; + + let address = parse_eth_address(eth_address)?; + + // compare the addresses + if recovered_address != address { + return Err(signature_error(&"unverifiable signature")); + } + + Ok(address) +} + +pub fn recover_pubkey_secp256k1( + api: &dyn Api, + msg_hash: &[u8], + signature: &str, +) -> Result, StdError> { + // parse the eth style signature, extracting the recovery param from r and s + let (recovery_param, raw_signature) = parse_eth_signature(signature)?; + + // recover the public key from the signature + let recovered_public_key = api + .secp256k1_recover_pubkey(msg_hash, &raw_signature, recovery_param) + .map_err(|err| signature_error(&err))?; + + if recovered_public_key.len() != 65 { + return Err(signature_error(&"Wrong length")); + } + if recovered_public_key[0] != 4 { + return Err(signature_error(&"First byte not 0x04")); + } + + Ok(recovered_public_key) +} + +// Errors + +pub fn signature_error(err: &T) -> StdError { + StdError::generic_err(format!("Eth signature error {}", err)) +} + +pub fn addresses_error(err: &T) -> StdError { + StdError::generic_err(format!("Eth address error {}", err)) +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::crypto::cosmos::cosmos_address_from_pubkey; + use crate::crypto::hashing::compress_pubkey_secp256k1; + use cosmwasm_std::testing::mock_dependencies; + + #[test] + fn it_parses_eth_addresses() { + assert!(parse_eth_address("0xfdsfsdfsdfs").is_err()); + assert!(parse_eth_address("0x12312399fsdf").is_err()); + assert!(parse_eth_address("0x0000000000000000000000000000000000000000").is_err()); + assert!(parse_eth_address("0xBf8a79E9473c314d344859885E9d7d0906Af8420").is_ok()); + assert!(parse_eth_address("Bf8a79E9473c314d344859885E9d7d0906Af8420").is_ok()); + } + + #[test] + fn it_wraps_eth_sign_messages() { + let wrapped = build_eth_msg_for_signing("why hello there"); + assert_eq!(&wrapped, "\x19Ethereum Signed Message:\n15why hello there") + } + + #[test] + fn it_computes_digests() { + let native = Addr::unchecked("native-address"); + + let msg = format!("Associate {} with {}", &native, "eth-address"); + let result = compute_eth_msg_digest(&msg); + + assert_eq!( + hex::encode(result), + "41b34fcffd51799f83523fdcf409482012be1c99e0ebde4d21016977b27d56ef" + ); + } + + #[test] + fn it_doesnt_parse_bad_signatures() { + assert!(parse_eth_signature("asdfasdfasdf").is_err()); + assert!(parse_eth_signature("AQIDBAUGBwgJEBESExQVFhcYGSAhIiMkJSYnKCkwMTIzNDU2Nzg5QEFCQ0RFRkdISVBRUlNUVVZXWFlgYWJjZB0=").is_err()); // rc 2 + assert!(parse_eth_signature("AQIDBAUGBwgJEBESExQVFhcYGSAhIiMkJSYnKCkwMTIzNDU2Nzg5QEFCQ0RFRkdISVBRUlNUVVZXWFlgYWJjZB4=").is_err()); + // rc 3 + } + + #[test] + fn it_does_parse_good_signatures() { + let (rc, sig) = parse_eth_signature("AQIDBAUGBwgJEBESExQVFhcYGSAhIiMkJSYnKCkwMTIzNDU2Nzg5QEFCQ0RFRkdISVBRUlNUVVZXWFlgYWJjZBs=").unwrap(); + assert_eq!(rc, 0u8); + assert_eq!(hex::encode(&sig), "01020304050607080910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364"); + + let (rc, sig) = parse_eth_signature("AQIDBAUGBwgJEBESExQVFhcYGSAhIiMkJSYnKCkwMTIzNDU2Nzg5QEFCQ0RFRkdISVBRUlNUVVZXWFlgYWJjZBw=").unwrap(); + assert_eq!(rc, 1u8); + assert_eq!(hex::encode(&sig), "01020304050607080910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364"); + } + + #[test] + fn it_does_parse_ledger_style_signatures() { + let (rc, sig) = parse_eth_signature("AQIDBAUGBwgJCgsMDQ4PEBESExQVFhcYGRobHB0eHyAhIiMkJSYnKCkqKywtLi8wMTIzNDU2Nzg5Ojs8PT4/QAA=").unwrap(); + assert_eq!(rc, 0u8); + assert_eq!(hex::encode(&sig), "0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f40"); + + let (rc, sig) = parse_eth_signature("AQIDBAUGBwgJCgsMDQ4PEBESExQVFhcYGRobHB0eHyAhIiMkJSYnKCkqKywtLi8wMTIzNDU2Nzg5Ojs8PT4/QAE=").unwrap(); + assert_eq!(rc, 1u8); + assert_eq!(hex::encode(&sig), "0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f40"); + } + + #[test] + fn it_can_verify_a_signature() { + let deps = mock_dependencies(); + + let destination_address = Addr::unchecked("native-address"); + let eth_address = "0x561122493eF141Ba1A16301634d0C898B03a0Cf0"; + let signature = "Mh3dWbNS68m9tjwF5a5zXLi7QEhooNKZZqQ5GsXzXHVCboGkxZqxXzqF32CW2heI+LHLN8j3bze2Pae382OaDBw="; + + assert!(check_registration( + deps.as_ref().api, + &destination_address, + eth_address, + signature + ) + .is_ok()); + } + + #[test] + fn it_can_verify_a_ledger_generated_signature() { + let deps = mock_dependencies(); + + let destination_address = Addr::unchecked("fetch1dhmexsh45m6apvmuy2936dtt3juxphvqwlfv8t"); + let eth_address = "0x29f9bb90ac4f3fab98ad65b0c5472a65d0d9ae9e"; + let signature = "pt/jDRAfygsFrllH27ALqN6Uw+HC1I/jIY3Mcw1Y69JEETFfuO4leIIJ9Rn43DBn0qxAh3YEbRiOB4BfZbC03gE="; + + assert!(check_registration( + deps.as_ref().api, + &destination_address, + eth_address, + signature + ) + .is_ok()); + } + + #[test] + fn it_cant_verify_a_bad_signature() { + let deps = mock_dependencies(); + + let destination_address = Addr::unchecked("native-address2"); + let eth_address = "0xbaCf56506032e9f1BF4c9C92925460DE929fa8d8"; + let signature = "AxGAOevyAdZl13QtjAtNnsv5HKB5WzyWVfuCbPoIN/QJ6ul1Q24ZS1WUhdAmyrMe6vhA87gKcbB9T3yy+eeUfRs="; + + assert!(check_registration( + deps.as_ref().api, + &destination_address, + eth_address, + signature + ) + .is_err()); + } + + #[test] + fn test_encode_bech32() { + let eth_pubkey = hex::decode("5c084296dfaeaf815a3a7e4e8688ed4140e403f1cd2d2f545c7a3822007763ae0e547eb989d5eecbfc5acd0204531b38e3bcfab232c506db7a9353d68932ca61").unwrap(); + let expected_fetch_address = "fetch1e6lpplutmnxae8u7le9xsr7r9r4y9rukaf4lx8"; + + let compressed_pubkey = compress_pubkey_secp256k1(ð_pubkey).unwrap(); + + let fetch_address = cosmos_address_from_pubkey(&compressed_pubkey, "fetch"); + assert_eq!(fetch_address, expected_fetch_address); + } +} diff --git a/src/crypto/hashing.rs b/src/crypto/hashing.rs new file mode 100644 index 0000000..7e66579 --- /dev/null +++ b/src/crypto/hashing.rs @@ -0,0 +1,60 @@ +use cosmwasm_std::StdError; +use ripemd::Ripemd160; +use sha2::{Digest, Sha256}; +use tiny_keccak::Hasher; + +pub type KeccakDigest = [u8; 32]; + +/// Computes the Eth style Keccak digest for the input data +/// +/// # Arguments +/// +/// * `data` - The slice to be hashed +/// +pub fn keccak(data: &[u8]) -> KeccakDigest { + let mut output = [0u8; 32]; + + let mut hasher = tiny_keccak::Keccak::v256(); + hasher.update(data); + hasher.finalize(&mut output); + + output +} + +pub fn sha256(data: &[u8]) -> Vec { + let mut hasher = Sha256::new(); + hasher.update(data); + hasher.finalize().to_vec() +} + +pub fn ripemd160(data: &[u8]) -> Vec { + let mut ripemd160 = Ripemd160::new(); + ripemd160.update(data); + ripemd160.finalize().to_vec() +} + +pub fn compress_pubkey_secp256k1(uncompressed_pubkey_bytes: &[u8]) -> Result, StdError> { + if uncompressed_pubkey_bytes.len() != 64 { + return Err(pubkey_error(&"Wrong len")); + } + + // The first byte is the prefix, followed by 32 bytes for X and 32 bytes for Y + let x_bytes = &uncompressed_pubkey_bytes[0..32]; + let y_bytes = &uncompressed_pubkey_bytes[32..64]; + + // Determine if Y is even or odd for the prefix + // Y's last byte's least significant bit determines its evenness or oddness + let prefix_byte = if (y_bytes[31] & 1) == 0 { 0x02 } else { 0x03 }; + + // Create the compressed public key + let mut compressed_pubkey: Vec = Vec::with_capacity(33); + compressed_pubkey.push(prefix_byte); + compressed_pubkey.extend_from_slice(x_bytes); + + Ok(compressed_pubkey) +} + +// Error +pub fn pubkey_error(err: &T) -> StdError { + StdError::generic_err(format!("Eth pubkey error {}", err)) +} diff --git a/src/crypto/mod.rs b/src/crypto/mod.rs index a934a4b..d94e630 100644 --- a/src/crypto/mod.rs +++ b/src/crypto/mod.rs @@ -1,6 +1,5 @@ -mod crypto_impl; - -pub use crate::crypto::crypto_impl::{ - base32_parsing_error, encode_bech32, encode_length_prefixed, parse_bech32, prefix_error, - pubkey_to_address, AGENT_ADDRESS_PREFIX, SIGNATURE_PREFIX, -}; +pub mod cosmos; +pub mod encoding; +pub mod ethereum; +pub mod hashing; +pub mod uagents; diff --git a/src/crypto/uagents.rs b/src/crypto/uagents.rs new file mode 100644 index 0000000..b2e7878 --- /dev/null +++ b/src/crypto/uagents.rs @@ -0,0 +1,10 @@ +pub const AGENT_ADDRESS_PREFIX: &str = "agent"; +pub const SIGNATURE_PREFIX: &str = "sig"; + +pub fn encode_length_prefixed(data: &[u8]) -> Vec { + // return u64_be_data_length: [u8] + data: [u8] + let mut result: Vec = Vec::new(); + result.append(&mut (data.len() as u64).to_be_bytes().to_vec()); + result.append(&mut data.to_vec()); + result +} diff --git a/src/events/response_handler.rs b/src/events/response_handler.rs index a256ba8..577be7d 100644 --- a/src/events/response_handler.rs +++ b/src/events/response_handler.rs @@ -1,5 +1,5 @@ use crate::events::IntoEvent; -use cosmwasm_std::Response; +use cosmwasm_std::{Addr, BankMsg, Coin, CosmosMsg, Response, SubMsg}; pub struct ResponseHandler { response: Response, @@ -10,9 +10,20 @@ impl ResponseHandler { self.response.events.push(event.into_event()); } + pub fn add_msg(&mut self, msg: impl Into) { + self.response.messages.push(SubMsg::new(msg)); + } + pub fn into_response(self) -> Response { self.response } + + pub fn add_bank_send_msg(&mut self, to_addr: &Addr, amount: Vec) { + self.add_msg(BankMsg::Send { + to_address: to_addr.to_string(), + amount, + }) + } } impl Default for ResponseHandler { diff --git a/src/helpers/iteration_guard.rs b/src/helpers/iteration_guard.rs index 17eb065..b25a8ba 100644 --- a/src/helpers/iteration_guard.rs +++ b/src/helpers/iteration_guard.rs @@ -75,7 +75,7 @@ impl IterationGuard { #[cfg(test)] mod tests { use super::*; - use crate::helpers::IterationResult::{Done, Stopped}; + use crate::helpers::iteration_guard::IterationResult::{Done, Stopped}; #[test] fn iteration_gurad() { diff --git a/src/helpers/mod.rs b/src/helpers/mod.rs index 86797b9..48fe2f6 100644 --- a/src/helpers/mod.rs +++ b/src/helpers/mod.rs @@ -1,3 +1,2 @@ -mod iteration_guard; - -pub use crate::helpers::iteration_guard::{IterationGuard, IterationResult}; +pub mod iteration_guard; +pub mod tests_helpers; diff --git a/src/helpers/tests_helpers.rs b/src/helpers/tests_helpers.rs new file mode 100644 index 0000000..09f5455 --- /dev/null +++ b/src/helpers/tests_helpers.rs @@ -0,0 +1,33 @@ +use cosmwasm_std::testing::{mock_dependencies, MockApi, MockQuerier, MockStorage}; +use cosmwasm_std::ContractResult as StdContractResult; +use cosmwasm_std::{ + to_json_binary, Addr, ContractInfoResponse, Empty, OwnedDeps, SystemError, SystemResult, + WasmQuery, +}; + +pub fn deps_with_creator( + creator: Addr, + contract_address: Addr, +) -> OwnedDeps { + let mut deps = mock_dependencies(); + let mut querier = MockQuerier::default(); + querier.update_wasm(move |request| match request { + WasmQuery::ContractInfo { contract_addr } => { + if *contract_addr == contract_address { + let mut response = ContractInfoResponse::default(); + response.admin = Some(creator.to_string()); + SystemResult::Ok(StdContractResult::Ok(to_json_binary(&response).unwrap())) + } else { + SystemResult::Err(SystemError::NoSuchContract { + addr: contract_addr.clone(), + }) + } + } + + _ => { + panic!() + } + }); + deps.querier = querier; + deps +}