1use anyhow::Result;
2use fuels::{
3 prelude::*,
4 tx::StorageSlot,
5 types::{
6 AssetId,
7 ContractId,
8 Identity,
9 },
10};
11
12use crate::blob_loader;
13
14abigen!(
15 Contract(
16 name = "SharedEventsV1",
17 abi = r#"{"programType":"contract","specVersion":"1.2","encodingVersion":"1","concreteTypes":[{"type":"()","concreteTypeId":"2e38e77b22c314a449e91fafed92a43826ac6aa403ae6a8acb6cf58239fbaf5d"},{"type":"b256","concreteTypeId":"7c5ee1cecf5f8eacd1284feb5f0bf2bdea533a51e2f0c9aabe9236d335989f3b"},{"type":"struct contract_schema::sidecar::SharedOrderMatchedEvent","concreteTypeId":"b67e97f64320e8ebb08209f9a5e4f55f8ff63d26fd34efedb1224ffd2cafbaa6","metadataTypeId":59},{"type":"u64","concreteTypeId":"1506e6f44c1d6291cdf46395a8e573276a4fa79e8ace3fc891e092ef32d1b0a0"}],"metadataTypes":[{"type":"enum contract_schema::sidecar::HouseExecutionRole","metadataTypeId":18,"components":[{"name":"Maker","typeId":"2e38e77b22c314a449e91fafed92a43826ac6aa403ae6a8acb6cf58239fbaf5d"},{"name":"Taker","typeId":"2e38e77b22c314a449e91fafed92a43826ac6aa403ae6a8acb6cf58239fbaf5d"}]},{"type":"struct contract_schema::sidecar::SharedOrderMatchedEvent","metadataTypeId":59,"components":[{"name":"source_order_id","typeId":"7c5ee1cecf5f8eacd1284feb5f0bf2bdea533a51e2f0c9aabe9236d335989f3b"},{"name":"turbo_order_id","typeId":"7c5ee1cecf5f8eacd1284feb5f0bf2bdea533a51e2f0c9aabe9236d335989f3b"},{"name":"house_execution_role","typeId":18},{"name":"quantity","typeId":"1506e6f44c1d6291cdf46395a8e573276a4fa79e8ace3fc891e092ef32d1b0a0"},{"name":"price","typeId":"1506e6f44c1d6291cdf46395a8e573276a4fa79e8ace3fc891e092ef32d1b0a0"},{"name":"timestamp","typeId":72}]},{"type":"struct std::time::Time","metadataTypeId":72,"components":[{"name":"unix","typeId":"1506e6f44c1d6291cdf46395a8e573276a4fa79e8ace3fc891e092ef32d1b0a0"}]}],"functions":[],"loggedTypes":[{"logId":"13150115045912406251","concreteTypeId":"b67e97f64320e8ebb08209f9a5e4f55f8ff63d26fd34efedb1224ffd2cafbaa6"}],"messagesTypes":[],"configurables":[],"errorCodes":{},"panickingCalls":{}}"#
18 ),
19 Contract(
20 name = "OrderBook",
21 abi = "artifacts/order-book/order-book-abi.json"
22 ),
23 Contract(
24 name = "HouseOrderBook",
25 abi = "artifacts/house-order-book/house-order-book-abi.json"
26 ),
27 Contract(
28 name = "OrderBookProxy",
29 abi = "artifacts/order-book-proxy/order-book-proxy-abi.json"
30 ),
31 Contract(
32 name = "OrderBookWhitelist",
33 abi = "artifacts/order-book-whitelist/order-book-whitelist-abi.json",
34 ),
35 Contract(
36 name = "OrderBookBlacklist",
37 abi = "artifacts/order-book-blacklist/order-book-blacklist-abi.json",
38 ),
39);
40pub const ORDER_BOOK_BYTECODE: &[u8] =
41 include_bytes!("../artifacts/order-book/order-book.bin");
42pub const ORDER_BOOK_STORAGE: &[u8] =
43 include_bytes!("../artifacts/order-book/order-book-storage_slots.json");
44pub const ORDER_BOOK_PROXY_BYTECODE: &[u8] =
45 include_bytes!("../artifacts/order-book-proxy/order-book-proxy.bin");
46pub const ORDER_BOOK_PROXY_STORAGE: &[u8] =
47 include_bytes!("../artifacts/order-book-proxy/order-book-proxy-storage_slots.json");
48pub const ORDER_BOOK_WHITELIST_BYTECODE: &[u8] =
49 include_bytes!("../artifacts/order-book-whitelist/order-book-whitelist.bin");
50pub const ORDER_BOOK_WHITELIST_STORAGE: &[u8] = include_bytes!(
51 "../artifacts/order-book-whitelist/order-book-whitelist-storage_slots.json"
52);
53pub const ORDER_BOOK_BLACKLIST_BYTECODE: &[u8] =
54 include_bytes!("../artifacts/order-book-blacklist/order-book-blacklist.bin");
55pub const ORDER_BOOK_BLACKLIST_STORAGE: &[u8] = include_bytes!(
56 "../artifacts/order-book-blacklist/order-book-blacklist-storage_slots.json"
57);
58
59#[derive(Clone)]
62pub struct OrderBookDeployConfig {
63 pub order_book_bytecode: Vec<u8>,
65 pub order_book_storage_slots: Vec<StorageSlot>,
67 pub order_book_configurables: OrderBookConfigurables,
69 pub order_book_proxy_bytecode: Vec<u8>,
71 pub order_book_proxy_storage_slots: Vec<StorageSlot>,
73 pub order_book_proxy_configurables: OrderBookProxyConfigurables,
75 pub order_book_whitelist_bytecode: Vec<u8>,
77 pub order_book_whitelist_storage_slots: Vec<StorageSlot>,
79 pub order_book_blacklist_bytecode: Vec<u8>,
81 pub order_book_blacklist_storage_slots: Vec<StorageSlot>,
83 pub max_words_per_blob: usize,
85 pub proxy_owner: Option<Identity>,
87 pub order_book_owner: Option<Identity>,
89 pub salt: Salt,
91}
92
93pub struct OrderBookBlob {
105 pub id: BlobId,
107 pub exists: bool,
109 pub blob: Blob,
112 pub data_blobs: Vec<Blob>,
116}
117
118impl Default for OrderBookDeployConfig {
119 fn default() -> Self {
120 Self {
121 order_book_bytecode: ORDER_BOOK_BYTECODE.to_vec(),
122 order_book_storage_slots: serde_json::from_slice(ORDER_BOOK_STORAGE).unwrap(),
123 order_book_whitelist_bytecode: ORDER_BOOK_WHITELIST_BYTECODE.to_vec(),
124 order_book_configurables: OrderBookConfigurables::default(),
125 order_book_proxy_bytecode: ORDER_BOOK_PROXY_BYTECODE.to_vec(),
126 order_book_proxy_storage_slots: serde_json::from_slice(
127 ORDER_BOOK_PROXY_STORAGE,
128 )
129 .unwrap(),
130 order_book_proxy_configurables: OrderBookProxyConfigurables::default(),
131 order_book_whitelist_storage_slots: serde_json::from_slice(
132 ORDER_BOOK_WHITELIST_STORAGE,
133 )
134 .unwrap(),
135 order_book_blacklist_bytecode: ORDER_BOOK_BLACKLIST_BYTECODE.to_vec(),
136 order_book_blacklist_storage_slots: serde_json::from_slice(
137 ORDER_BOOK_BLACKLIST_STORAGE,
138 )
139 .unwrap(),
140 max_words_per_blob: 10_000,
141 proxy_owner: None,
142 order_book_owner: None,
143 salt: Salt::default(),
144 }
145 }
146}
147
148impl OrderBookDeployConfig {
149 pub fn with_configurables(order_book_configurables: OrderBookConfigurables) -> Self {
150 Self {
151 order_book_configurables,
152 ..OrderBookDeployConfig::default()
153 }
154 }
155}
156
157#[derive(Clone)]
160pub struct OrderBookDeploy<W: Account + Clone> {
161 pub order_book_proxy: OrderBookProxy<W>,
163 pub order_book: OrderBook<W>,
165 pub contract_id: ContractId,
167 pub base_asset: AssetId,
169 pub quote_asset: AssetId,
171 pub whitelist: Option<OrderBookWhitelist<W>>,
173 pub whitelist_id: Option<ContractId>,
175}
176
177impl<W: Account + Clone> OrderBookDeploy<W> {
178 pub fn new(
179 w: W,
180 contract_id: ContractId,
181 base_asset: AssetId,
182 quote_asset: AssetId,
183 ) -> Self {
184 let order_book = OrderBook::new(contract_id, w.clone());
185 let order_book_proxy = OrderBookProxy::new(contract_id, w.clone());
186 Self {
187 order_book,
188 order_book_proxy,
189 contract_id,
190 base_asset,
191 quote_asset,
192 whitelist: None,
193 whitelist_id: None,
194 }
195 }
196
197 pub async fn initialize(&self) -> Result<()> {
198 self.order_book_proxy
200 .methods()
201 .initialize_proxy()
202 .call()
203 .await?;
204 self.order_book.methods().initialize().call().await?;
206 Ok(())
207 }
208
209 pub async fn deploy_order_book_blacklist(
210 deployer_wallet: &W,
211 owner_identity: &Identity,
212 config: &OrderBookDeployConfig,
213 ) -> Result<OrderBookBlacklist<W>> {
214 let contract = Contract::regular(
215 config.order_book_blacklist_bytecode.clone(),
216 config.salt,
217 config.order_book_blacklist_storage_slots.clone(),
218 )
219 .with_configurables(
220 OrderBookBlacklistConfigurables::default()
221 .with_INITIAL_OWNER(State::Initialized(*owner_identity))?,
222 )
223 .with_salt(config.salt);
224 let contract_id = contract.contract_id();
225 let instance = OrderBookBlacklist::new(contract_id, deployer_wallet.clone());
226 let contract_exists = deployer_wallet
227 .try_provider()?
228 .contract_exists(&contract_id)
229 .await?;
230 if !contract_exists {
231 contract
232 .deploy(deployer_wallet, TxPolicies::default())
233 .await?;
234 instance.methods().initialize().call().await?;
235 }
236 Ok(instance)
237 }
238
239 pub async fn deploy_order_book_whitelist(
240 deployer_wallet: &W,
241 owner_identity: &Identity,
242 config: &OrderBookDeployConfig,
243 ) -> Result<OrderBookWhitelist<W>> {
244 let contract = Contract::regular(
245 config.order_book_whitelist_bytecode.clone(),
246 config.salt,
247 config.order_book_whitelist_storage_slots.clone(),
248 )
249 .with_configurables(
250 OrderBookWhitelistConfigurables::default()
251 .with_INITIAL_OWNER(State::Initialized(*owner_identity))?,
252 )
253 .with_salt(config.salt);
254 let contract_id = contract.contract_id();
255 let instance = OrderBookWhitelist::new(contract_id, deployer_wallet.clone());
256 let contract_exists = deployer_wallet
257 .try_provider()?
258 .contract_exists(&contract_id)
259 .await?;
260
261 if !contract_exists {
262 contract
263 .deploy(deployer_wallet, TxPolicies::default())
264 .await?;
265 instance.methods().initialize().call().await?;
266 }
267
268 Ok(instance)
269 }
270
271 pub async fn order_book_blob(
272 deployer_wallet: &W,
273 base_asset: AssetId,
274 quote_asset: AssetId,
275 config: &OrderBookDeployConfig,
276 ) -> Result<OrderBookBlob> {
277 let order_book_owner = config
278 .order_book_owner
279 .unwrap_or(Identity::Address(deployer_wallet.address()));
280 let configurables = config
282 .order_book_configurables
283 .clone()
284 .with_BASE_ASSET(base_asset)?
285 .with_QUOTE_ASSET(quote_asset)?
286 .with_INITIAL_OWNER(State::Initialized(order_book_owner))?;
287 let (data_blobs, loader_blob) = blob_loader::build_loader_blobs(
290 config.order_book_bytecode.clone(),
291 config.salt,
292 config.order_book_storage_slots.clone(),
293 configurables.clone(),
294 config.max_words_per_blob,
295 )?;
296 let loader_blob_id = loader_blob.id();
297 let loader_blob_exists = deployer_wallet
298 .try_provider()?
299 .blob_exists(loader_blob_id)
300 .await?;
301
302 Ok(OrderBookBlob {
303 id: loader_blob_id,
304 exists: loader_blob_exists,
305 blob: loader_blob,
306 data_blobs,
307 })
308 }
309
310 pub async fn deploy_order_book_blob(
321 deployer_wallet: &W,
322 base_asset: AssetId,
323 quote_asset: AssetId,
324 config: &OrderBookDeployConfig,
325 ) -> Result<BlobId> {
326 let order_book_blob =
327 Self::order_book_blob(deployer_wallet, base_asset, quote_asset, config)
328 .await?;
329 blob_loader::upload_loader_blobs(
330 deployer_wallet,
331 order_book_blob.data_blobs,
332 order_book_blob.blob,
333 )
334 .await
335 }
336
337 pub async fn deploy_order_book_proxy(
338 deployer_wallet: &W,
339 order_book_blob_id: &BlobId,
340 config: &OrderBookDeployConfig,
341 ) -> Result<(OrderBookProxy<W>, bool)> {
342 let proxy_owner = config
343 .proxy_owner
344 .unwrap_or(Identity::Address(deployer_wallet.address()));
345 let blob_id = ContractId::new(*order_book_blob_id);
346 let configurables = config
347 .order_book_proxy_configurables
348 .clone()
349 .with_INITIAL_TARGET(blob_id)?
350 .with_INITIAL_OWNER(State::Initialized(proxy_owner))?;
351 let contract = Contract::regular(
352 config.order_book_proxy_bytecode.clone(),
353 config.salt,
354 config.order_book_proxy_storage_slots.clone(),
355 )
356 .with_configurables(configurables);
357
358 let contract_id = contract.contract_id();
359 let already_deployed = deployer_wallet
360 .try_provider()?
361 .contract_exists(&contract_id)
362 .await?;
363 let order_book_proxy = OrderBookProxy::new(contract_id, deployer_wallet.clone());
364
365 if !already_deployed {
366 contract
367 .deploy(deployer_wallet, TxPolicies::default())
368 .await?;
369 }
370 let requires_initialization = !already_deployed;
371
372 Ok((order_book_proxy, requires_initialization))
373 }
374
375 pub async fn deploy(
392 deployer_wallet: &W,
393 base_asset: AssetId,
394 quote_asset: AssetId,
395 config: &OrderBookDeployConfig,
396 ) -> Result<OrderBookDeploy<W>> {
397 let (deploy, requires_initialization) = Self::deploy_without_initialization(
399 deployer_wallet,
400 base_asset,
401 quote_asset,
402 config,
403 )
404 .await?;
405
406 if requires_initialization {
407 deploy.initialize().await?;
409 }
410
411 Ok(deploy)
412 }
413
414 pub async fn deploy_without_initialization(
416 deployer_wallet: &W,
417 base_asset: AssetId,
418 quote_asset: AssetId,
419 config: &OrderBookDeployConfig,
420 ) -> Result<(OrderBookDeploy<W>, bool)> {
421 let order_book_blob_id = Self::deploy_order_book_blob(
422 deployer_wallet,
423 base_asset,
424 quote_asset,
425 config,
426 )
427 .await?;
428 let (deploy_order_book_proxy, requires_initialization) =
429 Self::deploy_order_book_proxy(deployer_wallet, &order_book_blob_id, config)
430 .await?;
431 let contract_id = deploy_order_book_proxy.contract_id();
432 let deploy = OrderBookDeploy::new(
433 deployer_wallet.clone(),
434 contract_id,
435 base_asset,
436 quote_asset,
437 );
438
439 Ok((deploy, requires_initialization))
440 }
441}
442
443#[cfg(test)]
444mod tests {
445 use super::*;
446 use fuels::test_helpers::{
447 WalletsConfig,
448 launch_custom_provider_and_get_wallets,
449 };
450
451 #[tokio::test]
452 async fn test_deploy_order_book_blob() {
453 let mut wallets = launch_custom_provider_and_get_wallets(
455 WalletsConfig::new(Some(2), Some(1), Some(1_000_000_000)),
456 None,
457 Some(::fuels::test_helpers::ChainConfig::local_testnet()),
458 )
459 .await
460 .unwrap();
461 let deployer_wallet = wallets.pop().unwrap();
462 let contract_owner = wallets.pop().unwrap();
463
464 let base_asset = AssetId::from([1u8; 32]);
466 let quote_asset = AssetId::from([2u8; 32]);
467 let config = OrderBookDeployConfig {
468 proxy_owner: Some(Identity::Address(contract_owner.address())),
469 order_book_owner: Some(Identity::Address(contract_owner.address())),
470 ..Default::default()
471 };
472
473 let order_book_blob_id = OrderBookDeploy::deploy_order_book_blob(
474 &deployer_wallet,
475 base_asset,
476 quote_asset,
477 &config,
478 )
479 .await
480 .unwrap();
481 let (order_book_deploy, requires_initialization) =
482 OrderBookDeploy::deploy_without_initialization(
483 &deployer_wallet,
484 base_asset,
485 quote_asset,
486 &config,
487 )
488 .await
489 .unwrap();
490 let order_book = order_book_deploy.order_book;
491 let order_book_proxy = order_book_deploy.order_book_proxy;
492
493 if requires_initialization {
494 order_book_proxy
495 .methods()
496 .initialize_proxy()
497 .call()
498 .await
499 .unwrap();
500 order_book.methods().initialize().call().await.unwrap();
501 }
502
503 assert_eq!(
504 order_book_proxy
505 .methods()
506 .proxy_owner()
507 .simulate(Execution::state_read_only())
508 .await
509 .unwrap()
510 .value,
511 State::Initialized(Identity::Address(contract_owner.address()))
512 );
513 assert_eq!(
514 order_book_proxy
515 .methods()
516 .proxy_target()
517 .simulate(Execution::state_read_only())
518 .await
519 .unwrap()
520 .value,
521 Some(ContractId::new(order_book_blob_id))
522 );
523 assert_eq!(
524 order_book
525 .methods()
526 .owner()
527 .simulate(Execution::state_read_only())
528 .await
529 .unwrap()
530 .value,
531 State::Initialized(Identity::Address(contract_owner.address()))
532 );
533 assert_eq!(
534 order_book
535 .methods()
536 .get_base_asset()
537 .simulate(Execution::state_read_only())
538 .await
539 .unwrap()
540 .value,
541 base_asset,
542 );
543 assert_eq!(
544 order_book
545 .methods()
546 .get_quote_asset()
547 .simulate(Execution::state_read_only())
548 .await
549 .unwrap()
550 .value,
551 quote_asset,
552 );
553
554 let new_base_asset = AssetId::from([3u8; 32]);
556 let order_book_blob_id = OrderBookDeploy::deploy_order_book_blob(
557 &deployer_wallet,
558 new_base_asset,
559 quote_asset,
560 &config,
561 )
562 .await
563 .unwrap();
564
565 order_book_proxy
566 .with_account(contract_owner)
567 .methods()
568 .set_proxy_target(ContractId::new(order_book_blob_id))
569 .call()
570 .await
571 .unwrap();
572 assert_eq!(
573 order_book
574 .methods()
575 .get_base_asset()
576 .simulate(Execution::state_read_only())
577 .await
578 .unwrap()
579 .value,
580 new_base_asset
581 );
582 }
583
584 #[tokio::test]
585 async fn test_order_book_deployment() {
586 let mut wallets = launch_custom_provider_and_get_wallets(
588 WalletsConfig::new(Some(2), Some(1), Some(1_000_000_000)),
589 None,
590 Some(::fuels::test_helpers::ChainConfig::local_testnet()),
591 )
592 .await
593 .unwrap();
594 let deployer_wallet = wallets.pop().unwrap();
595
596 let base_asset = AssetId::new([1u8; 32]);
598 let quote_asset = AssetId::new([2u8; 32]);
599 let config = OrderBookDeployConfig::default();
600
601 let deployment =
602 OrderBookDeploy::deploy(&deployer_wallet, base_asset, quote_asset, &config)
603 .await
604 .unwrap();
605
606 let provider = deployer_wallet.try_provider().unwrap();
608 let contract_exists = provider
609 .contract_exists(&deployment.contract_id)
610 .await
611 .unwrap();
612 assert!(contract_exists, "OrderBook contract should exist");
613
614 assert_eq!(deployment.base_asset, base_asset);
616 assert_eq!(deployment.quote_asset, quote_asset);
617 }
618}