-
Notifications
You must be signed in to change notification settings - Fork 124
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[WIP] cosmos-tx: MsgSend support w\ integration test
Basic support for `MsgSend` with end-to-end integration test. Adds the following: - Traits for simplifying Protobuf serialization: - `prost_ext::MessageExt` for Protobuf encoding - `msg::MsgType` and `msg::MsgProto` for decoding/encoding `Msg` types as Protobuf `Any` messages. - Domain types which model the following: - `Coin`: amount to send and a denom - `Denom`: name of a particular denomination - `Fee`: transaction fees - `MsgSend`: transaction message for performing a simple send Additionally includes an end-to-end test which uses Docker to spawn a single-node `gaia` and send a transaction.
- Loading branch information
1 parent
4d3db43
commit 9558257
Showing
15 changed files
with
788 additions
and
60 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,72 @@ | ||
//! Bank module support | ||
//! | ||
//! <https://docs.cosmos.network/master/modules/bank/> | ||
|
||
use crate::{AccountId, Coin, Msg, MsgType, Result}; | ||
use cosmos_sdk_proto::cosmos; | ||
use std::convert::{TryFrom, TryInto}; | ||
|
||
/// MsgSend represents a message to send coins from one account to another. | ||
#[derive(Clone, Debug, Eq, PartialEq, PartialOrd, Ord)] | ||
pub struct MsgSend { | ||
/// Sender's address. | ||
pub from_address: AccountId, | ||
|
||
/// Recipient's address. | ||
pub to_address: AccountId, | ||
|
||
/// Amount to send | ||
pub amount: Vec<Coin>, | ||
} | ||
|
||
impl MsgType for MsgSend { | ||
fn from_msg(msg: &Msg) -> Result<Self> { | ||
cosmos::bank::v1beta1::MsgSend::from_msg(msg).and_then(TryInto::try_into) | ||
} | ||
|
||
fn to_msg(&self) -> Result<Msg> { | ||
let proto = cosmos::bank::v1beta1::MsgSend::from(self); | ||
dbg!(&proto); | ||
proto.to_msg() | ||
} | ||
} | ||
|
||
impl TryFrom<cosmos::bank::v1beta1::MsgSend> for MsgSend { | ||
type Error = eyre::Report; | ||
|
||
fn try_from(proto: cosmos::bank::v1beta1::MsgSend) -> Result<MsgSend> { | ||
MsgSend::try_from(&proto) | ||
} | ||
} | ||
|
||
impl TryFrom<&cosmos::bank::v1beta1::MsgSend> for MsgSend { | ||
type Error = eyre::Report; | ||
|
||
fn try_from(proto: &cosmos::bank::v1beta1::MsgSend) -> Result<MsgSend> { | ||
Ok(MsgSend { | ||
from_address: proto.from_address.parse()?, | ||
to_address: proto.to_address.parse()?, | ||
amount: proto | ||
.amount | ||
.iter() | ||
.map(TryFrom::try_from) | ||
.collect::<Result<_, _>>()?, | ||
}) | ||
} | ||
} | ||
|
||
impl From<MsgSend> for cosmos::bank::v1beta1::MsgSend { | ||
fn from(coin: MsgSend) -> cosmos::bank::v1beta1::MsgSend { | ||
cosmos::bank::v1beta1::MsgSend::from(&coin) | ||
} | ||
} | ||
|
||
impl From<&MsgSend> for cosmos::bank::v1beta1::MsgSend { | ||
fn from(msg: &MsgSend) -> cosmos::bank::v1beta1::MsgSend { | ||
cosmos::bank::v1beta1::MsgSend { | ||
from_address: msg.from_address.to_string(), | ||
to_address: msg.to_address.to_string(), | ||
amount: msg.amount.iter().map(Into::into).collect(), | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,182 @@ | ||
//! Base functionality. | ||
|
||
use crate::{Decimal, Error, PublicKey, Result}; | ||
use cosmos_sdk_proto::cosmos; | ||
use std::{ | ||
convert::{TryFrom, TryInto}, | ||
fmt, | ||
str::FromStr, | ||
}; | ||
use subtle_encoding::bech32; | ||
|
||
/// Account identifiers | ||
#[derive(Clone, Eq, PartialEq, PartialOrd, Ord)] | ||
pub struct AccountId { | ||
/// Account ID encoded as Bech32 | ||
bech32: String, | ||
|
||
/// Length of the human-readable prefix of the address | ||
hrp_length: usize, | ||
} | ||
|
||
impl AccountId { | ||
/// Create an [`AccountId`] with the given human-readable prefix and | ||
/// public key hash. | ||
pub fn new(prefix: &str, bytes: [u8; tendermint::account::LENGTH]) -> Result<Self> { | ||
let id = bech32::encode(prefix, &bytes); | ||
|
||
// TODO(tarcieri): ensure this is the proper validation for an account prefix | ||
if prefix.chars().all(|c| matches!(c, 'a'..='z')) { | ||
Ok(Self { | ||
bech32: id, | ||
hrp_length: prefix.len(), | ||
}) | ||
} else { | ||
Err(Error::AccountId { id }.into()) | ||
} | ||
} | ||
|
||
/// Create an [`AccountId`] for the given [`PublicKey`] | ||
// TODO(tarcieri): extract our own public key type and move this method there | ||
pub fn for_public_key(key: &PublicKey, prefix: &str) -> Result<Self> { | ||
let account_id = match key { | ||
PublicKey::Secp256k1(encoded_point) => tendermint::account::Id::from(*encoded_point), | ||
_ => return Err(Error::Crypto.into()), | ||
}; | ||
|
||
AccountId::new(prefix, account_id.as_bytes().try_into()?) | ||
} | ||
|
||
/// Get the human-readable prefix of this account. | ||
pub fn prefix(&self) -> &str { | ||
&self.bech32[..self.hrp_length] | ||
} | ||
|
||
/// Decode an account ID from Bech32 to an inner byte value. | ||
pub fn to_bytes(&self) -> [u8; tendermint::account::LENGTH] { | ||
bech32::decode(&self.bech32) | ||
.ok() | ||
.and_then(|result| result.1.try_into().ok()) | ||
.expect("malformed Bech32 AccountId") | ||
} | ||
} | ||
|
||
impl AsRef<str> for AccountId { | ||
fn as_ref(&self) -> &str { | ||
&self.bech32 | ||
} | ||
} | ||
|
||
impl fmt::Debug for AccountId { | ||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { | ||
f.debug_tuple("AccountId").field(&self.as_ref()).finish() | ||
} | ||
} | ||
|
||
impl fmt::Display for AccountId { | ||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { | ||
f.write_str(self.as_ref()) | ||
} | ||
} | ||
|
||
impl FromStr for AccountId { | ||
type Err = eyre::Report; | ||
|
||
fn from_str(s: &str) -> Result<Self> { | ||
let (hrp, bytes) = bech32::decode(s)?; | ||
|
||
if bytes.len() == tendermint::account::LENGTH { | ||
Ok(Self { | ||
bech32: s.to_owned(), | ||
hrp_length: hrp.len(), | ||
}) | ||
} else { | ||
Err(Error::AccountId { id: s.to_owned() }.into()) | ||
} | ||
} | ||
} | ||
|
||
impl From<AccountId> for tendermint::account::Id { | ||
fn from(id: AccountId) -> tendermint::account::Id { | ||
tendermint::account::Id::from(&id) | ||
} | ||
} | ||
|
||
impl From<&AccountId> for tendermint::account::Id { | ||
fn from(id: &AccountId) -> tendermint::account::Id { | ||
tendermint::account::Id::new(id.to_bytes()) | ||
} | ||
} | ||
|
||
/// Coin defines a token with a denomination and an amount. | ||
#[derive(Clone, Debug, Eq, PartialEq, PartialOrd, Ord)] | ||
pub struct Coin { | ||
/// Denomination | ||
pub denom: Denom, | ||
|
||
/// Amount | ||
pub amount: Decimal, | ||
} | ||
|
||
impl TryFrom<cosmos::base::v1beta1::Coin> for Coin { | ||
type Error = eyre::Report; | ||
|
||
fn try_from(proto: cosmos::base::v1beta1::Coin) -> Result<Coin> { | ||
Coin::try_from(&proto) | ||
} | ||
} | ||
|
||
impl TryFrom<&cosmos::base::v1beta1::Coin> for Coin { | ||
type Error = eyre::Report; | ||
|
||
fn try_from(proto: &cosmos::base::v1beta1::Coin) -> Result<Coin> { | ||
Ok(Coin { | ||
denom: proto.denom.parse()?, | ||
amount: proto.amount.parse()?, | ||
}) | ||
} | ||
} | ||
|
||
impl From<Coin> for cosmos::base::v1beta1::Coin { | ||
fn from(coin: Coin) -> cosmos::base::v1beta1::Coin { | ||
cosmos::base::v1beta1::Coin::from(&coin) | ||
} | ||
} | ||
|
||
impl From<&Coin> for cosmos::base::v1beta1::Coin { | ||
fn from(coin: &Coin) -> cosmos::base::v1beta1::Coin { | ||
cosmos::base::v1beta1::Coin { | ||
denom: coin.denom.to_string(), | ||
amount: coin.amount.to_proto_string(), | ||
} | ||
} | ||
} | ||
|
||
/// Denomination. | ||
#[derive(Clone, Debug, Eq, PartialEq, PartialOrd, Ord)] | ||
pub struct Denom(String); | ||
|
||
impl AsRef<str> for Denom { | ||
fn as_ref(&self) -> &str { | ||
self.0.as_ref() | ||
} | ||
} | ||
|
||
impl fmt::Display for Denom { | ||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { | ||
f.write_str(self.as_ref()) | ||
} | ||
} | ||
|
||
impl FromStr for Denom { | ||
type Err = eyre::Report; | ||
|
||
fn from_str(s: &str) -> Result<Self> { | ||
// TODO(tarcieri): ensure this is the proper validation for a denom name | ||
if s.chars().all(|c| matches!(c, 'a'..='z')) { | ||
Ok(Denom(s.to_owned())) | ||
} else { | ||
Err(Error::Denom { name: s.to_owned() }.into()) | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.