1use enum_dispatch::enum_dispatch;
6use move_core_types::{ident_str, identifier::IdentStr};
7use num_enum::TryFromPrimitive;
8use schemars::JsonSchema;
9use serde::{Deserialize, Serialize};
10use serde_with::serde_as;
11
12use crate::{
13 IOTA_BRIDGE_OBJECT_ID,
14 base_types::{IotaAddress, ObjectID, SequenceNumber},
15 collection_types::{Bag, LinkedTable, LinkedTableNode, VecMap},
16 dynamic_field::{Field, get_dynamic_field_from_store},
17 error::{IotaError, IotaResult},
18 id::UID,
19 iota_serde::{BigInt, Readable},
20 object::Owner,
21 storage::ObjectStore,
22 versioned::Versioned,
23};
24
25pub type BridgeInnerDynamicField = Field<u64, BridgeInnerV1>;
26pub type BridgeRecordDynamicField = Field<
27 MoveTypeBridgeMessageKey,
28 LinkedTableNode<MoveTypeBridgeMessageKey, MoveTypeBridgeRecord>,
29>;
30
31pub const BRIDGE_MODULE_NAME: &IdentStr = ident_str!("bridge");
32pub const BRIDGE_TREASURY_MODULE_NAME: &IdentStr = ident_str!("treasury");
33pub const BRIDGE_LIMITER_MODULE_NAME: &IdentStr = ident_str!("limiter");
34pub const BRIDGE_COMMITTEE_MODULE_NAME: &IdentStr = ident_str!("committee");
35pub const BRIDGE_MESSAGE_MODULE_NAME: &IdentStr = ident_str!("message");
36pub const BRIDGE_CREATE_FUNCTION_NAME: &IdentStr = ident_str!("create");
37pub const BRIDGE_INIT_COMMITTEE_FUNCTION_NAME: &IdentStr = ident_str!("init_bridge_committee");
38pub const BRIDGE_REGISTER_FOREIGN_TOKEN_FUNCTION_NAME: &IdentStr =
39 ident_str!("register_foreign_token");
40pub const BRIDGE_CREATE_ADD_TOKEN_ON_IOTA_MESSAGE_FUNCTION_NAME: &IdentStr =
41 ident_str!("create_add_tokens_on_iota_message");
42pub const BRIDGE_EXECUTE_SYSTEM_MESSAGE_FUNCTION_NAME: &IdentStr =
43 ident_str!("execute_system_message");
44
45pub const BRIDGE_SUPPORTED_ASSET: &[&str] = &["btc", "eth", "usdc", "usdt"];
46
47pub const BRIDGE_COMMITTEE_MINIMAL_VOTING_POWER: u64 = 7500; pub const BRIDGE_COMMITTEE_MAXIMAL_VOTING_POWER: u64 = 10000; pub const APPROVAL_THRESHOLD_TOKEN_TRANSFER: u64 = 3334;
52pub const APPROVAL_THRESHOLD_EMERGENCY_PAUSE: u64 = 450;
53pub const APPROVAL_THRESHOLD_EMERGENCY_UNPAUSE: u64 = 5001;
54pub const APPROVAL_THRESHOLD_COMMITTEE_BLOCKLIST: u64 = 5001;
55pub const APPROVAL_THRESHOLD_LIMIT_UPDATE: u64 = 5001;
56pub const APPROVAL_THRESHOLD_ASSET_PRICE_UPDATE: u64 = 5001;
57pub const APPROVAL_THRESHOLD_EVM_CONTRACT_UPGRADE: u64 = 5001;
58pub const APPROVAL_THRESHOLD_ADD_TOKENS_ON_IOTA: u64 = 5001;
59pub const APPROVAL_THRESHOLD_ADD_TOKENS_ON_EVM: u64 = 5001;
60
61pub const TOKEN_ID_IOTA: u8 = 0;
63pub const TOKEN_ID_BTC: u8 = 1;
64pub const TOKEN_ID_ETH: u8 = 2;
65pub const TOKEN_ID_USDC: u8 = 3;
66pub const TOKEN_ID_USDT: u8 = 4;
67
68#[derive(
69 Debug, Serialize, Deserialize, PartialEq, Eq, Clone, Copy, TryFromPrimitive, JsonSchema, Hash,
70)]
71#[repr(u8)]
72pub enum BridgeChainId {
73 IotaMainnet = 0,
74 IotaTestnet = 1,
75 IotaCustom = 2,
76
77 EthMainnet = 10,
78 EthSepolia = 11,
79 EthCustom = 12,
80}
81
82impl BridgeChainId {
83 pub fn is_iota_chain(&self) -> bool {
84 matches!(
85 self,
86 BridgeChainId::IotaMainnet | BridgeChainId::IotaTestnet | BridgeChainId::IotaCustom
87 )
88 }
89}
90
91pub fn get_bridge_obj_initial_shared_version(
92 object_store: &dyn ObjectStore,
93) -> IotaResult<Option<SequenceNumber>> {
94 Ok(object_store
95 .get_object(&IOTA_BRIDGE_OBJECT_ID)?
96 .map(|obj| match obj.owner {
97 Owner::Shared {
98 initial_shared_version,
99 } => initial_shared_version,
100 _ => unreachable!("Bridge object must be shared"),
101 }))
102}
103
104#[derive(Debug, Serialize, Deserialize, Clone)]
109#[enum_dispatch(BridgeTrait)]
110pub enum Bridge {
111 V1(BridgeInnerV1),
112}
113
114#[derive(Debug, Serialize, Deserialize, Clone)]
121pub struct BridgeWrapper {
122 pub id: UID,
123 pub version: Versioned,
124}
125
126#[enum_dispatch]
128pub trait BridgeTrait {
129 fn bridge_version(&self) -> u64;
130 fn message_version(&self) -> u8;
131 fn chain_id(&self) -> u8;
132 fn sequence_nums(&self) -> &VecMap<u8, u64>;
133 fn committee(&self) -> &MoveTypeBridgeCommittee;
134 fn treasury(&self) -> &MoveTypeBridgeTreasury;
135 fn bridge_records(&self) -> &LinkedTable<MoveTypeBridgeMessageKey>;
136 fn frozen(&self) -> bool;
137 fn try_into_bridge_summary(self) -> IotaResult<BridgeSummary>;
138}
139
140#[serde_as]
141#[derive(Debug, Serialize, Deserialize, Clone, JsonSchema)]
142#[serde(rename_all = "camelCase")]
143pub struct BridgeSummary {
144 #[schemars(with = "BigInt<u64>")]
145 #[serde_as(as = "Readable<BigInt<u64>, _>")]
146 pub bridge_version: u64,
147 pub message_version: u8,
149 pub chain_id: u8,
151 #[schemars(with = "Vec<(u8, BigInt<u64>)>")]
153 #[serde_as(as = "Vec<(_, Readable<BigInt<u64>, _>)>")]
154 pub sequence_nums: Vec<(u8, u64)>,
155 pub committee: BridgeCommitteeSummary,
156 pub treasury: BridgeTreasurySummary,
158 pub bridge_records_id: ObjectID,
160 pub limiter: BridgeLimiterSummary,
162 pub is_frozen: bool,
164 }
166
167impl Default for BridgeSummary {
168 fn default() -> Self {
169 Self {
170 bridge_version: 1,
171 message_version: 1,
172 chain_id: 1,
173 sequence_nums: vec![],
174 committee: BridgeCommitteeSummary::default(),
175 treasury: BridgeTreasurySummary::default(),
176 bridge_records_id: ObjectID::random(),
177 limiter: BridgeLimiterSummary::default(),
178 is_frozen: false,
179 }
180 }
181}
182
183pub fn get_bridge_wrapper(object_store: &dyn ObjectStore) -> Result<BridgeWrapper, IotaError> {
184 let wrapper = object_store
185 .get_object(&IOTA_BRIDGE_OBJECT_ID)?
186 .ok_or_else(|| IotaError::IotaBridgeRead("BridgeWrapper object not found".to_owned()))?;
188 let move_object = wrapper.data.try_as_move().ok_or_else(|| {
189 IotaError::IotaBridgeRead("BridgeWrapper object must be a Move object".to_owned())
190 })?;
191 let result = bcs::from_bytes::<BridgeWrapper>(move_object.contents())
192 .map_err(|err| IotaError::IotaBridgeRead(err.to_string()))?;
193 Ok(result)
194}
195
196pub fn get_bridge(object_store: &dyn ObjectStore) -> Result<Bridge, IotaError> {
197 let wrapper = get_bridge_wrapper(object_store)?;
198 let id = wrapper.version.id.id.bytes;
199 let version = wrapper.version.version;
200 match version {
201 1 => {
202 let result: BridgeInnerV1 = get_dynamic_field_from_store(object_store, id, &version)
203 .map_err(|err| {
204 IotaError::IotaBridgeRead(format!(
205 "Failed to load bridge inner object with ID {:?} and version {:?}: {:?}",
206 id, version, err
207 ))
208 })?;
209 Ok(Bridge::V1(result))
210 }
211 _ => Err(IotaError::IotaBridgeRead(format!(
212 "Unsupported IotaBridge version: {}",
213 version
214 ))),
215 }
216}
217
218#[derive(Debug, Serialize, Deserialize, Clone)]
220pub struct BridgeInnerV1 {
221 pub bridge_version: u64,
222 pub message_version: u8,
223 pub chain_id: u8,
224 pub sequence_nums: VecMap<u8, u64>,
225 pub committee: MoveTypeBridgeCommittee,
226 pub treasury: MoveTypeBridgeTreasury,
227 pub bridge_records: LinkedTable<MoveTypeBridgeMessageKey>,
228 pub limiter: MoveTypeBridgeTransferLimiter,
229 pub frozen: bool,
230}
231
232impl BridgeTrait for BridgeInnerV1 {
233 fn bridge_version(&self) -> u64 {
234 self.bridge_version
235 }
236
237 fn message_version(&self) -> u8 {
238 self.message_version
239 }
240
241 fn chain_id(&self) -> u8 {
242 self.chain_id
243 }
244
245 fn sequence_nums(&self) -> &VecMap<u8, u64> {
246 &self.sequence_nums
247 }
248
249 fn committee(&self) -> &MoveTypeBridgeCommittee {
250 &self.committee
251 }
252
253 fn treasury(&self) -> &MoveTypeBridgeTreasury {
254 &self.treasury
255 }
256
257 fn bridge_records(&self) -> &LinkedTable<MoveTypeBridgeMessageKey> {
258 &self.bridge_records
259 }
260
261 fn frozen(&self) -> bool {
262 self.frozen
263 }
264
265 fn try_into_bridge_summary(self) -> IotaResult<BridgeSummary> {
266 let transfer_limit = self
267 .limiter
268 .transfer_limit
269 .contents
270 .into_iter()
271 .map(|e| {
272 let source = BridgeChainId::try_from(e.key.source).map_err(|_e| {
273 IotaError::GenericBridge {
274 error: format!("Unrecognized chain id: {}", e.key.source),
275 }
276 })?;
277 let destination = BridgeChainId::try_from(e.key.destination).map_err(|_e| {
278 IotaError::GenericBridge {
279 error: format!("Unrecognized chain id: {}", e.key.destination),
280 }
281 })?;
282 Ok((source, destination, e.value))
283 })
284 .collect::<IotaResult<Vec<_>>>()?;
285 let supported_tokens = self
286 .treasury
287 .supported_tokens
288 .contents
289 .into_iter()
290 .map(|e| (e.key, e.value))
291 .collect::<Vec<_>>();
292 let id_token_type_map = self
293 .treasury
294 .id_token_type_map
295 .contents
296 .into_iter()
297 .map(|e| (e.key, e.value))
298 .collect::<Vec<_>>();
299 let transfer_records = self
300 .limiter
301 .transfer_records
302 .contents
303 .into_iter()
304 .map(|e| {
305 let source = BridgeChainId::try_from(e.key.source).map_err(|_e| {
306 IotaError::GenericBridge {
307 error: format!("Unrecognized chain id: {}", e.key.source),
308 }
309 })?;
310 let destination = BridgeChainId::try_from(e.key.destination).map_err(|_e| {
311 IotaError::GenericBridge {
312 error: format!("Unrecognized chain id: {}", e.key.destination),
313 }
314 })?;
315 Ok((source, destination, e.value))
316 })
317 .collect::<IotaResult<Vec<_>>>()?;
318 let limiter = BridgeLimiterSummary {
319 transfer_limit,
320 transfer_records,
321 };
322 Ok(BridgeSummary {
323 bridge_version: self.bridge_version,
324 message_version: self.message_version,
325 chain_id: self.chain_id,
326 sequence_nums: self
327 .sequence_nums
328 .contents
329 .into_iter()
330 .map(|e| (e.key, e.value))
331 .collect(),
332 committee: BridgeCommitteeSummary {
333 members: self
334 .committee
335 .members
336 .contents
337 .into_iter()
338 .map(|e| (e.key, e.value))
339 .collect(),
340 member_registration: self
341 .committee
342 .member_registrations
343 .contents
344 .into_iter()
345 .map(|e| (e.key, e.value))
346 .collect(),
347 last_committee_update_epoch: self.committee.last_committee_update_epoch,
348 },
349 bridge_records_id: self.bridge_records.id,
350 limiter,
351 treasury: BridgeTreasurySummary {
352 supported_tokens,
353 id_token_type_map,
354 },
355 is_frozen: self.frozen,
356 })
357 }
358}
359
360#[derive(Debug, Serialize, Deserialize, Clone)]
362pub struct MoveTypeBridgeTreasury {
363 pub treasuries: Bag,
364 pub supported_tokens: VecMap<String, BridgeTokenMetadata>,
365 pub id_token_type_map: VecMap<u8, String>,
367 pub waiting_room: Bag,
369}
370
371#[derive(Debug, Serialize, Deserialize, Clone, JsonSchema, Default, PartialEq, Eq)]
372#[serde(rename_all = "camelCase")]
373pub struct BridgeTokenMetadata {
374 pub id: u8,
375 pub decimal_multiplier: u64,
376 pub notional_value: u64,
377 pub native_token: bool,
378}
379
380#[derive(Debug, Serialize, Deserialize, Clone)]
382pub struct MoveTypeBridgeCommittee {
383 pub members: VecMap<Vec<u8>, MoveTypeCommitteeMember>,
384 pub member_registrations: VecMap<IotaAddress, MoveTypeCommitteeMemberRegistration>,
385 pub last_committee_update_epoch: u64,
386}
387
388#[serde_as]
390#[derive(Debug, Serialize, Deserialize, Clone, JsonSchema, Default, PartialEq, Eq)]
391#[serde(rename_all = "camelCase")]
392pub struct MoveTypeCommitteeMemberRegistration {
393 pub iota_address: IotaAddress,
394 pub bridge_pubkey_bytes: Vec<u8>,
395 pub http_rest_url: Vec<u8>,
396}
397
398#[serde_as]
399#[derive(Debug, Serialize, Deserialize, Clone, JsonSchema, Default)]
400#[serde(rename_all = "camelCase")]
401pub struct BridgeCommitteeSummary {
402 pub members: Vec<(Vec<u8>, MoveTypeCommitteeMember)>,
403 pub member_registration: Vec<(IotaAddress, MoveTypeCommitteeMemberRegistration)>,
404 pub last_committee_update_epoch: u64,
405}
406
407#[serde_as]
408#[derive(Debug, Serialize, Deserialize, Clone, JsonSchema, Default)]
409#[serde(rename_all = "camelCase")]
410pub struct BridgeLimiterSummary {
411 pub transfer_limit: Vec<(BridgeChainId, BridgeChainId, u64)>,
412 pub transfer_records: Vec<(BridgeChainId, BridgeChainId, MoveTypeBridgeTransferRecord)>,
413}
414
415#[serde_as]
416#[derive(Debug, Serialize, Deserialize, Clone, JsonSchema, Default)]
417#[serde(rename_all = "camelCase")]
418pub struct BridgeTreasurySummary {
419 pub supported_tokens: Vec<(String, BridgeTokenMetadata)>,
420 pub id_token_type_map: Vec<(u8, String)>,
421}
422
423#[serde_as]
425#[derive(Debug, Serialize, Deserialize, Clone, JsonSchema, Default, Eq, PartialEq)]
426#[serde(rename_all = "camelCase")]
427pub struct MoveTypeCommitteeMember {
428 pub iota_address: IotaAddress,
429 pub bridge_pubkey_bytes: Vec<u8>,
430 pub voting_power: u64,
431 pub http_rest_url: Vec<u8>,
432 pub blocklisted: bool,
433}
434
435#[derive(Debug, Serialize, Deserialize, Clone, Eq, PartialEq)]
437pub struct MoveTypeBridgeMessageKey {
438 pub source_chain: u8,
439 pub message_type: u8,
440 pub bridge_seq_num: u64,
441}
442
443#[derive(Debug, Serialize, Deserialize, Clone)]
445pub struct MoveTypeBridgeTransferLimiter {
446 pub transfer_limit: VecMap<MoveTypeBridgeRoute, u64>,
447 pub transfer_records: VecMap<MoveTypeBridgeRoute, MoveTypeBridgeTransferRecord>,
448}
449
450#[derive(Debug, Serialize, Deserialize, Clone)]
452pub struct MoveTypeBridgeRoute {
453 pub source: u8,
454 pub destination: u8,
455}
456
457#[derive(Debug, Serialize, Deserialize, Clone, JsonSchema)]
459pub struct MoveTypeBridgeTransferRecord {
460 hour_head: u64,
461 hour_tail: u64,
462 per_hour_amounts: Vec<u64>,
463 total_amount: u64,
464}
465
466#[derive(Debug, Serialize, Deserialize)]
468pub struct MoveTypeBridgeMessage {
469 pub message_type: u8,
470 pub message_version: u8,
471 pub seq_num: u64,
472 pub source_chain: u8,
473 pub payload: Vec<u8>,
474}
475
476#[derive(Debug, Serialize, Deserialize)]
478pub struct MoveTypeBridgeRecord {
479 pub message: MoveTypeBridgeMessage,
480 pub verified_signatures: Option<Vec<Vec<u8>>>,
481 pub claimed: bool,
482}
483
484pub fn is_bridge_committee_initiated(object_store: &dyn ObjectStore) -> IotaResult<bool> {
485 match get_bridge(object_store) {
486 Ok(bridge) => Ok(!bridge.committee().members.contents.is_empty()),
487 Err(IotaError::IotaBridgeRead(..)) => Ok(false),
488 Err(other) => Err(other),
489 }
490}
491
492#[derive(Debug, Serialize, Deserialize, Clone, Eq, PartialEq)]
494pub struct MoveTypeTokenTransferPayload {
495 pub sender_address: Vec<u8>,
496 pub target_chain: u8,
497 pub target_address: Vec<u8>,
498 pub token_type: u8,
499 pub amount: u64,
500}
501
502#[derive(Debug, Serialize, Deserialize, Clone, Eq, PartialEq)]
504pub struct MoveTypeParsedTokenTransferMessage {
505 pub message_version: u8,
506 pub seq_num: u64,
507 pub source_chain: u8,
508 pub payload: Vec<u8>,
509 pub parsed_payload: MoveTypeTokenTransferPayload,
510}