@@ -18,18 +18,22 @@ use forc_pkg::{manifest::GenericManifestFile, MemberFilter};
18
18
use forc_tracing:: { println_action_green, println_warning} ;
19
19
use forc_util:: default_output_directory;
20
20
use forc_wallet:: utils:: default_wallet_path;
21
+ use fuel_abi_types:: abi:: program:: Configurable ;
21
22
use fuel_core_client:: client:: types:: { ChainInfo , TransactionStatus } ;
22
23
use fuel_core_client:: client:: FuelClient ;
23
24
use fuel_crypto:: fuel_types:: ChainId ;
24
25
use fuel_tx:: { Salt , Transaction } ;
25
- use fuel_vm:: prelude:: * ;
26
+ use fuel_vm:: { consts :: WORD_SIZE , fuel_asm :: op , prelude:: * } ;
26
27
use fuels:: {
27
28
macros:: abigen,
28
29
programs:: {
29
30
contract:: { LoadConfiguration , StorageConfiguration } ,
30
31
executable:: Executable ,
31
32
} ,
32
- types:: { bech32:: Bech32ContractId , transaction_builders:: Blob } ,
33
+ types:: {
34
+ bech32:: Bech32ContractId ,
35
+ transaction_builders:: { Blob , BlobId } ,
36
+ } ,
33
37
} ;
34
38
use fuels_accounts:: { provider:: Provider , Account , ViewOnlyAccount } ;
35
39
use fuels_core:: types:: { transaction:: TxPolicies , transaction_builders:: CreateTransactionBuilder } ;
@@ -43,8 +47,8 @@ use std::{
43
47
sync:: Arc ,
44
48
time:: Duration ,
45
49
} ;
46
- use sway_core:: language:: parsed:: TreeType ;
47
50
use sway_core:: BuildTarget ;
51
+ use sway_core:: { asm_generation:: ProgramABI , language:: parsed:: TreeType } ;
48
52
49
53
/// Default maximum contract size allowed for a single contract. If the target
50
54
/// contract size is bigger than this amount, forc-deploy will automatically
@@ -356,6 +360,96 @@ pub async fn deploy(command: cmd::Deploy) -> Result<Vec<DeployedPackage>> {
356
360
Ok ( deployed_packages)
357
361
}
358
362
363
+ fn loader_data_offset ( binary : & [ u8 ] , blob_id : & BlobId ) -> Result < Option < usize > > {
364
+ // The final code is going to have this structure (if the data section is non-empty):
365
+ // 1. loader instructions
366
+ // 2. blob id
367
+ // 3. length_of_data_section
368
+ // 4. the data_section (updated with configurables as needed)
369
+ const BLOB_ID_SIZE : u16 = 32 ;
370
+ const REG_ADDRESS_OF_DATA_AFTER_CODE : u8 = 0x10 ;
371
+ const REG_START_OF_LOADED_CODE : u8 = 0x11 ;
372
+ const REG_GENERAL_USE : u8 = 0x12 ;
373
+ let get_instructions = |num_of_instructions| {
374
+ // There are 3 main steps:
375
+ // 1. Load the blob content into memory
376
+ // 2. Load the data section right after the blob
377
+ // 3. Jump to the beginning of the memory where the blob was loaded
378
+ [
379
+ // 1. Load the blob content into memory
380
+ // Find the start of the hardcoded blob ID, which is located after the loader code ends.
381
+ op:: move_ ( REG_ADDRESS_OF_DATA_AFTER_CODE , RegId :: PC ) ,
382
+ // hold the address of the blob ID.
383
+ op:: addi (
384
+ REG_ADDRESS_OF_DATA_AFTER_CODE ,
385
+ REG_ADDRESS_OF_DATA_AFTER_CODE ,
386
+ num_of_instructions * Instruction :: SIZE as u16 ,
387
+ ) ,
388
+ // The code is going to be loaded from the current value of SP onwards, save
389
+ // the location into REG_START_OF_LOADED_CODE so we can jump into it at the end.
390
+ op:: move_ ( REG_START_OF_LOADED_CODE , RegId :: SP ) ,
391
+ // REG_GENERAL_USE to hold the size of the blob.
392
+ op:: bsiz ( REG_GENERAL_USE , REG_ADDRESS_OF_DATA_AFTER_CODE ) ,
393
+ // Push the blob contents onto the stack.
394
+ op:: ldc ( REG_ADDRESS_OF_DATA_AFTER_CODE , 0 , REG_GENERAL_USE , 1 ) ,
395
+ // Move on to the data section length
396
+ op:: addi (
397
+ REG_ADDRESS_OF_DATA_AFTER_CODE ,
398
+ REG_ADDRESS_OF_DATA_AFTER_CODE ,
399
+ BLOB_ID_SIZE ,
400
+ ) ,
401
+ // load the size of the data section into REG_GENERAL_USE
402
+ op:: lw ( REG_GENERAL_USE , REG_ADDRESS_OF_DATA_AFTER_CODE , 0 ) ,
403
+ // after we have read the length of the data section, we move the pointer to the actual
404
+ // data by skipping WORD_SIZE B.
405
+ op:: addi (
406
+ REG_ADDRESS_OF_DATA_AFTER_CODE ,
407
+ REG_ADDRESS_OF_DATA_AFTER_CODE ,
408
+ WORD_SIZE as u16 ,
409
+ ) ,
410
+ // load the data section of the executable
411
+ op:: ldc ( REG_ADDRESS_OF_DATA_AFTER_CODE , 0 , REG_GENERAL_USE , 2 ) ,
412
+ // Jump into the memory where the contract is loaded.
413
+ // What follows is called _jmp_mem by the sway compiler.
414
+ // Subtract the address contained in IS because jmp will add it back.
415
+ op:: sub (
416
+ REG_START_OF_LOADED_CODE ,
417
+ REG_START_OF_LOADED_CODE ,
418
+ RegId :: IS ,
419
+ ) ,
420
+ // jmp will multiply by 4, so we need to divide to cancel that out.
421
+ op:: divi ( REG_START_OF_LOADED_CODE , REG_START_OF_LOADED_CODE , 4 ) ,
422
+ // Jump to the start of the contract we loaded.
423
+ op:: jmp ( REG_START_OF_LOADED_CODE ) ,
424
+ ]
425
+ } ;
426
+
427
+ let offset = extract_data_offset ( & binary) ?;
428
+
429
+ if binary. len ( ) < offset {
430
+ anyhow:: bail!( "data sectio offset is out of bounds" ) ;
431
+ }
432
+
433
+ let data_section = binary[ offset..] . to_vec ( ) ;
434
+
435
+ if !data_section. is_empty ( ) {
436
+ let num_of_instructions = u16:: try_from ( get_instructions ( 0 ) . len ( ) )
437
+ . expect ( "to never have more than u16::MAX instructions" ) ;
438
+
439
+ let instruction_bytes = get_instructions ( num_of_instructions)
440
+ . into_iter ( )
441
+ . flat_map ( |instruction| instruction. to_bytes ( ) ) ;
442
+
443
+ let blob_bytes = blob_id. iter ( ) . copied ( ) ;
444
+
445
+ let loader_offset = instruction_bytes. count ( ) + blob_bytes. count ( ) ;
446
+
447
+ Ok ( Some ( loader_offset) )
448
+ } else {
449
+ Ok ( None )
450
+ }
451
+ }
452
+
359
453
/// Builds and deploys executable (script and predicate) package(s) as blobs,
360
454
/// and generates a loader for each of them.
361
455
pub async fn deploy_executables (
@@ -387,6 +481,29 @@ pub async fn deploy_executables(
387
481
"Saved" ,
388
482
& format ! ( "loader bytecode at {}" , bin_path. display( ) ) ,
389
483
) ;
484
+ if let Some ( loader_data_section_offset) =
485
+ loader_data_offset ( & pkg. bytecode . bytes , & BlobId :: default ( ) ) ?
486
+ {
487
+ if let ProgramABI :: Fuel ( mut fuel_abi) = pkg. program_abi . clone ( ) {
488
+ println_action_green ( "Generating" , "loader abi for the uploaded executable." ) ;
489
+ let json_abi_path = out_dir. join ( format ! ( "{pkg_name}-loader-abi.json" ) ) ;
490
+ let original_data_section = extract_data_offset ( & pkg. bytecode . bytes ) . unwrap ( ) ;
491
+ let offset_shift = original_data_section - loader_data_section_offset;
492
+ // if there are configurables in the abi we need to shift them by `offset_shift`.
493
+ let configurables = fuel_abi. configurables . clone ( ) . map ( |configs| {
494
+ configs
495
+ . into_iter ( )
496
+ . map ( |config| Configurable {
497
+ offset : config. offset - offset_shift as u64 ,
498
+ ..config. clone ( )
499
+ } )
500
+ . collect ( )
501
+ } ) ;
502
+ fuel_abi. configurables = configurables;
503
+ let json_string = serde_json:: to_string_pretty ( & fuel_abi) ?;
504
+ std:: fs:: write ( json_abi_path, json_string) ?;
505
+ }
506
+ }
390
507
// If the executable is a predicate, we also want to display and save the predicate root.
391
508
if pkg
392
509
. descriptor
@@ -419,6 +536,19 @@ pub async fn deploy_executables(
419
536
Ok ( deployed_executable)
420
537
}
421
538
539
+ fn extract_data_offset ( binary : & [ u8 ] ) -> Result < usize > {
540
+ if binary. len ( ) < 16 {
541
+ anyhow:: bail!(
542
+ "given binary is too short to contain a data offset, len: {}" ,
543
+ binary. len( )
544
+ ) ;
545
+ }
546
+
547
+ let data_offset: [ u8 ; 8 ] = binary[ 8 ..16 ] . try_into ( ) . expect ( "checked above" ) ;
548
+
549
+ Ok ( u64:: from_be_bytes ( data_offset) as usize )
550
+ }
551
+
422
552
/// Builds and deploys contract(s). If the given path corresponds to a workspace, all deployable members
423
553
/// will be built and deployed.
424
554
///
0 commit comments