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