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

Add staking functions to cosmos-sdk #82

Merged
merged 16 commits into from
Jun 22, 2021
1 change: 1 addition & 0 deletions cosmos-sdk-rs/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@

pub mod bank;
pub mod crypto;
pub mod staking;
pub mod tx;

#[cfg(feature = "dev")]
Expand Down
240 changes: 240 additions & 0 deletions cosmos-sdk-rs/src/staking.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,240 @@
//! Staking module support
//!
//! <https://docs.cosmos.network/master/modules/staking/>

use crate::{
proto,
tx::{Msg, MsgType},
AccountId, Coin, Result,
};
use std::convert::{TryFrom, TryInto};

/// MsgDelegate represents a message to delegate coins to a validator.
#[derive(Clone, Debug, Eq, PartialEq, PartialOrd, Ord)]
pub struct MsgDelegate {
/// Delegator's address.
pub delegator_address: AccountId,

/// Validator's address.
pub validator_address: AccountId,

/// Amount to send
pub amount: Option<Coin>,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm curious... when would this ever be None?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

proto::cosmos::staking::v1beta1::MsgDelegate's amount can be None, so I'm mirroring that here.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The Rust code generated from the protos has no way to express non-optional fields for messages, so that's not a great thing to go off of.

Looking at the raw Protobuf definition, it's:

https://github.com/cosmos/cosmos-sdk/blob/9fd866e3820b3510010ae172b682d71594cd8c14/proto/cosmos/staking/v1beta1/tx.proto#L89

message MsgDelegate {
  option (gogoproto.equal)           = false;
  option (gogoproto.goproto_getters) = false;

  string                   delegator_address = 1 [(gogoproto.moretags) = "yaml:\"delegator_address\""];
  string                   validator_address = 2 [(gogoproto.moretags) = "yaml:\"validator_address\""];
  cosmos.base.v1beta1.Coin amount            = 3 [(gogoproto.nullable) = false];
}

It's annotated as non-nullable (using a custom gogoproto directive which the Rust proto parser doesn't understand), so this should be changed to pub amount: Coin and messages missing this field should be rejected.

}

impl MsgType for MsgDelegate {
fn from_msg(msg: &Msg) -> Result<Self> {
proto::cosmos::staking::v1beta1::MsgDelegate::from_msg(msg).and_then(TryInto::try_into)
}

fn to_msg(&self) -> Result<Msg> {
proto::cosmos::staking::v1beta1::MsgDelegate::from(self).to_msg()
}
}

impl TryFrom<proto::cosmos::staking::v1beta1::MsgDelegate> for MsgDelegate {
type Error = eyre::Report;

fn try_from(proto: proto::cosmos::staking::v1beta1::MsgDelegate) -> Result<MsgDelegate> {
MsgDelegate::try_from(&proto)
}
}

impl TryFrom<&proto::cosmos::staking::v1beta1::MsgDelegate> for MsgDelegate {
type Error = eyre::Report;

fn try_from(proto: &proto::cosmos::staking::v1beta1::MsgDelegate) -> Result<MsgDelegate> {
let amount = if let Some(amount) = &proto.amount {
Some(Coin {
denom: amount.denom.parse()?,
amount: amount.amount.parse()?,
})
} else {
None
};
Ok(MsgDelegate {
delegator_address: proto.delegator_address.parse()?,
validator_address: proto.validator_address.parse()?,
amount,
})
}
}

impl From<MsgDelegate> for proto::cosmos::staking::v1beta1::MsgDelegate {
fn from(coin: MsgDelegate) -> proto::cosmos::staking::v1beta1::MsgDelegate {
proto::cosmos::staking::v1beta1::MsgDelegate::from(&coin)
}
}

impl From<&MsgDelegate> for proto::cosmos::staking::v1beta1::MsgDelegate {
fn from(msg: &MsgDelegate) -> proto::cosmos::staking::v1beta1::MsgDelegate {
let proto_amount = if let Some(amount) = &msg.amount {
Some(proto::cosmos::base::v1beta1::Coin {
denom: amount.denom.to_string(),
amount: amount.amount.to_string(),
})
} else {
None
};
proto::cosmos::staking::v1beta1::MsgDelegate {
delegator_address: msg.delegator_address.to_string(),
validator_address: msg.validator_address.to_string(),
amount: proto_amount,
}
}
}

/// MsgUndelegate represents a message to undelegate coins from a validator.
#[derive(Clone, Debug, Eq, PartialEq, PartialOrd, Ord)]
pub struct MsgUndelegate {
/// Delegator's address.
pub delegator_address: AccountId,

/// Validator's address.
pub validator_address: AccountId,

/// Amount to UnDelegate
pub amount: Option<Coin>,
}

impl MsgType for MsgUndelegate {
fn from_msg(msg: &Msg) -> Result<Self> {
proto::cosmos::staking::v1beta1::MsgUndelegate::from_msg(msg).and_then(TryInto::try_into)
}

fn to_msg(&self) -> Result<Msg> {
proto::cosmos::staking::v1beta1::MsgUndelegate::from(self).to_msg()
}
}

impl TryFrom<proto::cosmos::staking::v1beta1::MsgUndelegate> for MsgUndelegate {
type Error = eyre::Report;

fn try_from(proto: proto::cosmos::staking::v1beta1::MsgUndelegate) -> Result<MsgUndelegate> {
MsgUndelegate::try_from(&proto)
}
}

impl TryFrom<&proto::cosmos::staking::v1beta1::MsgUndelegate> for MsgUndelegate {
type Error = eyre::Report;

fn try_from(proto: &proto::cosmos::staking::v1beta1::MsgUndelegate) -> Result<MsgUndelegate> {
let amount = if let Some(amount) = &proto.amount {
Some(Coin {
denom: amount.denom.parse()?,
amount: amount.amount.parse()?,
})
} else {
None
};
Ok(MsgUndelegate {
delegator_address: proto.delegator_address.parse()?,
validator_address: proto.validator_address.parse()?,
amount,
})
}
}

impl From<MsgUndelegate> for proto::cosmos::staking::v1beta1::MsgUndelegate {
fn from(coin: MsgUndelegate) -> proto::cosmos::staking::v1beta1::MsgUndelegate {
proto::cosmos::staking::v1beta1::MsgUndelegate::from(&coin)
}
}

impl From<&MsgUndelegate> for proto::cosmos::staking::v1beta1::MsgUndelegate {
fn from(msg: &MsgUndelegate) -> proto::cosmos::staking::v1beta1::MsgUndelegate {
let proto_amount = if let Some(amount) = &msg.amount {
Some(proto::cosmos::base::v1beta1::Coin {
denom: amount.denom.to_string(),
amount: amount.amount.to_string(),
})
} else {
None
};
proto::cosmos::staking::v1beta1::MsgUndelegate {
delegator_address: msg.delegator_address.to_string(),
validator_address: msg.validator_address.to_string(),
amount: proto_amount,
}
}
}

/// MsgBeginRedelegate represents a message to redelegate coins from one validator to another.
#[derive(Clone, Debug, Eq, PartialEq, PartialOrd, Ord)]
pub struct MsgBeginRedelegate {
/// Delegator's address.
pub delegator_address: AccountId,

/// Source validator's address.
pub validator_src_address: AccountId,

/// Destination validator's address.
pub validator_dst_address: AccountId,

/// Amount to UnDelegate
pub amount: Option<Coin>,
}

impl MsgType for MsgBeginRedelegate {
fn from_msg(msg: &Msg) -> Result<Self> {
proto::cosmos::staking::v1beta1::MsgBeginRedelegate::from_msg(msg).and_then(TryInto::try_into)
}

fn to_msg(&self) -> Result<Msg> {
proto::cosmos::staking::v1beta1::MsgBeginRedelegate::from(self).to_msg()
}
}

impl TryFrom<proto::cosmos::staking::v1beta1::MsgBeginRedelegate> for MsgBeginRedelegate {
type Error = eyre::Report;

fn try_from(proto: proto::cosmos::staking::v1beta1::MsgBeginRedelegate) -> Result<MsgBeginRedelegate> {
MsgBeginRedelegate::try_from(&proto)
}
}

impl TryFrom<&proto::cosmos::staking::v1beta1::MsgBeginRedelegate> for MsgBeginRedelegate {
type Error = eyre::Report;

fn try_from(proto: &proto::cosmos::staking::v1beta1::MsgBeginRedelegate) -> Result<MsgBeginRedelegate> {
let amount = if let Some(amount) = &proto.amount {
Some(Coin {
denom: amount.denom.parse()?,
amount: amount.amount.parse()?,
})
} else {
None
};
Ok(MsgBeginRedelegate {
delegator_address: proto.delegator_address.parse()?,
validator_src_address: proto.validator_src_address.parse()?,
validator_dst_address: proto.validator_dst_address.parse()?,
amount,
})
}
}

impl From<MsgBeginRedelegate> for proto::cosmos::staking::v1beta1::MsgBeginRedelegate {
fn from(coin: MsgBeginRedelegate) -> proto::cosmos::staking::v1beta1::MsgBeginRedelegate {
proto::cosmos::staking::v1beta1::MsgBeginRedelegate::from(&coin)
}
}

impl From<&MsgBeginRedelegate> for proto::cosmos::staking::v1beta1::MsgBeginRedelegate {
fn from(msg: &MsgBeginRedelegate) -> proto::cosmos::staking::v1beta1::MsgBeginRedelegate {
let proto_amount = if let Some(amount) = &msg.amount {
Some(proto::cosmos::base::v1beta1::Coin {
denom: amount.denom.to_string(),
amount: amount.amount.to_string(),
})
} else {
None
};
proto::cosmos::staking::v1beta1::MsgBeginRedelegate {
delegator_address: msg.delegator_address.to_string(),
validator_src_address: msg.validator_src_address.to_string(),
validator_dst_address: msg.validator_dst_address.to_string(),
amount: proto_amount,
}
}
}
12 changes: 12 additions & 0 deletions cosmos-sdk-rs/src/tx/msg.rs
Original file line number Diff line number Diff line change
Expand Up @@ -75,3 +75,15 @@ where
impl MsgProto for proto::cosmos::bank::v1beta1::MsgSend {
const TYPE_URL: &'static str = "/cosmos.bank.v1beta1.MsgSend";
}

impl MsgProto for proto::cosmos::staking::v1beta1::MsgDelegate {
const TYPE_URL: &'static str = "/cosmos.staking.v1beta1.MsgDelegate";
}

impl MsgProto for proto::cosmos::staking::v1beta1::MsgUndelegate {
const TYPE_URL: &'static str = "/cosmos.staking.v1beta1.MsgUndelegate";
}

impl MsgProto for proto::cosmos::staking::v1beta1::MsgBeginRedelegate {
const TYPE_URL: &'static str = "/cosmos.staking.v1beta1.MsgBeginRedelegate";
}