use crate::error::{Error, Result};
use crate::msg::crossvm::cross_vm_call_any;
use crate::query::QorClient;
use crate::tx::{
broadcast, send_messages, BroadcastMode, BuiltTx, Coin as TxCoin, Fee, SendMessagesParams,
};
use cosmrs::proto::cosmos::base::v1beta1::Coin as ProtoCoin;
use serde_json::Value;
pub const VM_TYPE_EVM: &str = "evm";
pub const VM_TYPE_COSMWASM: &str = "cosmwasm";
pub const VM_TYPE_SVM: &str = "svm";
pub const VM_TYPES: &[&str] = &[VM_TYPE_EVM, VM_TYPE_COSMWASM, VM_TYPE_SVM];
#[derive(Debug, Clone)]
pub enum Payload {
Raw(Vec<u8>),
CosmWasm(Value),
}
impl Payload {
pub fn to_bytes(&self) -> Result<Vec<u8>> {
match self {
Payload::Raw(b) => Ok(b.clone()),
Payload::CosmWasm(v) => serde_json::to_vec(v)
.map_err(|e| Error::InvalidResponse(format!("serialize cosmwasm payload: {e}"))),
}
}
}
impl From<Vec<u8>> for Payload {
fn from(b: Vec<u8>) -> Self {
Payload::Raw(b)
}
}
impl From<Value> for Payload {
fn from(v: Value) -> Self {
Payload::CosmWasm(v)
}
}
#[derive(Debug, Clone)]
pub struct CallOptions {
pub source_vm: String,
pub target_vm: String,
pub target_contract: String,
pub payload: Payload,
pub funds: Vec<TxCoin>,
}
impl CallOptions {
pub fn new(
target_vm: impl Into<String>,
target_contract: impl Into<String>,
payload: impl Into<Payload>,
) -> Self {
Self {
source_vm: VM_TYPE_EVM.to_string(),
target_vm: target_vm.into(),
target_contract: target_contract.into(),
payload: payload.into(),
funds: Vec::new(),
}
}
pub fn source_vm(mut self, vm: impl Into<String>) -> Self {
self.source_vm = vm.into();
self
}
pub fn funds(mut self, funds: Vec<TxCoin>) -> Self {
self.funds = funds;
self
}
}
#[derive(Debug, Clone)]
pub struct CrossVm {
pub sender: String,
pub private_key: Vec<u8>,
pub public_key: Vec<u8>,
pub chain_id: String,
pub account_number: u64,
pub sequence: u64,
pub fee: Fee,
pub rest_url: String,
pub mode: BroadcastMode,
pub qor: Option<QorClient>,
}
impl CrossVm {
pub fn build_call(&self, opts: &CallOptions) -> Result<BuiltTx> {
self.build_atomic(std::slice::from_ref(opts))
}
pub async fn call(&self, opts: &CallOptions) -> Result<Value> {
let built = self.build_call(opts)?;
broadcast(&self.rest_url, &built.tx_raw_bytes, self.mode).await
}
pub async fn call_atomic(&self, opts: &[CallOptions]) -> Result<Value> {
let built = self.build_atomic(opts)?;
broadcast(&self.rest_url, &built.tx_raw_bytes, self.mode).await
}
pub fn build_atomic(&self, opts: &[CallOptions]) -> Result<BuiltTx> {
if opts.is_empty() {
return Err(Error::InvalidResponse(
"cross_vm: at least one call is required".into(),
));
}
let mut messages = Vec::with_capacity(opts.len());
for o in opts {
let source_vm = if o.source_vm.is_empty() {
VM_TYPE_EVM
} else {
o.source_vm.as_str()
};
let payload = o.payload.to_bytes()?;
messages.push(cross_vm_call_any(
self.sender.clone(),
source_vm,
o.target_vm.clone(),
o.target_contract.clone(),
payload,
to_proto_coins(&o.funds),
));
}
send_messages(SendMessagesParams {
private_key: self.private_key.clone(),
public_key: self.public_key.clone(),
messages,
chain_id: self.chain_id.clone(),
account_number: self.account_number,
sequence: self.sequence,
fee: self.fee.clone(),
memo: String::new(),
timeout_height: 0,
})
}
pub async fn get_message(&self, id: &str) -> Result<Value> {
let qor = self.qor.as_ref().ok_or_else(|| {
Error::MissingEndpoint("qor (cross_vm.get_message requires a QorClient)".into())
})?;
qor.get_cross_vm_message(id).await
}
}
fn to_proto_coins(coins: &[TxCoin]) -> Vec<ProtoCoin> {
coins
.iter()
.map(|c| ProtoCoin {
denom: c.denom.clone(),
amount: c.amount.clone(),
})
.collect()
}
#[cfg(test)]
mod tests {
use super::*;
use serde_json::json;
#[test]
fn payload_raw_passthrough() {
let p = Payload::Raw(vec![1, 2, 3]);
assert_eq!(p.to_bytes().unwrap(), vec![1, 2, 3]);
}
#[test]
fn payload_cosmwasm_is_compact_json() {
let p = Payload::CosmWasm(json!({ "increment": {} }));
assert_eq!(p.to_bytes().unwrap(), br#"{"increment":{}}"#.to_vec());
}
#[test]
fn call_options_defaults_source_to_evm() {
let o = CallOptions::new(VM_TYPE_COSMWASM, "qor1contract", vec![0u8]);
assert_eq!(o.source_vm, VM_TYPE_EVM);
assert_eq!(o.target_vm, "cosmwasm");
}
#[test]
fn vm_type_constants() {
assert_eq!(VM_TYPES, &["evm", "cosmwasm", "svm"]);
}
}