From 10cddc72b85296756b111bc0ce1cc524d3709d47 Mon Sep 17 00:00:00 2001 From: Joshy Orndorff Date: Tue, 27 Jun 2023 15:56:56 -0400 Subject: [PATCH 1/2] attempt --- Cargo.lock | 20 +- crates/cargo-contract/Cargo.toml | 4 + crates/cargo-contract/src/account.rs | 238 ++++++++++++++++++ .../src/cmd/extrinsics/instantiate.rs | 4 +- .../cargo-contract/src/cmd/extrinsics/mod.rs | 3 +- crates/cargo-contract/src/cmd/mod.rs | 21 +- crates/cargo-contract/src/main.rs | 1 + crates/transcode/src/lib.rs | 4 +- 8 files changed, 280 insertions(+), 15 deletions(-) create mode 100644 crates/cargo-contract/src/account.rs diff --git a/Cargo.lock b/Cargo.lock index 4efd9ad89..ff306c6e5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -640,7 +640,9 @@ dependencies = [ "contract-transcode", "current_platform", "hex", + "impl-serde", "jsonrpsee 0.18.2", + "libsecp256k1", "pallet-contracts-primitives", "parity-scale-codec", "predicates", @@ -649,7 +651,9 @@ dependencies = [ "scale-info", "serde", "serde_json", + "sha3", "sp-core 21.0.0", + "sp-io 23.0.0", "sp-runtime 24.0.0", "sp-weights 20.0.0", "substrate-build-script-utils", @@ -2397,9 +2401,9 @@ dependencies = [ [[package]] name = "keccak" -version = "0.1.3" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3afef3b6eff9ce9d8ff9b3601125eec7f0c8cbac7abd14f355d053fa56c98768" +checksum = "8f6d5ed8676d904364de097082f4e7d240b571b67989ced0240f08b7f966f940" dependencies = [ "cpufeatures", ] @@ -2830,9 +2834,9 @@ dependencies = [ [[package]] name = "parity-scale-codec" -version = "3.4.0" +version = "3.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "637935964ff85a605d114591d4d2c13c5d1ba2806dae97cea6bf180238a749ac" +checksum = "2287753623c76f953acd29d15d8100bcab84d29db78fb6f352adb3c53e83b967" dependencies = [ "arrayvec 0.7.2", "bitvec", @@ -2845,9 +2849,9 @@ dependencies = [ [[package]] name = "parity-scale-codec-derive" -version = "3.1.4" +version = "3.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86b26a931f824dd4eca30b3e43bb4f31cd5f0d3a403c5f5ff27106b805bfde7b" +checksum = "2b6937b5e67bfba3351b87b040d48352a2fcb6ad72f81855412ce97b45c8f110" dependencies = [ "proc-macro-crate 1.3.1", "proc-macro2", @@ -3875,9 +3879,9 @@ dependencies = [ [[package]] name = "sha3" -version = "0.10.6" +version = "0.10.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bdf0c33fae925bdc080598b84bc15c55e7b9a4a43b3c704da051f977469691c9" +checksum = "75872d278a8f37ef87fa0ddbda7802605cb18344497949862c0d4dcb291eba60" dependencies = [ "digest 0.10.6", "keccak", diff --git a/crates/cargo-contract/Cargo.toml b/crates/cargo-contract/Cargo.toml index 8885eeea9..3072ba6fd 100644 --- a/crates/cargo-contract/Cargo.toml +++ b/crates/cargo-contract/Cargo.toml @@ -44,6 +44,10 @@ scale-info = "2.8.0" subxt = "0.29.0" hex = "0.4.3" jsonrpsee = { version = "0.18.2", features = ["ws-client"] } +libsecp256k1 = { version = "0.7", default-features = false , features = [ "hmac" ] } +sha3 = "0.10.8" +sp-io = "23.0.0" +impl-serde = "0.4.0" [build-dependencies] anyhow = "1.0.71" diff --git a/crates/cargo-contract/src/account.rs b/crates/cargo-contract/src/account.rs new file mode 100644 index 000000000..f1d61e18e --- /dev/null +++ b/crates/cargo-contract/src/account.rs @@ -0,0 +1,238 @@ + +// Copyright 2019-2022 PureStake Inc. +// This file is part of Moonbeam. + +// Moonbeam is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Moonbeam is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Moonbeam. If not, see . + +//! The Ethereum Signature implementation. +//! +//! It includes the Verify and IdentifyAccount traits for the AccountId20 + + +use scale::{Decode, Encode, MaxEncodedLen}; +use scale_info::TypeInfo; +use sha3::{Digest, Keccak256}; +use sp_core::{ecdsa, H160}; + +pub use serde::{de::DeserializeOwned, Deserialize, Serialize}; + +//TODO Maybe this should be upstreamed into Frontier (And renamed accordingly) so that it can +// be used in palletEVM as well. It may also need more traits such as AsRef, AsMut, etc like +// AccountId32 has. + +/// The account type to be used in Moonbeam. It is a wrapper for 20 fixed bytes. We prefer to use +/// a dedicated type to prevent using arbitrary 20 byte arrays were AccountIds are expected. With +/// the introduction of the `scale-info` crate this benefit extends even to non-Rust tools like +/// Polkadot JS. + +#[derive( + Eq, PartialEq, Copy, Clone, Encode, Decode, TypeInfo, MaxEncodedLen, Default, PartialOrd, Ord, +)] +pub struct AccountId20(pub [u8; 20]); + +impl_serde::impl_fixed_hash_serde!(AccountId20, 20); + +impl std::fmt::Display for AccountId20 { + //TODO This is a pretty quck-n-dirty implementation. Perhaps we should add + // checksum casing here? I bet there is a crate for that. + // Maybe this one https://github.com/miguelmota/rust-eth-checksum + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{:?}", self.0) + } +} + +impl core::fmt::Debug for AccountId20 { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "{:?}", H160(self.0)) + } +} + +impl From<[u8; 20]> for AccountId20 { + fn from(bytes: [u8; 20]) -> Self { + Self(bytes) + } +} + +impl From for [u8; 20] { + fn from(value: AccountId20) -> Self { + value.0 + } +} + +impl From<[u8; 32]> for AccountId20 { + fn from(bytes: [u8; 32]) -> Self { + let mut buffer = [0u8; 20]; + buffer.copy_from_slice(&bytes[..20]); + Self(buffer) + } +} + +impl From for AccountId20 { + fn from(h160: H160) -> Self { + Self(h160.0) + } +} + +impl From for H160 { + fn from(value: AccountId20) -> Self { + H160(value.0) + } +} + +impl std::str::FromStr for AccountId20 { + type Err = &'static str; + fn from_str(input: &str) -> Result { + H160::from_str(input) + .map(Into::into) + .map_err(|_| "invalid hex address.") + } +} + +#[derive(serde::Serialize, serde::Deserialize)] +#[derive(Eq, PartialEq, Clone, Encode, Decode, sp_core::RuntimeDebug, TypeInfo)] +pub struct EthereumSignature(ecdsa::Signature); + +impl From for EthereumSignature { + fn from(x: ecdsa::Signature) -> Self { + EthereumSignature(x) + } +} + +impl sp_runtime::traits::Verify for EthereumSignature { + type Signer = EthereumSigner; + fn verify>(&self, mut msg: L, signer: &AccountId20) -> bool { + let mut m = [0u8; 32]; + m.copy_from_slice(Keccak256::digest(msg.get()).as_slice()); + match sp_io::crypto::secp256k1_ecdsa_recover(self.0.as_ref(), &m) { + Ok(pubkey) => { + AccountId20(H160::from_slice(&Keccak256::digest(pubkey).as_slice()[12..32]).0) + == *signer + } + Err(sp_io::EcdsaVerifyError::BadRS) => { + // log::error!(target: "evm", "Error recovering: Incorrect value of R or S"); + false + } + Err(sp_io::EcdsaVerifyError::BadV) => { + // log::error!(target: "evm", "Error recovering: Incorrect value of V"); + false + } + Err(sp_io::EcdsaVerifyError::BadSignature) => { + // log::error!(target: "evm", "Error recovering: Invalid signature"); + false + } + } + } +} + +/// Public key for an Ethereum / Moonbeam compatible account +#[derive( + Eq, PartialEq, Ord, PartialOrd, Clone, Encode, Decode, sp_core::RuntimeDebug, TypeInfo, +)] +#[derive(serde::Serialize, serde::Deserialize)] +pub struct EthereumSigner([u8; 20]); + +impl sp_runtime::traits::IdentifyAccount for EthereumSigner { + type AccountId = AccountId20; + fn into_account(self) -> AccountId20 { + AccountId20(self.0) + } +} + +impl From<[u8; 20]> for EthereumSigner { + fn from(x: [u8; 20]) -> Self { + EthereumSigner(x) + } +} + +impl From for EthereumSigner { + fn from(x: ecdsa::Public) -> Self { + let decompressed = libsecp256k1::PublicKey::parse_slice( + &x.0, + Some(libsecp256k1::PublicKeyFormat::Compressed), + ) + .expect("Wrong compressed public key provided") + .serialize(); + let mut m = [0u8; 64]; + m.copy_from_slice(&decompressed[1..65]); + let account = H160::from_slice(&Keccak256::digest(m).as_slice()[12..32]); + EthereumSigner(account.into()) + } +} + +impl From for EthereumSigner { + fn from(x: libsecp256k1::PublicKey) -> Self { + let mut m = [0u8; 64]; + m.copy_from_slice(&x.serialize()[1..65]); + let account = H160::from_slice(&Keccak256::digest(m).as_slice()[12..32]); + EthereumSigner(account.into()) + } +} + +impl std::fmt::Display for EthereumSigner { + fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result { + write!(fmt, "ethereum signature: {:?}", H160::from_slice(&self.0)) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use sp_core::{ecdsa, Pair, H256}; + use sp_runtime::traits::IdentifyAccount; + + #[test] + fn test_account_derivation_1() { + // Test from https://asecuritysite.com/encryption/ethadd + let secret_key = + hex::decode("502f97299c472b88754accd412b7c9a6062ef3186fba0c0388365e1edec24875") + .unwrap(); + let mut expected_hex_account = [0u8; 20]; + hex::decode_to_slice( + "976f8456e4e2034179b284a23c0e0c8f6d3da50c", + &mut expected_hex_account, + ) + .expect("example data is 20 bytes of valid hex"); + + let public_key = ecdsa::Pair::from_seed_slice(&secret_key).unwrap().public(); + let account: EthereumSigner = public_key.into(); + let expected_account = AccountId20::from(expected_hex_account); + assert_eq!(account.into_account(), expected_account); + } + #[test] + fn test_account_derivation_2() { + // Test from https://asecuritysite.com/encryption/ethadd + let secret_key = + hex::decode("0f02ba4d7f83e59eaa32eae9c3c4d99b68ce76decade21cdab7ecce8f4aef81a") + .unwrap(); + let mut expected_hex_account = [0u8; 20]; + hex::decode_to_slice( + "420e9f260b40af7e49440cead3069f8e82a5230f", + &mut expected_hex_account, + ) + .expect("example data is 20 bytes of valid hex"); + + let public_key = ecdsa::Pair::from_seed_slice(&secret_key).unwrap().public(); + let account: EthereumSigner = public_key.into(); + let expected_account = AccountId20::from(expected_hex_account); + assert_eq!(account.into_account(), expected_account); + } + #[test] + fn test_account_derivation_3() { + let m = hex::decode("c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470") + .unwrap(); + let old = AccountId20(H160::from(H256::from_slice(Keccak256::digest(&m).as_slice())).0); + let new = AccountId20(H160::from_slice(&Keccak256::digest(&m).as_slice()[12..32]).0); + assert_eq!(new, old); + } +} diff --git a/crates/cargo-contract/src/cmd/extrinsics/instantiate.rs b/crates/cargo-contract/src/cmd/extrinsics/instantiate.rs index f81cd400b..a55ff6e11 100644 --- a/crates/cargo-contract/src/cmd/extrinsics/instantiate.rs +++ b/crates/cargo-contract/src/cmd/extrinsics/instantiate.rs @@ -41,7 +41,7 @@ use crate::{ Balance, CodeHash, }, - DEFAULT_KEY_COL_WIDTH, + DEFAULT_KEY_COL_WIDTH, account::AccountId20, }; use anyhow::{ anyhow, @@ -329,7 +329,7 @@ impl Exec { &self, result: &ExtrinsicEvents, code_hash: Option, - contract_address: subxt::utils::AccountId32, + contract_address: AccountId20, token_metadata: &TokenMetadata, ) -> Result<(), ErrorVariant> { let events = DisplayEvents::from_events( diff --git a/crates/cargo-contract/src/cmd/extrinsics/mod.rs b/crates/cargo-contract/src/cmd/extrinsics/mod.rs index 411960f43..27a2a21b0 100644 --- a/crates/cargo-contract/src/cmd/extrinsics/mod.rs +++ b/crates/cargo-contract/src/cmd/extrinsics/mod.rs @@ -94,7 +94,8 @@ pub use contract_transcode::ContractMessageTranscoder; pub use error::ErrorVariant; pub use instantiate::InstantiateCommand; pub use remove::RemoveCommand; -pub use subxt::PolkadotConfig as DefaultConfig; +// pub use subxt::PolkadotConfig as DefaultConfig; +pub use super::DefaultConfig; pub use upload::UploadCommand; type PairSigner = tx::PairSigner; diff --git a/crates/cargo-contract/src/cmd/mod.rs b/crates/cargo-contract/src/cmd/mod.rs index cf2fc0594..416df7759 100644 --- a/crates/cargo-contract/src/cmd/mod.rs +++ b/crates/cargo-contract/src/cmd/mod.rs @@ -20,6 +20,8 @@ pub mod encode; pub mod info; pub mod runtime_api; +use crate::account; + pub(crate) use self::{ build::{ BuildCommand, @@ -40,10 +42,25 @@ pub(crate) use self::extrinsics::{ use subxt::{ Config, - OnlineClient, + OnlineClient, SubstrateConfig, }; +use subxt::config::substrate::SubstrateExtrinsicParams; + +// pub use subxt::PolkadotConfig as DefaultConfig; +pub use AcademyPowConfig as DefaultConfig; + +pub enum AcademyPowConfig{} +impl subxt::Config for DefaultConfig { + type Index = ::Index; + type Hash = ::Hash; + type AccountId = account::AccountId20; + type Address = account::AccountId20; + type Signature = account::EthereumSignature; + type Hasher = ::Hasher; + type Header = ::Header; + type ExtrinsicParams = SubstrateExtrinsicParams; +} -pub use subxt::PolkadotConfig as DefaultConfig; type Client = OnlineClient; type Balance = u128; type CodeHash = ::Hash; diff --git a/crates/cargo-contract/src/main.rs b/crates/cargo-contract/src/main.rs index f6c766ae4..a6e61c897 100644 --- a/crates/cargo-contract/src/main.rs +++ b/crates/cargo-contract/src/main.rs @@ -17,6 +17,7 @@ #![deny(unused_crate_dependencies)] mod cmd; +mod account; use self::cmd::{ BuildCommand, diff --git a/crates/transcode/src/lib.rs b/crates/transcode/src/lib.rs index 81735058f..3730da92b 100644 --- a/crates/transcode/src/lib.rs +++ b/crates/transcode/src/lib.rs @@ -98,7 +98,7 @@ //! println!("Encoded constructor data {:?}", data); //! ``` -mod account_id; +// mod account_id; mod decode; mod encode; pub mod env_types; @@ -107,7 +107,7 @@ mod transcoder; mod util; pub use self::{ - account_id::AccountId32, + // account_id::AccountId32, scon::{ Hex, Map, From 201b5a291d94ea34fe29d931bf6dccc92df26427 Mon Sep 17 00:00:00 2001 From: Joshy Orndorff Date: Wed, 28 Jun 2023 11:30:36 -0400 Subject: [PATCH 2/2] more bs --- Cargo.lock | 5 + crates/transcode/Cargo.toml | 6 + .../src/account.rs | 136 +++++++++--------- crates/transcode/src/lib.rs | 1 + 4 files changed, 80 insertions(+), 68 deletions(-) rename crates/{cargo-contract => transcode}/src/account.rs (73%) diff --git a/Cargo.lock b/Cargo.lock index ff306c6e5..adeb13eb4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -866,11 +866,13 @@ dependencies = [ "contract-metadata", "escape8259", "hex", + "impl-serde", "indexmap 2.0.0", "ink", "ink_env", "ink_metadata", "itertools 0.11.0", + "libsecp256k1", "nom", "nom-supreme", "parity-scale-codec", @@ -878,8 +880,11 @@ dependencies = [ "scale-info", "serde", "serde_json", + "sha3", "sp-core 20.0.0", + "sp-io 23.0.0", "sp-keyring", + "sp-runtime 24.0.0", "strsim", "thiserror", "tracing", diff --git a/crates/transcode/Cargo.toml b/crates/transcode/Cargo.toml index 25fc1d7ff..9a80b211f 100644 --- a/crates/transcode/Cargo.toml +++ b/crates/transcode/Cargo.toml @@ -38,6 +38,12 @@ serde_json = "1.0.99" thiserror = "1.0.40" strsim = "0.10.0" +libsecp256k1 = { version = "0.7", default-features = false , features = [ "hmac" ] } +sha3 = "0.10.8" +sp-io = "23.0.0" +impl-serde = "0.4.0" +sp-runtime = "24.0.0" + [dev-dependencies] assert_matches = "1.5.0" ink = "4.2.0" diff --git a/crates/cargo-contract/src/account.rs b/crates/transcode/src/account.rs similarity index 73% rename from crates/cargo-contract/src/account.rs rename to crates/transcode/src/account.rs index f1d61e18e..c306289bf 100644 --- a/crates/cargo-contract/src/account.rs +++ b/crates/transcode/src/account.rs @@ -23,72 +23,72 @@ use scale::{Decode, Encode, MaxEncodedLen}; use scale_info::TypeInfo; use sha3::{Digest, Keccak256}; -use sp_core::{ecdsa, H160}; - -pub use serde::{de::DeserializeOwned, Deserialize, Serialize}; - -//TODO Maybe this should be upstreamed into Frontier (And renamed accordingly) so that it can -// be used in palletEVM as well. It may also need more traits such as AsRef, AsMut, etc like -// AccountId32 has. - -/// The account type to be used in Moonbeam. It is a wrapper for 20 fixed bytes. We prefer to use -/// a dedicated type to prevent using arbitrary 20 byte arrays were AccountIds are expected. With -/// the introduction of the `scale-info` crate this benefit extends even to non-Rust tools like -/// Polkadot JS. - -#[derive( - Eq, PartialEq, Copy, Clone, Encode, Decode, TypeInfo, MaxEncodedLen, Default, PartialOrd, Ord, -)] -pub struct AccountId20(pub [u8; 20]); - -impl_serde::impl_fixed_hash_serde!(AccountId20, 20); - -impl std::fmt::Display for AccountId20 { - //TODO This is a pretty quck-n-dirty implementation. Perhaps we should add - // checksum casing here? I bet there is a crate for that. - // Maybe this one https://github.com/miguelmota/rust-eth-checksum - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "{:?}", self.0) - } -} - -impl core::fmt::Debug for AccountId20 { - fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - write!(f, "{:?}", H160(self.0)) - } -} - -impl From<[u8; 20]> for AccountId20 { - fn from(bytes: [u8; 20]) -> Self { - Self(bytes) - } -} - -impl From for [u8; 20] { - fn from(value: AccountId20) -> Self { - value.0 - } -} - -impl From<[u8; 32]> for AccountId20 { - fn from(bytes: [u8; 32]) -> Self { - let mut buffer = [0u8; 20]; - buffer.copy_from_slice(&bytes[..20]); - Self(buffer) - } -} - -impl From for AccountId20 { - fn from(h160: H160) -> Self { - Self(h160.0) - } -} - -impl From for H160 { - fn from(value: AccountId20) -> Self { - H160(value.0) - } -} +// use sp_core::{ecdsa, H160}; + +// pub use serde::{de::DeserializeOwned, Deserialize, Serialize}; + +// //TODO Maybe this should be upstreamed into Frontier (And renamed accordingly) so that it can +// // be used in palletEVM as well. It may also need more traits such as AsRef, AsMut, etc like +// // AccountId32 has. + +// /// The account type to be used in Moonbeam. It is a wrapper for 20 fixed bytes. We prefer to use +// /// a dedicated type to prevent using arbitrary 20 byte arrays were AccountIds are expected. With +// /// the introduction of the `scale-info` crate this benefit extends even to non-Rust tools like +// /// Polkadot JS. + +// #[derive( +// Eq, PartialEq, Copy, Clone, Encode, Decode, TypeInfo, MaxEncodedLen, Default, PartialOrd, Ord, +// )] +// pub struct AccountId20(pub [u8; 20]); + +// impl_serde::impl_fixed_hash_serde!(AccountId20, 20); + +// impl std::fmt::Display for AccountId20 { +// //TODO This is a pretty quck-n-dirty implementation. Perhaps we should add +// // checksum casing here? I bet there is a crate for that. +// // Maybe this one https://github.com/miguelmota/rust-eth-checksum +// fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { +// write!(f, "{:?}", self.0) +// } +// } + +// impl core::fmt::Debug for AccountId20 { +// fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { +// write!(f, "{:?}", H160(self.0)) +// } +// } + +// impl From<[u8; 20]> for AccountId20 { +// fn from(bytes: [u8; 20]) -> Self { +// Self(bytes) +// } +// } + +// impl From for [u8; 20] { +// fn from(value: AccountId20) -> Self { +// value.0 +// } +// } + +// impl From<[u8; 32]> for AccountId20 { +// fn from(bytes: [u8; 32]) -> Self { +// let mut buffer = [0u8; 20]; +// buffer.copy_from_slice(&bytes[..20]); +// Self(buffer) +// } +// } + +// impl From for AccountId20 { +// fn from(h160: H160) -> Self { +// Self(h160.0) +// } +// } + +// impl From for H160 { +// fn from(value: AccountId20) -> Self { +// H160(value.0) +// } +// } impl std::str::FromStr for AccountId20 { type Err = &'static str; @@ -100,7 +100,7 @@ impl std::str::FromStr for AccountId20 { } #[derive(serde::Serialize, serde::Deserialize)] -#[derive(Eq, PartialEq, Clone, Encode, Decode, sp_core::RuntimeDebug, TypeInfo)] +#[derive(Eq, PartialEq, Clone, Encode, Decode, TypeInfo)] pub struct EthereumSignature(ecdsa::Signature); impl From for EthereumSignature { @@ -137,7 +137,7 @@ impl sp_runtime::traits::Verify for EthereumSignature { /// Public key for an Ethereum / Moonbeam compatible account #[derive( - Eq, PartialEq, Ord, PartialOrd, Clone, Encode, Decode, sp_core::RuntimeDebug, TypeInfo, + Eq, PartialEq, Ord, PartialOrd, Clone, Encode, Decode, TypeInfo, )] #[derive(serde::Serialize, serde::Deserialize)] pub struct EthereumSigner([u8; 20]); diff --git a/crates/transcode/src/lib.rs b/crates/transcode/src/lib.rs index 3730da92b..b036fff21 100644 --- a/crates/transcode/src/lib.rs +++ b/crates/transcode/src/lib.rs @@ -99,6 +99,7 @@ //! ``` // mod account_id; +mod account; mod decode; mod encode; pub mod env_types;