use super::account::TestAccount;
use crate::chain_config::ChainConfig;
use crate::cosmos_client::gas_info::ClientGasInfo;
use crate::cosmos_client::CosmosClient;
use crate::tx_builder::derivation_path::CosmosDerivationPath;
use crate::tx_builder::TxBuilder;
use cosmrs::bank::MsgSend;
use cosmrs::cosmwasm::{MsgExecuteContract, MsgInstantiateContract};
use cosmrs::tendermint::abci::Code;
use cosmrs::tx::Msg;
use cosmrs::{AccountId, Coin};
use std::sync::atomic::{AtomicUsize, Ordering};
use std::sync::Arc;
use std::time::SystemTime;
use tendermint_rpc::{HttpClient, WebSocketClient};
static CONTRACT_COUNTER: AtomicUsize = AtomicUsize::new(0);
pub struct TestScenario {
pub cosmos_client: Arc<CosmosClient>,
pub websocket_client: Arc<CosmosClient<WebSocketClient>>,
pub admin_mnemonic: String,
pub chain_prefix: String,
pub chain_id: String,
pub chain_denom: String,
pub derivation_path: CosmosDerivationPath,
pub admin_address: String,
pub price_feeder_account: TestAccount,
pub gas_price: u128,
pub gas_adjustment: Option<f64>,
pub oracle_code_id: u64,
pub settlement_code_id: u64,
pub router_code_id: u64,
}
impl TestScenario {
#[allow(clippy::too_many_arguments)]
pub async fn new(
rpc_url: String,
websocket_url: String,
admin_mnemonic: String,
chain_prefix: String,
chain_id: String,
chain_denom: String,
derivation_path: CosmosDerivationPath,
gas_price: u128,
gas_adjustment: Option<f64>,
oracle_code_id: u64,
settlement_code_id: u64,
router_code_id: u64,
) -> Self {
let tx_builder = TxBuilder::new(
admin_mnemonic.clone(),
chain_prefix.clone(),
chain_id.clone(),
derivation_path,
0,
0,
)
.expect("Failed to create TxBuilder");
let admin_address = tx_builder.account_id.to_string();
let cosmos_client = Arc::new(
CosmosClient::<HttpClient>::new(&rpc_url)
.await
.unwrap()
.with_gas_info(
ClientGasInfo::new(gas_price, chain_denom.clone())
.with_adjustment(gas_adjustment.unwrap_or(1.0)),
),
);
let websocket_client = Arc::new(
CosmosClient::<WebSocketClient>::new(&websocket_url)
.await
.unwrap(),
);
let price_feeder_account =
TestAccount::new_random(&chain_prefix, &chain_id, derivation_path);
let scenario = TestScenario {
cosmos_client,
websocket_client,
admin_mnemonic,
chain_prefix,
chain_id,
chain_denom,
derivation_path,
admin_address,
price_feeder_account,
gas_price,
gas_adjustment,
oracle_code_id,
settlement_code_id,
router_code_id,
};
scenario
.fund_account(&scenario.price_feeder_account, 1)
.await;
scenario
}
pub fn gas_info(&self) -> ClientGasInfo {
ClientGasInfo::new(self.gas_price, self.chain_denom.clone())
.with_adjustment(self.gas_adjustment.unwrap_or(1.0))
}
pub async fn new_from_config(file_path: String) -> Self {
let config = std::fs::read_to_string(file_path).expect("Failed to read config file");
let config: serde_json::Value =
serde_json::from_str(&config).expect("Failed to parse config file");
let rpc_url = config["rpc_client_url"]
.as_str()
.expect("Failed to get rpc url")
.to_string();
let websocket_url = config["websocket_url"]
.as_str()
.expect("Failed to get websocket url")
.to_string();
let admin_mnemonic = config["admin_mnemonic"]
.as_str()
.expect("Failed to get admin mnemonic")
.to_string();
let chain_prefix = config["chain_prefix"]
.as_str()
.expect("Failed to get chain prefix")
.to_string();
let chain_id = config["chain_id"]
.as_str()
.expect("Failed to get chain id")
.to_string();
let chain_denom = config["chain_denom"]
.as_str()
.expect("Failed to get chain denom")
.to_string();
let derivation_path = CosmosDerivationPath::default();
let oracle_code_id = config["oracle_code_id"]
.as_u64()
.expect("Failed to get oracle code id");
let settlement_code_id = config["settlement_code_id"]
.as_u64()
.expect("Failed to get settlement code id");
let router_code_id = config["router_code_id"]
.as_u64()
.expect("Failed to get router code id");
let gas_price = config["gas_price"]
.as_u64()
.expect("Failed to get gas price") as u128;
let gas_adjustment = config["gas_adjustment"].as_f64();
TestScenario::new(
rpc_url,
websocket_url,
admin_mnemonic,
chain_prefix,
chain_id,
chain_denom,
derivation_path,
gas_price,
gas_adjustment,
oracle_code_id,
settlement_code_id,
router_code_id,
)
.await
}
pub fn get_chain_config(&self, oracle_contract_address: &str) -> ChainConfig {
let oracle_contract_address = oracle_contract_address
.parse()
.expect("Failed to parse oracle contract address");
ChainConfig {
prefix: self.chain_prefix.clone(),
oracle_contract_address,
chain_id: self.chain_id.clone(),
fee_denom: self.chain_denom.clone(),
}
}
pub fn add_contract_instantiate_msg(
&self,
tx_builder: &mut TxBuilder,
code_id: u64,
label: String,
msg: Vec<u8>,
funds: Vec<Coin>,
) {
let msg = MsgInstantiateContract {
sender: tx_builder.account_id.clone(),
code_id,
label: Some(label),
msg,
funds,
admin: Some(tx_builder.account_id.clone()),
};
let any_msg = msg
.to_any()
.expect("Failed to create contract instantiate msg");
tx_builder.add_msg(any_msg);
}
pub fn add_contract_execute_msg(
&self,
tx_builder: &mut TxBuilder,
contract_address: AccountId,
msg: Vec<u8>,
funds: Vec<Coin>,
) {
let msg = MsgExecuteContract {
sender: tx_builder.account_id.clone(),
contract: contract_address,
msg,
funds,
};
let any_msg = msg.to_any().expect("Failed to create contract execute msg");
tx_builder.add_msg(any_msg);
}
pub async fn instantiate_contract(
&self,
msg: Vec<u8>,
code_id: u64,
contract_name: &str,
) -> String {
let account = self
.cosmos_client
.account(self.admin_address.clone())
.await
.expect("Get account");
let mut tx_builder = TxBuilder::new(
self.admin_mnemonic.clone(),
self.chain_prefix.clone(),
self.chain_id.clone(),
self.derivation_path,
account.sequence,
account.account_number,
)
.expect("Create TxBuilder");
self.add_contract_instantiate_msg(
&mut tx_builder,
code_id,
format!("bolt-{}", contract_name),
msg,
vec![],
);
tx_builder.set_memo(format!(
"Instantiate {}-{} contract at {}",
contract_name,
CONTRACT_COUNTER.fetch_add(1, Ordering::Relaxed),
SystemTime::now()
.duration_since(SystemTime::UNIX_EPOCH)
.unwrap()
.as_nanos()
));
tx_builder.set_timeout_height(0u8);
let gas = 480_000u64;
let amount = 65_000_000_000_000_000u128;
tx_builder.set_fee(amount, &self.chain_denom, gas);
let signed_bytes = tx_builder.get_signed_bytes().expect("Get signed bytes");
let response = self
.cosmos_client
.broadcast_tx(signed_bytes)
.await
.expect("Should broadcast tx");
match response.tx_result.code {
Code::Ok => {
if response.check_tx.code.is_err() {
panic!(
"Failed to send the tx.\nCode: {}\nLog: {}",
response.check_tx.code.value(),
response.check_tx.log
);
}
println!(
"{} contract instantiated with hash: {:?}",
contract_name,
response.hash.to_string()
);
let instantiate_event = response
.tx_result
.events
.iter()
.find(|ev| ev.kind == "instantiate")
.expect("Should find instantiate event");
let contract_address_event_attr = instantiate_event
.attributes
.iter()
.find(|attr| {
attr.key_str().expect("Should stringify attribute key")
== "_contract_address"
})
.expect("Should find the contract address attribute")
.clone();
contract_address_event_attr
.value_str()
.expect("Should stringify the contract address")
.to_string()
}
_ => {
panic!(
"Failed to instantiate {} contract: {:?}",
contract_name, response
);
}
}
}
pub fn add_send_msg(
&self,
tx_builder: &mut TxBuilder,
sender: AccountId,
receiver: AccountId,
denom: &str,
amount: u128,
) {
let coin = Coin {
denom: denom.parse().unwrap(),
amount,
};
let send_msg = MsgSend {
from_address: sender,
to_address: receiver,
amount: vec![coin],
};
let msg = send_msg.to_any().expect("Should serialize the SendMsg");
tx_builder.add_msg(msg);
}
pub fn admin_account(&self) -> TestAccount {
TestAccount {
mnemonic: self.admin_mnemonic.clone(),
account_id: self.admin_address.parse().unwrap(),
}
}
}