iota_cluster_test/
helper.rs1use anyhow::bail;
6use iota_json_rpc_types::{
7 BalanceChange, IotaData, IotaObjectData, IotaObjectDataOptions, IotaObjectResponseError,
8};
9use iota_sdk::IotaClient;
10use iota_sdk_types::{ObjectId, Owner, TypeTag};
11use iota_types::{gas_coin::GasCoin, parse_iota_type_tag};
12use tracing::{debug, trace};
13
14#[derive(Debug)]
22pub struct ObjectChecker {
23 object_id: ObjectId,
24 owner: Option<Owner>,
25 is_deleted: bool,
26 is_iota_coin: Option<bool>,
27}
28
29impl ObjectChecker {
30 pub fn new(object_id: ObjectId) -> ObjectChecker {
31 Self {
32 object_id,
33 owner: None,
34 is_deleted: false, is_iota_coin: None,
36 }
37 }
38
39 pub fn owner(mut self, owner: Owner) -> Self {
40 self.owner = Some(owner);
41 self
42 }
43
44 pub fn deleted(mut self) -> Self {
45 self.is_deleted = true;
46 self
47 }
48
49 pub fn is_iota_coin(mut self, is_iota_coin: bool) -> Self {
50 self.is_iota_coin = Some(is_iota_coin);
51 self
52 }
53
54 pub async fn check_into_gas_coin(self, client: &IotaClient) -> GasCoin {
55 if self.is_iota_coin == Some(false) {
56 panic!("'check_into_gas_coin' shouldn't be called with 'is_iota_coin' set as false");
57 }
58 self.is_iota_coin(true)
59 .check(client)
60 .await
61 .unwrap()
62 .into_gas_coin()
63 }
64
65 pub async fn check_into_object(self, client: &IotaClient) -> IotaObjectData {
66 self.check(client).await.unwrap().into_object()
67 }
68
69 pub async fn check(self, client: &IotaClient) -> Result<CheckerResultObject, anyhow::Error> {
70 debug!(?self);
71
72 let object_id = self.object_id;
73 let object_info = client
74 .read_api()
75 .get_object_with_options(
76 object_id,
77 IotaObjectDataOptions::new()
78 .with_type()
79 .with_owner()
80 .with_bcs(),
81 )
82 .await
83 .or_else(|err| bail!("failed to get object info (id: {object_id}), err: {err}"))?;
84
85 trace!("getting object {object_id}, info :: {object_info:?}");
86
87 match (object_info.data, object_info.error) {
88 (None, Some(IotaObjectResponseError::NotExists { object_id })) => {
89 panic!(
90 "node can't find gas object {object_id} with client {:?}",
91 client.read_api()
92 )
93 }
94 (
95 None,
96 Some(IotaObjectResponseError::DynamicFieldNotFound {
97 parent_object_id: object_id,
98 }),
99 ) => {
100 panic!(
101 "node can't find dynamic field for {object_id} with client {:?}",
102 client.read_api()
103 )
104 }
105 (
106 None,
107 Some(IotaObjectResponseError::Deleted {
108 object_id,
109 version: _,
110 digest: _,
111 }),
112 ) => {
113 if !self.is_deleted {
114 panic!("gas object {object_id} was deleted");
115 }
116 Ok(CheckerResultObject::new(None, None))
117 }
118 (Some(object), _) => {
119 if self.is_deleted {
120 panic!("expect gas object {object_id} deleted, but it is not");
121 }
122 if let Some(owner) = self.owner {
123 let object_owner = object
124 .owner
125 .unwrap_or_else(|| panic!("object {object_id} does not have owner"));
126 assert_eq!(
127 object_owner, owner,
128 "gas coin {object_id} does not belong to {owner}, but {object_owner}"
129 );
130 }
131 if self.is_iota_coin == Some(true) {
132 let move_obj = object
133 .bcs
134 .as_ref()
135 .unwrap_or_else(|| panic!("object {object_id} does not have bcs data"))
136 .try_as_move()
137 .unwrap_or_else(|| panic!("object {object_id} is not a move object"));
138
139 let gas_coin = move_obj.deserialize()?;
140 return Ok(CheckerResultObject::new(Some(gas_coin), Some(object)));
141 }
142 Ok(CheckerResultObject::new(None, Some(object)))
143 }
144 (None, Some(IotaObjectResponseError::Display { error })) => {
145 panic!("display error: {error:?}");
146 }
147 (None, None) | (None, Some(IotaObjectResponseError::Unknown)) => {
148 panic!("unexpected response: object not found and no specific error provided");
149 }
150 }
151 }
152}
153
154pub struct CheckerResultObject {
155 gas_coin: Option<GasCoin>,
156 object: Option<IotaObjectData>,
157}
158
159impl CheckerResultObject {
160 pub fn new(gas_coin: Option<GasCoin>, object: Option<IotaObjectData>) -> Self {
161 Self { gas_coin, object }
162 }
163 pub fn into_gas_coin(self) -> GasCoin {
164 self.gas_coin.unwrap()
165 }
166 pub fn into_object(self) -> IotaObjectData {
167 self.object.unwrap()
168 }
169}
170
171#[macro_export]
172macro_rules! assert_eq_if_present {
173 ($left:expr, $right:expr, $($arg:tt)+) => {
174 match (&$left, &$right) {
175 (Some(left_val), right_val) if !(&left_val == right_val) => {
176 panic!("{} does not match, left: {left_val:?}, right: {right_val:?}", $($arg)+);
177 }
178 _ => ()
179 }
180 };
181}
182
183#[derive(Default, Debug)]
184pub struct BalanceChangeChecker {
185 owner: Option<Owner>,
186 coin_type: Option<TypeTag>,
187 amount: Option<i128>,
188}
189
190impl BalanceChangeChecker {
191 pub fn new() -> Self {
192 Default::default()
193 }
194
195 pub fn owner(mut self, owner: Owner) -> Self {
196 self.owner = Some(owner);
197 self
198 }
199 pub fn coin_type(mut self, coin_type: &str) -> Self {
200 self.coin_type = Some(parse_iota_type_tag(coin_type).unwrap());
201 self
202 }
203
204 pub fn amount(mut self, amount: i128) -> Self {
205 self.amount = Some(amount);
206 self
207 }
208
209 pub fn check(self, event: &BalanceChange) {
210 let BalanceChange {
211 owner,
212 coin_type,
213 amount,
214 } = event;
215
216 assert_eq_if_present!(self.owner, owner, "owner");
217 assert_eq_if_present!(self.coin_type, coin_type, "coin_type");
218 assert_eq_if_present!(self.amount, amount, "version");
219 }
220}