Repository: FuelLabs/fuels-rs Branch: master Commit: 137fa28aa630 Files: 641 Total size: 1.7 MB Directory structure: gitextract_yk9mjvhk/ ├── .editorconfig ├── .github/ │ ├── CODEOWNERS │ ├── PULL_REQUEST_TEMPLATE.md │ └── workflows/ │ ├── ci.yml │ ├── docs.yml │ ├── gh-pages.yml │ ├── repo-plan.toml │ └── scripts/ │ └── verify_tag.sh ├── .gitignore ├── .markdownlint.yaml ├── .markdownlintignore ├── Cargo.toml ├── LICENSE ├── README.md ├── SECURITY.md ├── _typos.toml ├── ci_checks.sh ├── docs/ │ ├── .spellcheck.yml │ ├── book.toml │ ├── spell-check-custom-words.txt │ ├── src/ │ │ ├── SUMMARY.md │ │ ├── abigen/ │ │ │ ├── index.md │ │ │ ├── the-abigen-macro.md │ │ │ └── the-json-abi-file.md │ │ ├── accounts.md │ │ ├── calling-contracts/ │ │ │ ├── call-params.md │ │ │ ├── call-response.md │ │ │ ├── calls-with-different-wallets.md │ │ │ ├── cost-estimation.md │ │ │ ├── custom-asset-transfer.md │ │ │ ├── custom-inputs-outputs.md │ │ │ ├── index.md │ │ │ ├── logs.md │ │ │ ├── low-level-calls.md │ │ │ ├── multicalls.md │ │ │ ├── other-contracts.md │ │ │ ├── simulation.md │ │ │ ├── tx-dependency-estimation.md │ │ │ ├── tx-policies.md │ │ │ └── variable-outputs.md │ │ ├── cli/ │ │ │ ├── fuels-abi-cli.md │ │ │ └── index.md │ │ ├── codec/ │ │ │ ├── decoding.md │ │ │ ├── encoding.md │ │ │ └── index.md │ │ ├── connecting/ │ │ │ ├── external-node.md │ │ │ ├── index.md │ │ │ ├── querying.md │ │ │ ├── retrying.md │ │ │ ├── rocksdb.md │ │ │ └── short-lived.md │ │ ├── contributing/ │ │ │ ├── CONTRIBUTING.md │ │ │ └── tests-structure.md │ │ ├── cookbook/ │ │ │ ├── custom-chain.md │ │ │ ├── deposit-and-withdraw.md │ │ │ ├── index.md │ │ │ └── transfer-all-assets.md │ │ ├── custom-transactions/ │ │ │ ├── custom-calls.md │ │ │ ├── index.md │ │ │ └── transaction-builders.md │ │ ├── debugging/ │ │ │ ├── decoding-script-transactions.md │ │ │ ├── function-selector.md │ │ │ └── index.md │ │ ├── deploying/ │ │ │ ├── configurable-constants.md │ │ │ ├── index.md │ │ │ ├── interacting-with-contracts.md │ │ │ ├── large_contracts.md │ │ │ ├── storage-slots.md │ │ │ └── the-fuelvm-binary-file.md │ │ ├── getting-started.md │ │ ├── glossary.md │ │ ├── index.md │ │ ├── predicates/ │ │ │ ├── index.md │ │ │ └── send-spend-predicate.md │ │ ├── preuploading-code.md │ │ ├── reference.md │ │ ├── running-scripts.md │ │ ├── testing/ │ │ │ ├── basics.md │ │ │ ├── chains.md │ │ │ ├── index.md │ │ │ └── the-setup-program-test-macro.md │ │ ├── types/ │ │ │ ├── B512.md │ │ │ ├── address.md │ │ │ ├── asset-id.md │ │ │ ├── bits256.md │ │ │ ├── bytes.md │ │ │ ├── bytes32.md │ │ │ ├── contract-id.md │ │ │ ├── conversion.md │ │ │ ├── custom_types.md │ │ │ ├── evm_address.md │ │ │ ├── index.md │ │ │ ├── string.md │ │ │ └── vectors.md │ │ └── wallets/ │ │ ├── access.md │ │ ├── checking-balances-and-coins.md │ │ ├── fake_signer.md │ │ ├── index.md │ │ ├── keystore.md │ │ ├── kms.md │ │ ├── private_key_signer.md │ │ ├── signing.md │ │ └── test-wallets.md │ └── theme/ │ └── highlight.js ├── e2e/ │ ├── Cargo.toml │ ├── Forc.toml │ ├── build.rs │ ├── src/ │ │ ├── aws_kms.rs │ │ ├── e2e_helpers.rs │ │ └── lib.rs │ ├── sway/ │ │ ├── abi/ │ │ │ ├── simple_contract/ │ │ │ │ ├── Forc.toml │ │ │ │ └── src/ │ │ │ │ └── main.sw │ │ │ ├── wasm_contract/ │ │ │ │ ├── Forc.toml │ │ │ │ └── src/ │ │ │ │ └── main.sw │ │ │ └── wasm_predicate/ │ │ │ ├── Forc.toml │ │ │ └── src/ │ │ │ └── main.sw │ │ ├── bindings/ │ │ │ ├── sharing_types/ │ │ │ │ ├── contract_a/ │ │ │ │ │ ├── Forc.toml │ │ │ │ │ └── src/ │ │ │ │ │ └── main.sw │ │ │ │ ├── contract_b/ │ │ │ │ │ ├── Forc.toml │ │ │ │ │ └── src/ │ │ │ │ │ └── main.sw │ │ │ │ └── shared_lib/ │ │ │ │ ├── Forc.toml │ │ │ │ └── src/ │ │ │ │ └── lib.sw │ │ │ ├── simple_contract/ │ │ │ │ ├── Forc.toml │ │ │ │ └── src/ │ │ │ │ └── main.sw │ │ │ └── type_paths/ │ │ │ ├── Forc.toml │ │ │ └── src/ │ │ │ ├── another_lib.sw │ │ │ ├── contract_a_types.sw │ │ │ └── main.sw │ │ ├── contracts/ │ │ │ ├── asserts/ │ │ │ │ ├── Forc.toml │ │ │ │ └── src/ │ │ │ │ └── main.sw │ │ │ ├── auth_testing_abi/ │ │ │ │ ├── Forc.toml │ │ │ │ └── src/ │ │ │ │ └── main.sw │ │ │ ├── auth_testing_contract/ │ │ │ │ ├── Forc.toml │ │ │ │ └── src/ │ │ │ │ └── main.sw │ │ │ ├── block_timestamp/ │ │ │ │ ├── Forc.toml │ │ │ │ └── src/ │ │ │ │ └── main.sw │ │ │ ├── configurables/ │ │ │ │ ├── Forc.toml │ │ │ │ └── src/ │ │ │ │ └── main.sw │ │ │ ├── contract_test/ │ │ │ │ ├── Forc.toml │ │ │ │ └── src/ │ │ │ │ └── main.sw │ │ │ ├── huge_contract/ │ │ │ │ ├── Forc.toml │ │ │ │ └── src/ │ │ │ │ └── main.sw │ │ │ ├── large_return_data/ │ │ │ │ ├── Forc.toml │ │ │ │ └── src/ │ │ │ │ └── main.sw │ │ │ ├── lib_contract/ │ │ │ │ ├── Forc.toml │ │ │ │ └── src/ │ │ │ │ └── main.sw │ │ │ ├── lib_contract_abi/ │ │ │ │ ├── Forc.toml │ │ │ │ └── src/ │ │ │ │ └── main.sw │ │ │ ├── lib_contract_caller/ │ │ │ │ ├── Forc.toml │ │ │ │ └── src/ │ │ │ │ └── main.sw │ │ │ ├── library_test/ │ │ │ │ ├── Forc.toml │ │ │ │ └── src/ │ │ │ │ └── main.sw │ │ │ ├── liquidity_pool/ │ │ │ │ ├── Forc.toml │ │ │ │ └── src/ │ │ │ │ └── main.sw │ │ │ ├── low_level_caller/ │ │ │ │ ├── Forc.toml │ │ │ │ └── src/ │ │ │ │ └── main.sw │ │ │ ├── msg_methods/ │ │ │ │ ├── Forc.toml │ │ │ │ └── src/ │ │ │ │ └── main.sw │ │ │ ├── multiple_read_calls/ │ │ │ │ ├── Forc.toml │ │ │ │ └── src/ │ │ │ │ └── main.sw │ │ │ ├── needs_custom_decoder/ │ │ │ │ ├── Forc.toml │ │ │ │ └── src/ │ │ │ │ └── main.sw │ │ │ ├── payable_annotation/ │ │ │ │ ├── Forc.toml │ │ │ │ └── src/ │ │ │ │ └── main.sw │ │ │ ├── proxy/ │ │ │ │ ├── Forc.toml │ │ │ │ └── src/ │ │ │ │ └── main.sw │ │ │ ├── revert_transaction_error/ │ │ │ │ ├── Forc.toml │ │ │ │ └── src/ │ │ │ │ └── main.sw │ │ │ ├── storage/ │ │ │ │ ├── Forc.toml │ │ │ │ └── src/ │ │ │ │ └── main.sw │ │ │ ├── token_ops/ │ │ │ │ ├── Forc.toml │ │ │ │ └── src/ │ │ │ │ └── main.sw │ │ │ ├── transaction_block_height/ │ │ │ │ ├── Forc.toml │ │ │ │ └── src/ │ │ │ │ └── main.sw │ │ │ ├── tx_input_output/ │ │ │ │ ├── Forc.toml │ │ │ │ └── src/ │ │ │ │ └── main.sw │ │ │ └── var_outputs/ │ │ │ ├── Forc.toml │ │ │ └── src/ │ │ │ └── main.sw │ │ ├── logs/ │ │ │ ├── contract_logs/ │ │ │ │ ├── Forc.toml │ │ │ │ └── src/ │ │ │ │ └── main.sw │ │ │ ├── contract_logs_abi/ │ │ │ │ ├── Forc.toml │ │ │ │ └── src/ │ │ │ │ └── main.sw │ │ │ ├── contract_revert_logs/ │ │ │ │ ├── Forc.toml │ │ │ │ └── src/ │ │ │ │ └── main.sw │ │ │ ├── contract_with_contract_logs/ │ │ │ │ ├── Forc.toml │ │ │ │ └── src/ │ │ │ │ └── main.sw │ │ │ ├── script_heap_logs/ │ │ │ │ ├── Forc.toml │ │ │ │ └── src/ │ │ │ │ └── main.sw │ │ │ ├── script_logs/ │ │ │ │ ├── Forc.toml │ │ │ │ └── src/ │ │ │ │ └── main.sw │ │ │ ├── script_revert_logs/ │ │ │ │ ├── Forc.toml │ │ │ │ └── src/ │ │ │ │ └── main.sw │ │ │ └── script_with_contract_logs/ │ │ │ ├── Forc.toml │ │ │ └── src/ │ │ │ └── main.sw │ │ ├── predicates/ │ │ │ ├── basic_predicate/ │ │ │ │ ├── Forc.toml │ │ │ │ └── src/ │ │ │ │ └── main.sw │ │ │ ├── predicate_blobs/ │ │ │ │ ├── Forc.toml │ │ │ │ └── src/ │ │ │ │ └── main.sw │ │ │ ├── predicate_configurables/ │ │ │ │ ├── Forc.toml │ │ │ │ └── src/ │ │ │ │ └── main.sw │ │ │ ├── predicate_tx_input_output/ │ │ │ │ ├── Forc.toml │ │ │ │ └── src/ │ │ │ │ └── main.sw │ │ │ ├── predicate_witnesses/ │ │ │ │ ├── Forc.toml │ │ │ │ └── src/ │ │ │ │ └── main.sw │ │ │ ├── signatures/ │ │ │ │ ├── Forc.toml │ │ │ │ └── src/ │ │ │ │ └── main.sw │ │ │ └── swap/ │ │ │ ├── Forc.toml │ │ │ └── src/ │ │ │ └── main.sw │ │ ├── scripts/ │ │ │ ├── arguments/ │ │ │ │ ├── Forc.toml │ │ │ │ └── src/ │ │ │ │ └── main.sw │ │ │ ├── basic_script/ │ │ │ │ ├── Forc.toml │ │ │ │ └── src/ │ │ │ │ └── main.sw │ │ │ ├── empty/ │ │ │ │ ├── Forc.toml │ │ │ │ └── src/ │ │ │ │ └── main.sw │ │ │ ├── require_from_contract/ │ │ │ │ ├── Forc.toml │ │ │ │ └── src/ │ │ │ │ └── main.sw │ │ │ ├── reverting/ │ │ │ │ ├── Forc.toml │ │ │ │ └── src/ │ │ │ │ └── main.sw │ │ │ ├── script_array/ │ │ │ │ ├── Forc.toml │ │ │ │ └── src/ │ │ │ │ └── main.sw │ │ │ ├── script_asserts/ │ │ │ │ ├── Forc.toml │ │ │ │ └── src/ │ │ │ │ └── main.sw │ │ │ ├── script_blobs/ │ │ │ │ ├── Forc.toml │ │ │ │ └── src/ │ │ │ │ └── main.sw │ │ │ ├── script_configurables/ │ │ │ │ ├── Forc.toml │ │ │ │ └── src/ │ │ │ │ └── main.sw │ │ │ ├── script_enum/ │ │ │ │ ├── Forc.toml │ │ │ │ └── src/ │ │ │ │ └── main.sw │ │ │ ├── script_needs_custom_decoder/ │ │ │ │ ├── Forc.toml │ │ │ │ └── src/ │ │ │ │ └── main.sw │ │ │ ├── script_proxy/ │ │ │ │ ├── Forc.toml │ │ │ │ └── src/ │ │ │ │ └── main.sw │ │ │ ├── script_struct/ │ │ │ │ ├── Forc.toml │ │ │ │ └── src/ │ │ │ │ └── main.sw │ │ │ ├── script_tx_input_output/ │ │ │ │ ├── Forc.toml │ │ │ │ └── src/ │ │ │ │ └── main.sw │ │ │ └── transfer_script/ │ │ │ ├── Forc.toml │ │ │ └── src/ │ │ │ └── main.sw │ │ └── types/ │ │ ├── contracts/ │ │ │ ├── b256/ │ │ │ │ ├── Forc.toml │ │ │ │ └── src/ │ │ │ │ └── main.sw │ │ │ ├── b512/ │ │ │ │ ├── Forc.toml │ │ │ │ └── src/ │ │ │ │ └── main.sw │ │ │ ├── bytes/ │ │ │ │ ├── Forc.toml │ │ │ │ └── src/ │ │ │ │ └── main.sw │ │ │ ├── call_empty_return/ │ │ │ │ ├── Forc.toml │ │ │ │ └── src/ │ │ │ │ └── main.sw │ │ │ ├── complex_types_contract/ │ │ │ │ ├── Forc.toml │ │ │ │ └── src/ │ │ │ │ └── main.sw │ │ │ ├── contract_output_test/ │ │ │ │ ├── Forc.toml │ │ │ │ └── src/ │ │ │ │ └── main.sw │ │ │ ├── empty_arguments/ │ │ │ │ ├── Forc.toml │ │ │ │ └── src/ │ │ │ │ └── main.sw │ │ │ ├── enum_as_input/ │ │ │ │ ├── Forc.toml │ │ │ │ └── src/ │ │ │ │ └── main.sw │ │ │ ├── enum_encoding/ │ │ │ │ ├── Forc.toml │ │ │ │ └── src/ │ │ │ │ └── main.sw │ │ │ ├── enum_inside_struct/ │ │ │ │ ├── Forc.toml │ │ │ │ └── src/ │ │ │ │ └── main.sw │ │ │ ├── evm_address/ │ │ │ │ ├── Forc.toml │ │ │ │ └── src/ │ │ │ │ └── main.sw │ │ │ ├── generics/ │ │ │ │ ├── Forc.toml │ │ │ │ └── src/ │ │ │ │ └── main.sw │ │ │ ├── heap_type_in_enums/ │ │ │ │ ├── Forc.toml │ │ │ │ └── src/ │ │ │ │ └── main.sw │ │ │ ├── heap_types/ │ │ │ │ ├── Forc.toml │ │ │ │ └── src/ │ │ │ │ └── main.sw │ │ │ ├── identity/ │ │ │ │ ├── Forc.toml │ │ │ │ └── src/ │ │ │ │ └── main.sw │ │ │ ├── native_types/ │ │ │ │ ├── Forc.toml │ │ │ │ └── src/ │ │ │ │ └── main.sw │ │ │ ├── nested_structs/ │ │ │ │ ├── Forc.toml │ │ │ │ └── src/ │ │ │ │ └── main.sw │ │ │ ├── options/ │ │ │ │ ├── Forc.toml │ │ │ │ └── src/ │ │ │ │ └── main.sw │ │ │ ├── raw_slice/ │ │ │ │ ├── Forc.toml │ │ │ │ └── src/ │ │ │ │ └── main.sw │ │ │ ├── results/ │ │ │ │ ├── Forc.toml │ │ │ │ └── src/ │ │ │ │ └── main.sw │ │ │ ├── std_lib_string/ │ │ │ │ ├── Forc.toml │ │ │ │ └── src/ │ │ │ │ └── main.sw │ │ │ ├── str_in_array/ │ │ │ │ ├── Forc.toml │ │ │ │ └── src/ │ │ │ │ └── main.sw │ │ │ ├── string_slice/ │ │ │ │ ├── Forc.toml │ │ │ │ └── src/ │ │ │ │ └── main.sw │ │ │ ├── tuples/ │ │ │ │ ├── Forc.toml │ │ │ │ └── src/ │ │ │ │ └── main.sw │ │ │ ├── two_structs/ │ │ │ │ ├── Forc.toml │ │ │ │ └── src/ │ │ │ │ └── main.sw │ │ │ ├── type_inside_enum/ │ │ │ │ ├── Forc.toml │ │ │ │ └── src/ │ │ │ │ └── main.sw │ │ │ ├── u128/ │ │ │ │ ├── Forc.toml │ │ │ │ └── src/ │ │ │ │ └── main.sw │ │ │ ├── u256/ │ │ │ │ ├── Forc.toml │ │ │ │ └── src/ │ │ │ │ └── main.sw │ │ │ ├── vector_output/ │ │ │ │ ├── Forc.toml │ │ │ │ └── src/ │ │ │ │ └── main.sw │ │ │ └── vectors/ │ │ │ ├── Forc.toml │ │ │ └── src/ │ │ │ ├── data_structures.sw │ │ │ ├── eq_impls.sw │ │ │ ├── main.sw │ │ │ └── utils.sw │ │ ├── predicates/ │ │ │ ├── address/ │ │ │ │ ├── Forc.toml │ │ │ │ └── src/ │ │ │ │ └── main.sw │ │ │ ├── enums/ │ │ │ │ ├── Forc.toml │ │ │ │ └── src/ │ │ │ │ └── main.sw │ │ │ ├── predicate_b256/ │ │ │ │ ├── Forc.toml │ │ │ │ └── src/ │ │ │ │ └── main.sw │ │ │ ├── predicate_bytes/ │ │ │ │ ├── Forc.toml │ │ │ │ └── src/ │ │ │ │ └── main.sw │ │ │ ├── predicate_bytes_hash/ │ │ │ │ ├── Forc.toml │ │ │ │ └── src/ │ │ │ │ └── main.sw │ │ │ ├── predicate_generics/ │ │ │ │ ├── Forc.toml │ │ │ │ └── src/ │ │ │ │ └── main.sw │ │ │ ├── predicate_raw_slice/ │ │ │ │ ├── Forc.toml │ │ │ │ └── src/ │ │ │ │ └── main.sw │ │ │ ├── predicate_std_lib_string/ │ │ │ │ ├── Forc.toml │ │ │ │ └── src/ │ │ │ │ └── main.sw │ │ │ ├── predicate_string_slice/ │ │ │ │ ├── Forc.toml │ │ │ │ └── src/ │ │ │ │ └── main.sw │ │ │ ├── predicate_tuples/ │ │ │ │ ├── Forc.toml │ │ │ │ └── src/ │ │ │ │ └── main.sw │ │ │ ├── predicate_u128/ │ │ │ │ ├── Forc.toml │ │ │ │ └── src/ │ │ │ │ └── main.sw │ │ │ ├── predicate_u256/ │ │ │ │ ├── Forc.toml │ │ │ │ └── src/ │ │ │ │ └── main.sw │ │ │ ├── predicate_vector/ │ │ │ │ ├── Forc.toml │ │ │ │ └── src/ │ │ │ │ └── main.sw │ │ │ ├── predicate_vectors/ │ │ │ │ ├── Forc.toml │ │ │ │ └── src/ │ │ │ │ └── main.sw │ │ │ ├── structs/ │ │ │ │ ├── Forc.toml │ │ │ │ └── src/ │ │ │ │ └── main.sw │ │ │ └── u64/ │ │ │ ├── Forc.toml │ │ │ └── src/ │ │ │ └── main.sw │ │ └── scripts/ │ │ ├── options_results/ │ │ │ ├── Forc.toml │ │ │ └── src/ │ │ │ └── main.sw │ │ ├── script_b256/ │ │ │ ├── Forc.toml │ │ │ └── src/ │ │ │ └── main.sw │ │ ├── script_bytes/ │ │ │ ├── Forc.toml │ │ │ └── src/ │ │ │ └── main.sw │ │ ├── script_generics/ │ │ │ ├── Forc.toml │ │ │ └── src/ │ │ │ └── main.sw │ │ ├── script_heap_types/ │ │ │ ├── Forc.toml │ │ │ └── src/ │ │ │ └── main.sw │ │ ├── script_raw_slice/ │ │ │ ├── Forc.toml │ │ │ └── src/ │ │ │ └── main.sw │ │ ├── script_std_lib_string/ │ │ │ ├── Forc.toml │ │ │ └── src/ │ │ │ └── main.sw │ │ ├── script_string_slice/ │ │ │ ├── Forc.toml │ │ │ └── src/ │ │ │ └── main.sw │ │ ├── script_tuples/ │ │ │ ├── Forc.toml │ │ │ └── src/ │ │ │ └── main.sw │ │ ├── script_u128/ │ │ │ ├── Forc.toml │ │ │ └── src/ │ │ │ └── main.sw │ │ ├── script_u256/ │ │ │ ├── Forc.toml │ │ │ └── src/ │ │ │ └── main.sw │ │ └── script_vectors/ │ │ ├── Forc.toml │ │ └── src/ │ │ ├── data_structures.sw │ │ ├── eq_impls.sw │ │ ├── main.sw │ │ └── utils.sw │ └── tests/ │ ├── aws.rs │ ├── binary_format.rs │ ├── bindings.rs │ ├── configurables.rs │ ├── contracts.rs │ ├── debug_utils.rs │ ├── from_token.rs │ ├── imports.rs │ ├── logs.rs │ ├── predicates.rs │ ├── providers.rs │ ├── scripts.rs │ ├── storage.rs │ ├── types_contracts.rs │ ├── types_predicates.rs │ ├── types_scripts.rs │ └── wallets.rs ├── examples/ │ ├── codec/ │ │ ├── Cargo.toml │ │ └── src/ │ │ └── lib.rs │ ├── contracts/ │ │ ├── Cargo.toml │ │ └── src/ │ │ └── lib.rs │ ├── cookbook/ │ │ ├── Cargo.toml │ │ └── src/ │ │ └── lib.rs │ ├── debugging/ │ │ ├── Cargo.toml │ │ └── src/ │ │ └── lib.rs │ ├── macros/ │ │ ├── Cargo.toml │ │ └── src/ │ │ └── lib.rs │ ├── predicates/ │ │ ├── Cargo.toml │ │ └── src/ │ │ └── lib.rs │ ├── providers/ │ │ ├── Cargo.toml │ │ └── src/ │ │ └── lib.rs │ ├── rust_bindings/ │ │ ├── Cargo.toml │ │ └── src/ │ │ ├── abi.json │ │ ├── lib.rs │ │ └── rust_bindings_formatted.rs │ ├── types/ │ │ ├── Cargo.toml │ │ └── src/ │ │ └── lib.rs │ └── wallets/ │ ├── Cargo.toml │ └── src/ │ └── lib.rs ├── packages/ │ ├── fuels/ │ │ ├── Cargo.toml │ │ └── src/ │ │ └── lib.rs │ ├── fuels-accounts/ │ │ ├── Cargo.toml │ │ └── src/ │ │ ├── account.rs │ │ ├── accounts_utils.rs │ │ ├── coin_cache.rs │ │ ├── keystore.rs │ │ ├── lib.rs │ │ ├── predicate.rs │ │ ├── provider/ │ │ │ ├── cache.rs │ │ │ ├── retry_util.rs │ │ │ ├── retryable_client.rs │ │ │ ├── supported_fuel_core_version.rs │ │ │ └── supported_versions.rs │ │ ├── provider.rs │ │ ├── schema/ │ │ │ └── schema.sdl │ │ ├── signers/ │ │ │ ├── fake.rs │ │ │ ├── kms/ │ │ │ │ ├── aws.rs │ │ │ │ └── google.rs │ │ │ ├── kms.rs │ │ │ └── private_key.rs │ │ ├── signers.rs │ │ └── wallet.rs │ ├── fuels-code-gen/ │ │ ├── Cargo.toml │ │ └── src/ │ │ ├── error.rs │ │ ├── lib.rs │ │ ├── program_bindings/ │ │ │ ├── abigen/ │ │ │ │ ├── abigen_target.rs │ │ │ │ ├── bindings/ │ │ │ │ │ ├── contract.rs │ │ │ │ │ ├── function_generator.rs │ │ │ │ │ ├── predicate.rs │ │ │ │ │ ├── script.rs │ │ │ │ │ └── utils.rs │ │ │ │ ├── bindings.rs │ │ │ │ ├── configurables.rs │ │ │ │ └── logs.rs │ │ │ ├── abigen.rs │ │ │ ├── custom_types/ │ │ │ │ ├── enums.rs │ │ │ │ ├── structs.rs │ │ │ │ └── utils.rs │ │ │ ├── custom_types.rs │ │ │ ├── generated_code.rs │ │ │ ├── resolved_type.rs │ │ │ └── utils.rs │ │ ├── program_bindings.rs │ │ └── utils.rs │ ├── fuels-core/ │ │ ├── Cargo.toml │ │ └── src/ │ │ ├── codec/ │ │ │ ├── abi_decoder/ │ │ │ │ ├── bounded_decoder.rs │ │ │ │ └── decode_as_debug_str.rs │ │ │ ├── abi_decoder.rs │ │ │ ├── abi_encoder/ │ │ │ │ └── bounded_encoder.rs │ │ │ ├── abi_encoder.rs │ │ │ ├── abi_formatter.rs │ │ │ ├── function_selector.rs │ │ │ ├── logs.rs │ │ │ └── utils.rs │ │ ├── codec.rs │ │ ├── lib.rs │ │ ├── traits/ │ │ │ ├── parameterize.rs │ │ │ ├── signer.rs │ │ │ └── tokenizable.rs │ │ ├── traits.rs │ │ ├── types/ │ │ │ ├── checksum_address.rs │ │ │ ├── core/ │ │ │ │ ├── bits.rs │ │ │ │ ├── bytes.rs │ │ │ │ ├── identity.rs │ │ │ │ ├── raw_slice.rs │ │ │ │ ├── sized_ascii_string.rs │ │ │ │ └── u256.rs │ │ │ ├── core.rs │ │ │ ├── dry_runner.rs │ │ │ ├── errors.rs │ │ │ ├── method_descriptor.rs │ │ │ ├── param_types/ │ │ │ │ ├── from_type_application.rs │ │ │ │ └── param_type.rs │ │ │ ├── param_types.rs │ │ │ ├── token.rs │ │ │ ├── transaction_builders/ │ │ │ │ ├── blob.rs │ │ │ │ └── script_tx_estimator.rs │ │ │ ├── transaction_builders.rs │ │ │ ├── tx_response.rs │ │ │ ├── tx_status.rs │ │ │ ├── wrappers/ │ │ │ │ ├── block.rs │ │ │ │ ├── chain_info.rs │ │ │ │ ├── coin.rs │ │ │ │ ├── coin_type.rs │ │ │ │ ├── coin_type_id.rs │ │ │ │ ├── input.rs │ │ │ │ ├── message.rs │ │ │ │ ├── message_proof.rs │ │ │ │ ├── node_info.rs │ │ │ │ ├── transaction.rs │ │ │ │ └── transaction_response.rs │ │ │ └── wrappers.rs │ │ ├── types.rs │ │ ├── utils/ │ │ │ ├── constants.rs │ │ │ └── offsets.rs │ │ └── utils.rs │ ├── fuels-macros/ │ │ ├── .gitignore │ │ ├── Cargo.toml │ │ ├── src/ │ │ │ ├── abigen/ │ │ │ │ └── parsing.rs │ │ │ ├── abigen.rs │ │ │ ├── derive/ │ │ │ │ ├── parameterize.rs │ │ │ │ ├── tokenizable.rs │ │ │ │ ├── try_from.rs │ │ │ │ └── utils.rs │ │ │ ├── derive.rs │ │ │ ├── lib.rs │ │ │ ├── parse_utils/ │ │ │ │ ├── command.rs │ │ │ │ ├── unique_lit_strs.rs │ │ │ │ └── unique_name_values.rs │ │ │ ├── parse_utils.rs │ │ │ ├── setup_program_test/ │ │ │ │ ├── code_gen.rs │ │ │ │ ├── parsing/ │ │ │ │ │ ├── command_parser.rs │ │ │ │ │ ├── commands/ │ │ │ │ │ │ ├── abigen.rs │ │ │ │ │ │ ├── deploy_contract.rs │ │ │ │ │ │ ├── initialize_wallet.rs │ │ │ │ │ │ ├── load_script.rs │ │ │ │ │ │ └── set_options.rs │ │ │ │ │ ├── commands.rs │ │ │ │ │ └── validations.rs │ │ │ │ └── parsing.rs │ │ │ └── setup_program_test.rs │ │ └── tests/ │ │ ├── macro_usage.rs │ │ └── ui/ │ │ ├── abigen/ │ │ │ ├── duplicate_attribute.rs │ │ │ ├── duplicate_attribute.stderr │ │ │ ├── invalid_abi_path.rs │ │ │ ├── invalid_abi_path.stderr │ │ │ ├── invalid_abi_value.rs │ │ │ ├── invalid_abi_value.stderr │ │ │ ├── invalid_name_value.rs │ │ │ ├── invalid_name_value.stderr │ │ │ ├── invalid_program_type.rs │ │ │ ├── invalid_program_type.stderr │ │ │ ├── malformed_abi.rs │ │ │ ├── malformed_abi.stderr │ │ │ ├── missing_abi_attribute.rs │ │ │ ├── missing_abi_attribute.stderr │ │ │ ├── missing_name_attr.rs │ │ │ ├── missing_name_attr.stderr │ │ │ ├── unrecognized_attribute.rs │ │ │ └── unrecognized_attribute.stderr │ │ ├── derive/ │ │ │ ├── parameterize/ │ │ │ │ ├── attribute_must_be_named_value.rs │ │ │ │ ├── attribute_must_be_named_value.stderr │ │ │ │ ├── only_generic_types_are_supported.rs │ │ │ │ ├── only_generic_types_are_supported.stderr │ │ │ │ ├── only_one_variant_element_supported.rs │ │ │ │ ├── only_one_variant_element_supported.stderr │ │ │ │ ├── struct_like_enum_variants_not_supported.rs │ │ │ │ ├── struct_like_enum_variants_not_supported.stderr │ │ │ │ ├── tuple_like_structs_not_supported.rs │ │ │ │ └── tuple_like_structs_not_supported.stderr │ │ │ └── tokenizable/ │ │ │ ├── attribute_must_be_named_value.stderr │ │ │ ├── only_generic_types_are_supported.rs │ │ │ ├── only_generic_types_are_supported.stderr │ │ │ ├── only_one_variant_element_supported.rs │ │ │ ├── only_one_variant_element_supported.stderr │ │ │ ├── struct_like_enum_variants_not_supported.rs │ │ │ ├── struct_like_enum_variants_not_supported.stderr │ │ │ ├── tuple_like_structs_not_supported.rs │ │ │ └── tuple_like_structs_not_supported.stderr │ │ └── setup_program_test/ │ │ ├── abigen_command_is_missing.rs │ │ ├── abigen_command_is_missing.stderr │ │ ├── duplicate_wallet_command.rs │ │ ├── duplicate_wallet_command.stderr │ │ ├── duplicate_wallet_names.rs │ │ ├── duplicate_wallet_names.stderr │ │ ├── invalid_path.rs │ │ ├── invalid_path.stderr │ │ ├── invalid_project_path.rs │ │ ├── invalid_project_path.stderr │ │ ├── unknown_command.rs │ │ ├── unknown_command.stderr │ │ ├── unknown_contract.rs │ │ ├── unknown_contract.stderr │ │ ├── unknown_options_key.rs │ │ ├── unknown_options_key.stderr │ │ ├── unknown_options_value.rs │ │ └── unknown_options_value.stderr │ ├── fuels-programs/ │ │ ├── Cargo.toml │ │ └── src/ │ │ ├── assembly/ │ │ │ ├── contract_call.rs │ │ │ ├── cursor.rs │ │ │ └── script_and_predicate_loader.rs │ │ ├── assembly.rs │ │ ├── calls/ │ │ │ ├── call_handler.rs │ │ │ ├── contract_call.rs │ │ │ ├── receipt_parser.rs │ │ │ ├── script_call.rs │ │ │ ├── traits/ │ │ │ │ ├── contract_dep_configurator.rs │ │ │ │ ├── response_parser.rs │ │ │ │ └── transaction_tuner.rs │ │ │ ├── traits.rs │ │ │ └── utils.rs │ │ ├── calls.rs │ │ ├── contract/ │ │ │ ├── loader.rs │ │ │ ├── regular.rs │ │ │ └── storage.rs │ │ ├── contract.rs │ │ ├── debug.rs │ │ ├── executable.rs │ │ ├── lib.rs │ │ ├── responses/ │ │ │ ├── call.rs │ │ │ └── submit.rs │ │ ├── responses.rs │ │ └── utils.rs │ └── fuels-test-helpers/ │ ├── Cargo.toml │ └── src/ │ ├── accounts.rs │ ├── fuel_bin_service.rs │ ├── lib.rs │ ├── node_types.rs │ ├── service.rs │ ├── utils.rs │ └── wallets_config.rs ├── rustfmt.toml ├── scripts/ │ ├── change-log/ │ │ ├── Cargo.toml │ │ └── src/ │ │ ├── adapters/ │ │ │ └── octocrab.rs │ │ ├── adapters.rs │ │ ├── domain/ │ │ │ ├── changelog.rs │ │ │ └── models.rs │ │ ├── domain.rs │ │ ├── lib.rs │ │ ├── main.rs │ │ ├── ports/ │ │ │ └── github.rs │ │ └── ports.rs │ ├── check-docs/ │ │ ├── Cargo.toml │ │ ├── src/ │ │ │ ├── lib.rs │ │ │ └── main.rs │ │ └── tests/ │ │ ├── harness.rs │ │ └── test_data/ │ │ ├── docs/ │ │ │ └── src/ │ │ │ ├── SUMMARY.md │ │ │ ├── test-not-there.md │ │ │ └── test.md │ │ ├── test_anchor_data.rs │ │ └── test_include_data.md │ ├── fuel-core-version/ │ │ ├── Cargo.toml │ │ └── src/ │ │ └── main.rs │ └── versions-replacer/ │ ├── Cargo.toml │ └── src/ │ ├── lib.rs │ ├── main.rs │ ├── metadata.rs │ └── replace.rs └── wasm-tests/ ├── .cargo/ │ └── config.toml ├── Cargo.toml └── src/ └── lib.rs ================================================ FILE CONTENTS ================================================ ================================================ FILE: .editorconfig ================================================ [*.toml] indent_style = space indent_size = 2 ================================================ FILE: .github/CODEOWNERS ================================================ * @FuelLabs/client ================================================ FILE: .github/PULL_REQUEST_TEMPLATE.md ================================================ # Release notes In this release, we: - Did this and that # Summary # Breaking Changes # Checklist - [ ] All **changes** are **covered** by **tests** (or not applicable) - [ ] All **changes** are **documented** (or not applicable) - [ ] I **reviewed** the **entire PR** myself (preferably, on GH UI) - [ ] I **described** all **Breaking Changes** (or there's none) ================================================ FILE: .github/workflows/ci.yml ================================================ name: CI on: push: branches: - master pull_request: release: types: [ published ] concurrency: group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} cancel-in-progress: true env: CARGO_TERM_COLOR: always DASEL_VERSION: https://github.com/TomWright/dasel/releases/download/v2.3.6/dasel_linux_amd64 RUSTFLAGS: "-D warnings" FUEL_CORE_VERSION: 0.47.1 FUEL_CORE_PATCH_BRANCH: "" FUEL_CORE_PATCH_REVISION: "" RUST_VERSION: 1.93.0 FORC_VERSION: 0.69.1 FORC_PATCH_BRANCH: "" FORC_PATCH_REVISION: "" NEXTEST_HIDE_PROGRESS_BAR: "true" NEXTEST_STATUS_LEVEL: "fail" jobs: setup-test-projects: runs-on: buildjet-4vcpu-ubuntu-2204 steps: - uses: actions/checkout@v3 - name: Install toolchain uses: dtolnay/rust-toolchain@master with: toolchain: ${{ env.RUST_VERSION }} # selecting a toolchain either by action or manual `rustup` calls should happen # before the cache plugin, as it uses the current rustc version as its cache key - uses: buildjet/cache@v3 with: prefix-key: "v1-rust" - name: Set git config run: | git config --global core.bigfilethreshold 100m - name: Install forc and forc-fmt run: | if [[ -n $FORC_PATCH_BRANCH ]]; then cargo install --locked forc forc-fmt --git https://github.com/FuelLabs/sway --branch $FORC_PATCH_BRANCH elif [[ -n $FORC_PATCH_REVISION ]]; then cargo install --locked forc forc-fmt --git https://github.com/FuelLabs/sway --rev $FORC_PATCH_REVISION else curl -sSLf https://github.com/FuelLabs/sway/releases/download/v${{ env.FORC_VERSION }}/forc-binaries-linux_amd64.tar.gz -L -o forc.tar.gz tar -xvf forc.tar.gz chmod +x forc-binaries/forc mv forc-binaries/forc /usr/local/bin/forc mv forc-binaries/forc-fmt /usr/local/bin/forc-fmt fi - name: Check format of Sway test projects run: forc fmt --check --path e2e - name: Build Sway test projects run: forc build --release --terse --error-on-warnings --path e2e - uses: actions/upload-artifact@v4 with: retention-days: 2 name: sway-examples path: | e2e/sway/**/out/* get-workspace-members: runs-on: buildjet-4vcpu-ubuntu-2204 outputs: members: ${{ steps.set-members.outputs.members }} steps: - name: Checkout repository uses: actions/checkout@v3 - id: set-members run: | # install dasel curl -sSLf "$DASEL_VERSION" -L -o dasel && chmod +x dasel mv ./dasel /usr/local/bin/dasel members=$(cat Cargo.toml | dasel -r toml -w json 'workspace.members' | jq -r ".[]" | xargs -I '{}' dasel -f {}/Cargo.toml 'package.name' | jq -R '[.]' | jq -s -c 'add') echo "members=$members" >> $GITHUB_OUTPUT verify-rust-version: runs-on: buildjet-4vcpu-ubuntu-2204 steps: - uses: actions/checkout@v3 # Ensure CI is using the same minimum toolchain specified in fuels Cargo.toml - run: | curl -sSLf "$DASEL_VERSION" -L -o dasel && chmod +x dasel mv ./dasel /usr/local/bin/dasel MIN_VERSION=$(cat Cargo.toml | dasel -r toml 'workspace.package.rust-version' -w plain) RUST_VERSION="${{ env.RUST_VERSION }}" echo "Comparing minimum supported toolchain ($MIN_VERSION) with ci toolchain (RUST_VERSION)" test "$MIN_VERSION" == "$RUST_VERSION" # Fetch Fuel Core and upload as artifact, useful when we build the core from a # revision so that we can repeat flaky tests without rebuilding the core. fetch-fuel-core: runs-on: buildjet-4vcpu-ubuntu-2204 steps: - uses: dtolnay/rust-toolchain@master with: toolchain: ${{ env.RUST_VERSION }} targets: wasm32-unknown-unknown # selecting a toolchain either by action or manual `rustup` calls should happen # before the cache plugin, as it uses the current rustc version as its cache key - uses: buildjet/cache@v3 continue-on-error: true with: key: "fuel-core-build" - name: Install Fuel Core run: | if [[ -n $FUEL_CORE_PATCH_BRANCH ]]; then cargo install --locked fuel-core-bin --git https://github.com/FuelLabs/fuel-core --branch "$FUEL_CORE_PATCH_BRANCH" --root fuel-core-install elif [[ -n $FUEL_CORE_PATCH_REVISION ]]; then cargo install --locked fuel-core-bin --git https://github.com/FuelLabs/fuel-core --rev "$FUEL_CORE_PATCH_REVISION" --root fuel-core-install else curl -sSLf https://github.com/FuelLabs/fuel-core/releases/download/v${{ env.FUEL_CORE_VERSION }}/fuel-core-${{ env.FUEL_CORE_VERSION }}-x86_64-unknown-linux-gnu.tar.gz -L -o fuel-core.tar.gz tar -xvf fuel-core.tar.gz chmod +x fuel-core-${{ env.FUEL_CORE_VERSION }}-x86_64-unknown-linux-gnu/fuel-core mkdir -p fuel-core-install/bin mv fuel-core-${{ env.FUEL_CORE_VERSION }}-x86_64-unknown-linux-gnu/fuel-core fuel-core-install/bin/fuel-core fi - uses: actions/upload-artifact@v4 with: name: fuel-core path: fuel-core-install/bin/fuel-core # Ensure workspace is publishable publish-crates-check: runs-on: buildjet-4vcpu-ubuntu-2204 steps: - name: Checkout repository uses: actions/checkout@v3 - uses: dtolnay/rust-toolchain@master with: toolchain: ${{ env.RUST_VERSION }} - name: Publish crate check uses: FuelLabs/publish-crates@v1 with: dry-run: true check-repo: false ignore-unpublished-changes: true cargo-verifications: needs: - setup-test-projects - verify-rust-version - get-workspace-members - publish-crates-check - fetch-fuel-core runs-on: buildjet-4vcpu-ubuntu-2204 strategy: matrix: cargo_command: [ check ] args: [ --all-features ] package: ${{fromJSON(needs.get-workspace-members.outputs.members)}} include: - cargo_command: fmt args: --all --verbose -- --check - cargo_command: clippy args: --all-targets download_sway_artifacts: sway-examples - cargo_command: nextest args: run --all-targets --features "default fuel-core-lib coin-cache" --workspace --cargo-quiet --no-fail-fast download_sway_artifacts: sway-examples install_fuel_core: true - cargo_command: nextest args: run --all-targets --workspace --cargo-quiet --no-fail-fast download_sway_artifacts: sway-examples install_fuel_core: true - cargo_command: test args: --doc --workspace - cargo_command: machete args: --skip-target-dir - command: test_wasm args: - command: check_fuel_core_version args: - command: check_doc_anchors_valid args: - command: check_doc_unresolved_links args: - command: check_typos args: steps: - name: Checkout repository uses: actions/checkout@v3 with: ref: ${{ github.event.pull_request.head.sha }} - uses: dtolnay/rust-toolchain@master with: toolchain: ${{ env.RUST_VERSION }} components: clippy,rustfmt targets: wasm32-unknown-unknown # selecting a toolchain either by action or manual `rustup` calls should happen # before the cache plugin, as it uses the current rustc version as its cache key - uses: buildjet/cache@v3 continue-on-error: true with: path: | ~/.cargo/registry ~/.cargo/git target key: "${{ matrix.cargo_command }} ${{ matrix.args }} ${{ matrix.package }}" - name: Download Fuel Core if: ${{ matrix.install_fuel_core }} uses: actions/download-artifact@v4 with: name: fuel-core - name: Install Fuel Core if: ${{ matrix.install_fuel_core }} run: | chmod +x fuel-core mv fuel-core /usr/local/bin/fuel-core - name: Download sway example artifacts if: ${{ matrix.download_sway_artifacts }} uses: actions/download-artifact@v4 with: name: ${{ matrix.download_sway_artifacts }} # Needed because `upload-artifact` will remove 'e2e/sway' because it is shared between all matched files path: e2e/sway/ - name: Install nextest if: ${{ matrix.cargo_command == 'nextest' }} uses: taiki-e/install-action@nextest - name: Install cargo-machete if: ${{ matrix.cargo_command == 'machete' }} uses: taiki-e/install-action@cargo-machete - name: Cargo (workspace-level) if: ${{ matrix.cargo_command && !matrix.package }} run: cargo ${{ matrix.cargo_command }} ${{ matrix.args }} - name: Cargo (package-level) if: ${{ matrix.cargo_command && matrix.package }} run: cargo ${{ matrix.cargo_command }} -p ${{ matrix.package }} ${{ matrix.args }} - name: Install NodeJS for WASM testing if: ${{ matrix.command == 'test_wasm' }} uses: actions/setup-node@v3 with: node-version: 22.8.0 # Until we fix the "missing env error" # - name: Test WASM # if: ${{ matrix.command == 'test_wasm' }} # run: | # curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh # cd wasm-tests # wasm-pack test --node - name: Check that fuel_core version.rs file is up to date if: ${{ matrix.command == 'check_fuel_core_version' }} run: cargo run --bin fuel-core-version -- --manifest-path ./Cargo.toml verify - name: Check for invalid documentation anchors if: ${{ matrix.command == 'check_doc_anchors_valid' }} run: cargo run --bin check-docs - name: Check for unresolved documentation links if: ${{ matrix.command == 'check_doc_unresolved_links' }} run: | ! cargo doc --document-private-items |& grep -A 6 "warning: unresolved link to" - name: Check for typos if: ${{ matrix.command == 'check_typos' }} uses: crate-ci/typos@v1.29.5 publish: needs: - cargo-verifications - publish-crates-check # Only do this job if publishing a release if: github.event_name == 'release' && github.event.action == 'published' runs-on: buildjet-4vcpu-ubuntu-2204 steps: - name: Checkout repository uses: actions/checkout@v3 - uses: dtolnay/rust-toolchain@master with: toolchain: ${{ env.RUST_VERSION }} - name: Verify tag version run: | curl -sSLf "$DASEL_VERSION" -L -o dasel && chmod +x dasel mv ./dasel /usr/local/bin/dasel ./.github/workflows/scripts/verify_tag.sh ${{ github.ref_name }} Cargo.toml - name: Publish crate uses: FuelLabs/publish-crates@v1 with: publish-delay: 30000 registry-token: ${{ secrets.CARGO_REGISTRY_TOKEN }} ================================================ FILE: .github/workflows/docs.yml ================================================ name: Docs on: push: branches: - master pull_request: release: types: [published] jobs: test: uses: FuelLabs/github-actions/.github/workflows/mdbook-docs.yml@master with: docs-src-path: "docs/src" pre-command: 'cargo run --package versions-replacer -- ./docs --manifest-path ./Cargo.toml --filename-regex "\.md$"' spellcheck-config-path: 'docs/.spellcheck.yml' ================================================ FILE: .github/workflows/gh-pages.yml ================================================ name: github pages on: push: branches: - master tags: - v* jobs: deploy: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - name: Setup mdBook uses: peaceiris/actions-mdbook@v1 with: mdbook-version: "0.4.15" - run: mdbook build docs - name: Deploy master uses: peaceiris/actions-gh-pages@v3 with: github_token: ${{ secrets.GITHUB_TOKEN }} publish_dir: ./docs/book destination_dir: master cname: rust.fuel.network if: github.ref == 'refs/heads/master' - name: Get tag id: branch_name run: | echo ::set-output name=BRANCH_NAME::${GITHUB_REF#refs/tags/} if: startsWith(github.ref, 'refs/tags') - name: Deploy tag uses: peaceiris/actions-gh-pages@v3 with: github_token: ${{ secrets.GITHUB_TOKEN }} publish_dir: ./docs/book destination_dir: ${{ steps.branch_name.outputs.BRANCH_NAME }} cname: rust.fuel.network if: startsWith(github.ref, 'refs/tags') - name: Create latest HTML redirect file if: startsWith(github.ref, 'refs/tags') run: | mkdir ./latest cat > ./latest/index.html < EOF - name: Set latest to point to tag uses: peaceiris/actions-gh-pages@v3 with: github_token: ${{ secrets.GITHUB_TOKEN }} publish_dir: ./latest/ destination_dir: ./latest/ cname: rust.fuel.network if: startsWith(github.ref, 'refs/tags') ================================================ FILE: .github/workflows/repo-plan.toml ================================================ [current-repo] name = "fuels-rs" owner = "FuelLabs" [repo.fuels-rs.details] name = "fuels-rs" owner = "FuelLabs" [repo.fuel-core.details] name = "fuel-core" owner = "FuelLabs" [repo.fuels-rs] dependencies = ["fuel-core"] ================================================ FILE: .github/workflows/scripts/verify_tag.sh ================================================ #!/usr/bin/env bash set -e err() { echo -e "\e[31m\e[1merror:\e[0m $@" 1>&2; } status() { WIDTH=12 printf "\e[32m\e[1m%${WIDTH}s\e[0m %s\n" "$1" "$2" } REF=$1 MANIFEST=$2 if [ -z "$REF" ]; then err "Expected ref to be set" exit 1 fi if [ -z "$MANIFEST" ]; then err "Expected manifest to be set" exit 1 fi # strip preceding 'v' if it exists on tag REF=${REF/#v} TOML_VERSION=$(cat $MANIFEST | dasel -r toml -w plain 'workspace.package.version') if [ "$TOML_VERSION" != "$REF" ]; then err "Crate version $TOML_VERSION, doesn't match tag version $REF" exit 1 else status "Crate version matches tag $TOML_VERSION" fi ================================================ FILE: .gitignore ================================================ # Generated by Cargo # will have compiled files and executables target/ # Remove Cargo.lock from gitignore if creating an executable, leave it for libraries # More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html Cargo.lock # These are backup files generated by rustfmt **/*.rs.bk # Don't add the generated MDBook artifacts docs/book/ # Don't add Forc lock files **/Forc.lock # Don't add out/ files from test Sway projects. e2e/sway/**/out/ e2e/sway/**/.gitignore .env output_changelog.md ================================================ FILE: .markdownlint.yaml ================================================ "default": true # Default state for all rules "MD013": false # Disable rule for line length "MD033": false # Disable rule banning inline HTML ================================================ FILE: .markdownlintignore ================================================ README.md scripts/check-docs ================================================ FILE: Cargo.toml ================================================ [workspace] # Use the new resolver to prevent dev-deps and build-deps from enabling debugging or test features in production. # # > If you are using a virtual workspace, you will still need to explicitly set the resolver field in the [workspace] # definition if you want to opt-in to the new resolver. # https://doc.rust-lang.org/edition-guide/rust-2021/default-cargo-resolver.html#details resolver = "2" members = [ "e2e", "examples/codec", "examples/contracts", "examples/cookbook", "examples/debugging", "examples/macros", "examples/predicates", "examples/providers", "examples/rust_bindings", "examples/types", "examples/wallets", "packages/fuels", "packages/fuels-accounts", "packages/fuels-code-gen", "packages/fuels-core", "packages/fuels-macros", "packages/fuels-programs", "packages/fuels-test-helpers", "scripts/change-log", "scripts/check-docs", "scripts/fuel-core-version", "scripts/versions-replacer", "wasm-tests", ] [workspace.package] authors = ["Fuel Labs "] edition = "2024" homepage = "https://fuel.network/" readme = "README.md" license = "Apache-2.0" repository = "https://github.com/FuelLabs/fuels-rs" rust-version = "1.93.0" version = "0.76.0" [workspace.dependencies] Inflector = "0.11.4" anyhow = { version = "1.0", default-features = false } dialoguer = { version = "0.11", default-features = false } async-trait = { version = "0.1.74", default-features = false } bytes = { version = "1.5.0", default-features = false } chrono = "0.4.31" cynic = { version = ">=3.1.0, <3.13.0", default-features = false } test-case = { version = "3.3", default-features = false } eth-keystore = "0.5.0" flate2 = { version = "1.0", default-features = false } fuel-abi-types = "0.15.3" futures = "0.3.29" hex = { version = "0.4.3", default-features = false } itertools = "0.12.0" portpicker = "0.1.1" pretty_assertions = { version = "1.4", default-features = false } proc-macro2 = "1.0.70" quote = "1.0.33" rand = { version = "0.8.5", default-features = false, features = [ "std_rng", "getrandom", ] } regex = "1.10.2" reqwest = { version = "0.12", default-features = false } semver = "1.0.20" serde = { version = "1.0.193", default-features = false } serde_json = "1.0.108" serde_with = { version = "3.4.0", default-features = false } auto_impl = { version = "1.0", default-features = false } sha2 = { version = "0.10.8", default-features = false } syn = "2.0.39" tai64 = { version = "4.0.0", default-features = false } tar = { version = "0.4", default-features = false } tempfile = { version = "3.8.1", default-features = false } thiserror = { version = "1.0.50", default-features = false } tokio = { version = "1.34.0", default-features = false } tracing = "0.1.40" trybuild = "1.0.85" uint = { version = "0.9.5", default-features = false } which = { version = "6.0.0", default-features = false } zeroize = "1.7.0" octocrab = { version = "0.43", default-features = false } dotenv = { version = "0.15", default-features = false } toml = { version = "0.8", default-features = false } mockall = { version = "0.13", default-features = false } google-cloud-kms = { version = "0.6", default-features = false } aws-config = { version = "1", default-features = false } aws-sdk-kms = { version = "1", default-features = false } testcontainers = { version = "0.23", default-features = false } k256 = { version = "0.13", default-features = false } # Dependencies from the `fuel-core` repository: fuel-core = { version = "0.47.1", default-features = false, features = [ "wasm-executor", ] } fuel-core-chain-config = { version = "0.47.1", default-features = false } fuel-core-client = { version = "0.47.1", default-features = false } fuel-core-poa = { version = "0.47.1", default-features = false } fuel-core-services = { version = "0.47.1", default-features = false } fuel-core-types = { version = "0.47.1", default-features = false } # Dependencies from the `fuel-vm` repository: fuel-asm = { version = "0.65.0" } fuel-crypto = { version = "0.65.0" } fuel-merkle = { version = "0.65.0" } fuel-storage = { version = "0.65.0" } fuel-tx = { version = "0.65.0" } fuel-types = { version = "0.65.0" } fuel-vm = { version = "0.65.0" } # Workspace projects fuels = { version = "0.76.0", path = "./packages/fuels", default-features = false } fuels-accounts = { version = "0.76.0", path = "./packages/fuels-accounts", default-features = false } fuels-code-gen = { version = "0.76.0", path = "./packages/fuels-code-gen", default-features = false } fuels-core = { version = "0.76.0", path = "./packages/fuels-core", default-features = false } fuels-macros = { version = "0.76.0", path = "./packages/fuels-macros", default-features = false } fuels-programs = { version = "0.76.0", path = "./packages/fuels-programs", default-features = false } fuels-test-helpers = { version = "0.76.0", path = "./packages/fuels-test-helpers", default-features = false } versions-replacer = { version = "0.76.0", path = "./scripts/versions-replacer", default-features = false } ================================================ FILE: LICENSE ================================================ Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright [yyyy] [name of copyright owner] Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. ================================================ FILE: README.md ================================================ # fuels-rs [![build](https://github.com/FuelLabs/fuels-rs/actions/workflows/ci.yml/badge.svg)](https://github.com/FuelLabs/fuels-rs/actions/workflows/ci.yml) [![crates.io](https://img.shields.io/crates/v/fuels?label=latest)](https://crates.io/crates/fuels) [![docs](https://docs.rs/fuels/badge.svg)](https://docs.rs/fuels) [![discord](https://img.shields.io/badge/chat%20on-discord-orange?&logo=discord&logoColor=ffffff&color=7389D8&labelColor=6A7EC2)](https://discord.gg/xfpK4Pe) Rust SDK for Fuel. It can be used for a variety of things, including but not limited to: - Compiling, deploying, and testing [Sway](https://github.com/FuelLabs/sway) contracts; - Launching a local Fuel network; - Crafting and signing transactions with hand-crafted scripts or contract calls; - Generating type-safe Rust bindings of contract methods; - And more, `fuels-rs` is still in active development. ## Documentation See [the `fuels-rs` book](https://fuellabs.github.io/fuels-rs/latest/) ## Features - [x] Launch Fuel nodes - [x] Deploy contracts - [x] Interact with deployed contracts - [x] Type-safe Sway contracts bindings code generation - [x] Run Sway scripts - [x] CLI for common operations - [x] Local test wallets - [ ] Wallet integration - [ ] Events querying/monitoring ## FAQ ### What dependencies do I need? - [The latest `stable` Rust toolchain](https://docs.fuel.network/guides/installation/#installing-rust); - [`forc` and `fuel-core` binaries](https://docs.fuel.network/guides/installation/#installing-the-fuel-toolchain-using-fuelup). ### How can I run the SDK tests? First, build the test projects using `forc`: ```shell forc build --release --path e2e ``` Then you can run the SDK tests with: ```shell cargo test ``` You can also run specific tests. The following example will run all integration tests in `types.rs` whose names contain `in_vector` and show their outputs: ```shell cargo test --test types in_vector -- --show-output ``` ### How to run WASM tests? You need to have wasm32 as a target, if you don't already: ```shell rustup target add wasm32-unknown-unknown ``` You also need `wasm-pack`, if you don't already: ```shell cargo install wasm-pack ``` Navigate to `packages/wasm-tests` and run `wasm-pack test`. ### What to do if my tests are failing on `master` Before doing anything else, try all these commands: ```shell cargo clean rm Cargo.lock forc build --release --path e2e cargo test ``` ### Why is the prefix `fuels` and not `fuel`? In order to make the SDK for Fuel feel familiar with those coming from the [ethers.js](https://github.com/ethers-io/ethers.js) ecosystem, this project opted for an `s` at the end. The `fuels-*` family of SDKs is inspired by The Ethers Project. ### How can I run the docs locally? Install `mdbook` by running: ```shell cargo install mdbook ``` Next, navigate to the `docs` folder and run the command below to start a local server and open a new tab in your browser. ```shell mdbook serve --open ``` You can build the book by running: ```shell mdbook build ``` ================================================ FILE: SECURITY.md ================================================ # Fuel Security Policy Thank you for helping make the Fuel ecosystem safe for everyone. The Fuel team take security bugs very seriously. We appreciate your efforts to responsibly disclose your findings, and will make every effort to acknowledge your contributions. ## Reporting Security Issues If you believe you have found a security vulnerability in any Fuel-owned repository, please report it to us through coordinated disclosure. **Please do not report security vulnerabilities through public GitHub issues, discussions, or pull requests.** Instead, please use the GitHub Security Advisory ["Report a Vulnerability"](https://github.com/FuelLabs/fuels-rs/security/advisories/new) tab. The Fuel team will send a response indicating the next steps in handling your report. After the initial reply to your report, the team will keep you informed of the progress towards a fix and full announcement, and may ask for additional information or guidance. Please include as much of the information listed below as you can to help us better understand and resolve the issue: * The type of issue (e.g., buffer overflow, SQL injection, or cross-site scripting) * Full paths of source file(s) related to the manifestation of the issue * The location of the affected source code (tag/branch/commit or direct URL) * Any special configuration required to reproduce the issue * Step-by-step instructions to reproduce the issue * Proof-of-concept or exploit code (if possible) * Impact of the issue, including how an attacker might exploit the issue This information will help us triage your report more quickly. Report security bugs in third-party modules to the person or team maintaining the module. ## Non-Security Issues If the issue is not security-related, please report it publicly by opening a [GitHub Issue](https://github.com/FuelLabs/fuels-rs/issues/new). ================================================ FILE: _typos.toml ================================================ [files] extend-exclude = ["packages/fuels-accounts/src/schema/schema.sdl"] ================================================ FILE: ci_checks.sh ================================================ #!/usr/bin/env bash # Requires installed: # The latest version of the `forc`,`forc-fmt` and `fuel-core`. # `cargo install fuel-core-bin --git https://github.com/FuelLabs/fuel-core --tag v0.18.1 --locked` # `cargo install forc --git https://github.com/FuelLabs/sway --tag v0.38.0 --locked` # `cargo install forc-fmt --git https://github.com/FuelLabs/sway --tag v0.38.0 --locked` # Note, if you need a custom branch, you can replace `--tag {RELEASE}` with the `--branch {BRANCH_NAME}`. cargo fmt --all -- --check && forc fmt --check --path e2e && forc build --release --terse --path e2e && cargo clippy --all-targets && forc build --release --terse --path e2e && cargo clippy --all-targets --all-features && cargo test --all-targets --all-features && cargo test --all-targets --all-features --workspace && cargo test --all-targets --workspace && cargo run --bin check-docs && $(cargo doc |& grep -A 6 "warning: unresolved link to") ================================================ FILE: docs/.spellcheck.yml ================================================ matrix: - name: SPCheck aspell: lang: en dictionary: encoding: utf-8 wordlists: - docs/spell-check-custom-words.txt pipeline: - pyspelling.filters.context: context_visible_first: true escapes: \\[\\`~] delimiters: # Ignore all code blocks - open: '(?s)^(?P *`{3,}\s*(\w+\s*,?\s*)+.*?)$' close: '^( *`{3,})$' - pyspelling.filters.markdown: markdown_extensions: - pymdownx.superfences: - pyspelling.filters.html: comments: false ignores: - code - pre sources: - 'docs/*.md' - 'docs/src/*.md' - 'docs/src/**/*.md' default_encoding: utf-8 ================================================ FILE: docs/book.toml ================================================ [book] authors = ["Fuel Labs "] language = "en" multilingual = false src = "src" title = "The Fuel Rust SDK" [output.html] git-repository-url = "https://github.com/FuelLabs/fuels-rs" [rust] edition = "2021" ================================================ FILE: docs/spell-check-custom-words.txt ================================================ ABI ABIs ALU APIs ASM AST AssemblyScript Bitwise Booleans BrowserStack CEI CLI Cardinality Changelog Codec Collateralized DApp DCA DEX DOT DSL Deserialization Dockerized EOA ERC ETH EVM EVM's Ethereum Ethereum's FVM FuelLabs FuelVM FuelVM's Fuelup GitHub Github GraphQL GraphViz HD Homebrew IDE IDEs IPFS IaaS Infura Intrinsics JSON JWT KMS Keystore Kms LSP MacOS Macbook Merkle MiB Orchestrator PRs PoA PoS PoW PostgreSQL Postgres RPC RocksDB SDK SDK's SDKs SHA SQLx SRC SauceLabs Schemas Sepolia Structs Subcommands Subcurrency SuperABIs Supertraits Sway TAI TLDR TODO TOML TypeChain TypeScript UI UTF UTXO UTXOs Updatable Utils VM VMs VS VSCode WASM WebAssembly Workspaces YAML abigen args arity async backend backends backtrace backtraces blockchain blockchain's blockchains boolean bytecode calldata codebase codec codespace codespaces collateralized compositional config configurables cryptographic cryptographically customizable customizations dApp dApps dapp decrypt deployable dereference dereferenced dereferencing deserializing destructuring deterministically dev dropdown entrancy enum enums env forc formatter frontend fuelup fullstack graphQL graphql growable hoc http https incrementor indexable initializer initializers inlines inlining instantiation interoperable intrinsics js keccak kms localhost lookups macOS mainnet mempool merkle monomorphization monorepo monorepos namespace namespaces natively neovim npm nvm onboarding orchestrator params pnpm pre prerelease queryable quickstart relayer relayers repo repos roadmap runnable runtime runtimes schemas semver stateful struct struct's structs subcommand subcommands subcurrency submodule superABI superABIs superset supertrait supertraits testnet testnets toolchain toolchains tuple's turbofish typeclass unary urql validator validators workspace workspaces ================================================ FILE: docs/src/SUMMARY.md ================================================ # Summary [The Fuel Rust SDK](./index.md) - [Getting Started](./getting-started.md) - [Connecting to a Fuel node](./connecting/index.md) - [Connecting to the Testnet or an external node](./connecting/external-node.md) - [Running a short-lived Fuel node with the SDK](./connecting/short-lived.md) - [RocksDB](./connecting/rocksdb.md) - [Querying the blockchain](./connecting/querying.md) - [Retrying upon errors](./connecting/retrying.md) - [Accounts](./accounts.md) - [Managing wallets](./wallets/index.md) - [Using private keys](./wallets/private_key_signer.md) - [Keystore](./wallets/keystore.md) - [Using KMS](./wallets/kms.md) - [Using a fake signer/impersonating an account](./wallets/fake_signer.md) - [Querying](./wallets/checking-balances-and-coins.md) - [Testing](./wallets/test-wallets.md) - [Locking](./wallets/access.md) - [Signing](./wallets/signing.md) - [Generating bindings with `abigen!`](./abigen/index.md) - [The JSON ABI file](abigen/the-json-abi-file.md) - [The `abigen!` macro](abigen/the-abigen-macro.md) - [Deploying contracts](./deploying/index.md) - [Configurable constants](./deploying/configurable-constants.md) - [Storage slots](./deploying/storage-slots.md) - [Interacting with contracts](./deploying/interacting-with-contracts.md) - [The FuelVM Binary file](./deploying/the-fuelvm-binary-file.md) - [Large contracts](./deploying/large_contracts.md) - [Calling contracts](./calling-contracts/index.md) - [Connecting wallets](./calling-contracts/calls-with-different-wallets.md) - [Transaction policies](./calling-contracts/tx-policies.md) - [Call parameters](./calling-contracts/call-params.md) - [Custom asset transfer](./calling-contracts/custom-asset-transfer.md) - [Custom inputs and outputs](./calling-contracts/custom-inputs-outputs.md) - [Call response](./calling-contracts/call-response.md) - [Logs](./calling-contracts/logs.md) - [Variable outputs and messages](./calling-contracts/variable-outputs.md) - [Simulating calls](./calling-contracts/simulation.md) - [Calling other contracts](./calling-contracts/other-contracts.md) - [Multiple contract calls](./calling-contracts/multicalls.md) - [Transaction dependency estimation](./calling-contracts/tx-dependency-estimation.md) - [Estimating cost](./calling-contracts/cost-estimation.md) - [Low-level calls](./calling-contracts/low-level-calls.md) - [Running scripts](./running-scripts.md) - [Predicates](./predicates/index.md) - [Signatures example](./predicates/send-spend-predicate.md) - [Pre-uploading code](./preuploading-code.md) - [Custom transactions](./custom-transactions/index.md) - [Transaction builders](./custom-transactions/transaction-builders.md) - [Custom contract and script calls](./custom-transactions/custom-calls.md) - [Types](./types/index.md) - [`Bytes32`](./types/bytes32.md) - [`Address`](./types/address.md) - [`ContractId`](./types/contract-id.md) - [`AssetId`](./types/asset-id.md) - [Structs and enums](./types/custom_types.md) - [`String`](./types/string.md) - [`Bits256`](./types/bits256.md) - [`Bytes`](./types/bytes.md) - [`B512`](./types/B512.md) - [`EvmAddress`](./types/evm_address.md) - [Vectors](./types/vectors.md) - [Converting types](./types/conversion.md) - [Codec](./codec/index.md) - [Encoding](./codec/encoding.md) - [Decoding](./codec/decoding.md) - [API Reference](./reference.md) - [Testing](./testing/index.md) - [Testing basics](./testing/basics.md) - [The `setup_program_test!` macro](testing/the-setup-program-test-macro.md) - [Tweaking the blockchain](./testing/chains.md) - [Cookbook](./cookbook/index.md) - [Custom consensus parameters](./cookbook/custom-chain.md) - [Deposit and Withdraw](./cookbook/deposit-and-withdraw.md) - [Transfer all assets](./cookbook/transfer-all-assets.md) - [Debugging](./debugging/index.md) - [The Function selector](./debugging/function-selector.md) - [Decoding script transactions](./debugging/decoding-script-transactions.md) - [Glossary](./glossary.md) - [Contributing](./contributing/CONTRIBUTING.md) - [Integration tests structure](./contributing/tests-structure.md) - [Command Line Interfaces](./cli/index.md) - [`fuels-abi-cli`](./cli/fuels-abi-cli.md) ================================================ FILE: docs/src/abigen/index.md ================================================ # Generating bindings with abigen You might have noticed this snippet in the previous sections: ```rust,ignore {{#include ../../../examples/contracts/src/lib.rs:abigen_example}} ``` The SDK lets you transform ABI methods of a smart contract, specified as JSON objects (which you can get from [Forc](https://github.com/FuelLabs/sway/tree/master/forc)), into Rust structs and methods that are type-checked at compile time. In order to call your contracts, scripts or predicates, you first need to generate the Rust bindings for them. The following subsections contain more details about the `abigen!` syntax and the code generated from it. ================================================ FILE: docs/src/abigen/the-abigen-macro.md ================================================ # abigen `abigen!` is a procedural macro -- it generates code. It accepts inputs in the format of: ```text ProgramType(name="MyProgramType", abi="my_program-abi.json")... ``` where: - `ProgramType` is one of: `Contract`, `Script` or `Predicate`, - `name` is the name that will be given to the generated bindings, - `abi` is either a path to the JSON ABI file or its actual contents. --- So, an `abigen!` which generates bindings for two contracts and one script looks like this: ```rust,ignore {{#include ../../../examples/macros/src/lib.rs:multiple_abigen_program_types}} ``` ## How does the generated code look? A rough overview: ```rust,ignore pub mod abigen_bindings { pub mod contract_a_mod { struct SomeCustomStruct{/*...*/}; // other custom types used in the contract struct ContractA {/*...*/}; impl ContractA {/*...*/}; // ... } pub mod contract_b_mod { // ... } pub mod my_script_mod { // ... } pub mod my_predicate_mod{ // ... } pub mod shared_types{ // ... } } pub use contract_a_mod::{/*..*/}; pub use contract_b_mod::{/*..*/}; pub use my_predicate_mod::{/*..*/}; pub use shared_types::{/*..*/}; ``` Each `ProgramType` gets its own `mod` based on the `name` given in the `abigen!`. Inside the respective mods, the custom types used by that program are generated, and the bindings through which the actual calls can be made. One extra `mod` called `shared_types` is generated if `abigen!` detects that the given programs share types. Instead of each `mod` regenerating the type for itself, the type is lifted out into the `shared_types` module, generated only once, and then shared between all program bindings that use it. Reexports are added to each mod so that even if a type is deemed shared, you can still access it as though each `mod` had generated the type for itself (i.e. `my_contract_mod::SharedType`). A type is deemed shared if its name and definition match up. This can happen either because you've used the same library (a custom one or a type from the `stdlib`) or because you've happened to define the exact same type. Finally, `pub use` statements are inserted, so you don't have to fully qualify the generated types. To avoid conflict, only types that have unique names will get a `pub use` statement. If you find `rustc` can't find your type, it might just be that there is another generated type with the same name. To fix the issue just qualify the path by doing `abigen_bindings::whatever_contract_mod::TheType`. > **Note:** > It is **highly** encouraged that you generate all your bindings in one `abigen!` call. Doing it in this manner will allow type sharing and avoid name collisions you'd normally get when calling `abigen!` multiple times inside the same namespace. If you choose to proceed otherwise, keep in mind the generated code overview presented above and appropriately separate the `abigen!` calls into different modules to resolve the collision. ## Using the bindings Let's look at a contract with two methods: `initialize_counter(arg: u64) -> u64` and `increment_counter(arg: u64) -> u64`, with the following JSON ABI: ```json,ignore {{#include ../../../examples/rust_bindings/src/abi.json}} ``` By doing this: ```rust,ignore {{#include ../../../examples/rust_bindings/src/lib.rs:use_abigen}} ``` or this: ```rust,ignore {{#include ../../../examples/rust_bindings/src/lib.rs:abigen_with_string}} ``` you'll generate this (shortened for brevity's sake): ```rust,ignore {{#include ../../../examples/rust_bindings/src/rust_bindings_formatted.rs}} ``` > **Note:** that is all **generated** code. No need to write any of that. Ever. The generated code might look different from one version to another, this is just an example to give you an idea of what it looks like. Then, you're able to use it to call the actual methods on the deployed contract: ```rust,ignore {{#include ../../../examples/contracts/src/lib.rs:use_deployed_contract}} ``` ================================================ FILE: docs/src/abigen/the-json-abi-file.md ================================================ # The JSON ABI file Whether you want to deploy or connect to a pre-existing smart contract, the JSON ABI file is extremely important: it's what tells the SDK about the [ABI methods](https://docs.fuel.network/guides/quickstart/building-a-smart-contract/#abi) in your smart contracts. For the same example Sway code as above: ```Rust contract; abi MyContract { fn test_function() -> bool; } impl MyContract for Contract { fn test_function() -> bool { true } } ``` The JSON ABI file looks like this: ```json $ cat out/release/my-test-abi.json [ { "type": "function", "inputs": [], "name": "test_function", "outputs": [ { "name": "", "type": "bool", "components": null } ] } ] ``` The Fuel Rust SDK will take this file as input and generate equivalent methods (and custom types if applicable) that you can call from your Rust code. ================================================ FILE: docs/src/accounts.md ================================================ # Accounts The `ViewOnlyAccount` trait provides a common interface to query balances. The `Account` trait, in addition to the above, also provides a way to transfer assets. When performing actions in the SDK that lead to a transaction, you will typically need to provide an account that will be used to allocate resources required by the transaction, including transaction fees. The traits are implemented by the following types: - [`Wallet`](./wallets/index.md) - [`Predicate`](./predicates/index.md) ## Transferring assets An account implements the following methods for transferring assets: - `transfer` - `force_transfer_to_contract` - `withdraw_to_base_layer` The following examples are provided for a `Wallet` account. A `Predicate` account would work similarly, but you might need to set its predicate data before attempting to spend resources owned by it. With `wallet.transfer` you can initiate a transaction to transfer an asset from your account to a target address. ```rust,ignore {{#include ../../examples/wallets/src/lib.rs:wallet_transfer}} ``` You can transfer assets to a contract via `wallet.force_transfer_to_contract`. ```rust,ignore {{#include ../../examples/wallets/src/lib.rs:wallet_contract_transfer}} ``` For transferring assets to the base layer chain, you can use `wallet.withdraw_to_base_layer`. ```rust,ignore {{#include ../../examples/wallets/src/lib.rs:wallet_withdraw_to_base}} ``` The above example creates an `Address` from a string. Next, it calls `wallet.withdraw_to_base_layer` by providing the address, the amount to be transferred, and the transaction policies. Lastly, to verify that the transfer succeeded, the relevant message proof is retrieved with `provider.get_message_proof,` and the amount and the recipient are verified. ================================================ FILE: docs/src/calling-contracts/call-params.md ================================================ # Call parameters The parameters for a contract call are: 1. Amount 2. Asset ID 3. Gas forwarded You can use these to forward coins to a contract. You can configure these parameters by creating an instance of [`CallParameters`](https://docs.rs/fuels/latest/fuels/programs/calls/struct.CallParameters.html) and passing it to a chain method called `call_params`. For instance, suppose the following contract that uses Sway's `msg_amount()` to return the amount sent in that transaction. ```rust,ignore {{#include ../../../e2e/sway/contracts/contract_test/src/main.sw:msg_amount}} ``` Then, in Rust, after setting up and deploying the above contract, you can configure the amount being sent in the transaction like this: ```rust,ignore {{#include ../../../examples/contracts/src/lib.rs:call_parameters}} ``` `call_params` returns a result to ensure you don't forward assets to a contract method that isn't payable. In the following example, we try to forward an amount of `100` of the base asset to `non_payable`. As its name suggests, `non_payable` isn't annotated with `#[payable]` in the contract code. Passing `CallParameters` with an amount other than `0` leads to an error: ```rust,ignore {{#include ../../../e2e/tests/contracts.rs:non_payable_params}} ``` > **Note:** forwarding gas to a contract call is always possible, regardless of the contract method being non-payable. You can also use `CallParameters::default()` to use the default values: ```rust,ignore {{#include ../../../packages/fuels-core/src/utils/constants.rs:default_call_parameters}} ``` This way: ```rust,ignore {{#include ../../../examples/contracts/src/lib.rs:call_parameters_default}} ``` The `gas_forwarded` parameter defines the limit for the actual contract call as opposed to the gas limit for the whole transaction. This means that it is constrained by the transaction limit. If it is set to an amount greater than the available gas, all available gas will be forwarded. ```rust,ignore {{#include ../../../examples/contracts/src/lib.rs:call_params_gas}} ``` If you don't set the call parameters or use `CallParameters::default()`, the transaction gas limit will be forwarded instead. ================================================ FILE: docs/src/calling-contracts/call-response.md ================================================ # Call response You've probably noticed that you're often chaining `.call().await.unwrap()`. That's because: 1. You have to choose between `.call()` and `.simulate()` (more on this in the next section). 2. Contract calls are asynchronous, so you can choose to either `.await` it or perform concurrent tasks, making full use of Rust's async. 3. `.unwrap()` the `Result` returned by the contract call. Once you unwrap the `CallResponse`, you have access to this struct: ```rust,ignore {{#include ../../../packages/fuels-programs/src/responses/call.rs:call_response}} ``` Where `value` will hold the value returned by its respective contract method, represented by the exact type returned by the FuelVM, E.g., if your contract returns a FuelVM's `u64`, `value`'s `D` will be a `u64`. If it's a FuelVM's tuple `(u8,bool)`, then `D` will be a `(u8,bool)`. If it's a custom type, for instance, a Sway struct `MyStruct` containing two components, a `u64`, and a `b256`, `D` will be a struct generated at compile-time, called `MyStruct` with `u64` and a `[u8; 32]` (the equivalent of `b256` in Rust). - `receipts` will hold all [receipts](https://docs.fuel.network/docs/specs/abi/receipts/) generated by that specific contract call. - `gas_used` is the amount of gas consumed by the contract call. - `tx_id` will hold the ID of the corresponding submitted transaction. ## Error handling You can use the `is_ok` and `is_err` methods to check if a contract call `Result` is `Ok` or contains an error. These methods will return either `true` or `false`. ```rust, ignore let is_ok = response.is_ok(); let is_error = response.is_err(); ``` If `is_err` returns `true`, you can use the `unwrap_err` method to unwrap the error message. ```rust, ignore if response.is_err() { let err = response.unwrap_err(); println!("ERROR: {:?}", err); }; ``` ================================================ FILE: docs/src/calling-contracts/calls-with-different-wallets.md ================================================ # Calls with different wallets You can use the `with_account()` method on an existing contract instance as a shorthand for creating a new instance connected to the provided wallet. This lets you make contracts calls with different wallets in a chain like fashion. ```rust,ignore {{#include ../../../examples/contracts/src/lib.rs:connect_wallet}} ``` > **Note:** connecting a different wallet to an existing instance ignores its set provider in favor of the provider used to deploy the contract. If you have two wallets connected to separate providers (each communicating with a separate fuel-core), the one assigned to the deploying wallet will also be used for contract calls. This behavior is only relevant if multiple providers (i.e. fuel-core instances) are present and can otherwise be ignored. ================================================ FILE: docs/src/calling-contracts/cost-estimation.md ================================================ # Estimating contract call cost With the function `estimate_transaction_cost(tolerance: Option, block_horizon: Option)` provided by `CallHandler`, you can get a cost estimation for a specific call. The return type, `TransactionCost`, is a struct that contains relevant information for the estimation: ```rust,ignore {{#include ../../../packages/fuels-accounts/src/provider.rs:transaction_cost}} ``` > **Note** `script_gas` refers to the part of the gas spent on the script execution. Below are examples that show how to get the estimated transaction cost from single and multi call transactions. ```rust,ignore {{#include ../../../examples/contracts/src/lib.rs:contract_call_cost_estimation}} ``` ```rust,ignore {{#include ../../../examples/contracts/src/lib.rs:multi_call_cost_estimation}} ``` The transaction cost estimation can be used to set the gas limit for an actual call, or to show the user the estimated cost. > **Note** The same estimation interface is available for scripts. ================================================ FILE: docs/src/calling-contracts/custom-asset-transfer.md ================================================ # Custom asset transfer The SDK provides the option to transfer assets within the same transaction, when making a contract call. By using `add_custom_asset()` you specify the asset ID, the amount, and the destination address: ```rust,ignore {{#include ../../../examples/contracts/src/lib.rs:add_custom_assets}} ``` ================================================ FILE: docs/src/calling-contracts/custom-inputs-outputs.md ================================================ # Custom inputs and outputs If you need to add specific inputs and outputs to contract calls, you can use the `with_inputs` and `with_outputs` methods. ```rust,ignore {{#include ../../../examples/contracts/src/lib.rs:add_custom_inputs_outputs}} ``` > **Note:** if custom inputs include coins that need to be signed, use the `add_signer` method to add the appropriate signer. ================================================ FILE: docs/src/calling-contracts/index.md ================================================ # Calling contracts Once you've deployed your contract, as seen in the previous sections, you'll likely want to: 1. Call contract methods; 2. Configure call parameters and transaction policies; 3. Forward coins and gas in your contract calls; 4. Read and interpret returned values and logs. Here's an example. Suppose your Sway contract has two ABI methods called `initialize_counter(u64)` and `increment_counter(u64)`. Once you've deployed it the contract, you can call these methods like this: ```rust,ignore {{#include ../../../examples/contracts/src/lib.rs:use_deployed_contract}} ``` The example above uses all the default configurations and performs a simple contract call. Furthermore, if you need to separate submission from value retrieval for any reason, you can do so as follows: ```rust,ignore {{#include ../../../examples/contracts/src/lib.rs:submit_response_contract}} ``` Next, we'll see how we can further configure the many different parameters in a contract call. ================================================ FILE: docs/src/calling-contracts/logs.md ================================================ # Logs Whenever you log a value within a contract method, the resulting log entry is added to the log receipt and the variable type is recorded in the contract's ABI. The SDK lets you parse those values into Rust types. Consider the following contract method: ```rust,ignore {{#include ../../../e2e/sway/logs/contract_logs/src/main.sw:produce_logs}} ``` You can access the logged values in Rust by calling `decode_logs_with_type::` from a `CallResponse`, where `T` is the type of the logged variables you want to retrieve. The result will be a `Vec`: ```rust,ignore {{#include ../../../e2e/tests/logs.rs:produce_logs}} ``` You can use the `decode_logs()` function to retrieve a `LogResult` struct containing a `results` field that is a vector of `Result` values representing the success or failure of decoding each log. ```rust, ignore {{#include ../../../e2e/tests/logs.rs:decode_logs}} ``` Due to possible performance hits, it is not recommended to use `decode_logs()` outside of a debugging scenario. > **Note:** String slices cannot be logged directly. Use the `__to_str_array()` function to convert it to a `str[N]` first. ================================================ FILE: docs/src/calling-contracts/low-level-calls.md ================================================ # Low-level calls With low-level calls, you can specify the parameters of your calls at runtime and make indirect calls through other contracts. Your caller contract should call `std::low_level_call::call_with_function_selector`, providing: - target contract ID - function selector encoded as `Bytes` - calldata encoded as `Bytes` - whether the calldata contains only a single value argument (e.g. a `u64`) - `std::low_level_call::CallParams` ```rust,ignore {{#include ../../../e2e/sway/contracts/low_level_caller/src/main.sw:low_level_call_contract}} ``` On the SDK side, you can construct an encoded function selector using `fuels::core::encode_fn_selector`, and encoded calldata using the `fuels::core::calldata!` macro. E.g. to call the following function on the target contract: ```rust,ignore {{#include ../../../e2e/sway/contracts/contract_test/src/main.sw:low_level_call}} ``` you would construct the function selector and the calldata as such, and provide them to the caller contract (like the one above): ```rust,ignore {{#include ../../../examples/contracts/src/lib.rs:low_level_call}} ``` > Note: the `calldata!` macro uses the default `EncoderConfig` configuration under the hood. ================================================ FILE: docs/src/calling-contracts/multicalls.md ================================================ # Multiple contract calls With `CallHandler`, you can execute multiple contract calls within a single transaction. To achieve this, you first prepare all the contract calls that you want to bundle: ```rust,ignore {{#include ../../../examples/contracts/src/lib.rs:multi_call_prepare}} ``` You can also set call parameters, variable outputs, or external contracts for every contract call, as long as you don't execute it with `call()` or `simulate()`. > **Note:** if custom inputs or outputs have been added to the separate calls, the input and output order will follow the order how the calls are added to the multi-call. Next, you provide the prepared calls to your `CallHandler` and optionally configure transaction policies: ```rust,ignore {{#include ../../../examples/contracts/src/lib.rs:multi_call_build}} ``` > **Note:** any transaction policies configured on separate contract calls are disregarded in favor of the parameters provided to the multi-call `CallHandler`. Furthermore, if you need to separate submission from value retrieval for any reason, you can do so as follows: ```rust,ignore {{#include ../../../examples/contracts/src/lib.rs:submit_response_multicontract}} ``` ## Output values To get the output values of the bundled calls, you need to provide explicit type annotations when saving the result of `call()` or `simulate()` to a variable: ```rust,ignore {{#include ../../../examples/contracts/src/lib.rs:multi_call_values}} ``` You can also interact with the `CallResponse` by moving the type annotation to the invoked method: ```rust,ignore {{#include ../../../examples/contracts/src/lib.rs:multi_contract_call_response}} ``` ================================================ FILE: docs/src/calling-contracts/other-contracts.md ================================================ # Calling other contracts If your contract method is calling other contracts you will have to add the appropriate `Inputs` and `Outputs` to your transaction. For your convenience, the `CallHandler` provides methods that prepare those inputs and outputs for you. You have two methods that you can use: `with_contracts(&[&contract_instance, ...])` and `with_contract_ids(&[&contract_id, ...])`. `with_contracts(&[&contract_instance, ...])` requires contract instances that were created using the `abigen` macro. When setting the external contracts with this method, logs and require revert errors originating from the external contract can be propagated and decoded by the calling contract. ```rust,ignore {{#include ../../../e2e/tests/contracts.rs:external_contract}} ``` If however, you do not need to decode logs or you do not have a contract instance that was generated using the `abigen` macro you can use `with_contract_ids(&[&contract_id, ...])` and provide the required contract ids. ```rust,ignore {{#include ../../../e2e/tests/contracts.rs:external_contract_ids}} ``` ================================================ FILE: docs/src/calling-contracts/simulation.md ================================================ # Simulating calls Sometimes you want to simulate a call to a contract without changing the state of the blockchain. This can be achieved by calling `.simulate` instead of `.call` and passing in the desired execution context: * `.simulate(Execution::realistic())` simulates the transaction in a manner that closely resembles a real call. You need a wallet with base assets to cover the transaction cost, even though no funds will be consumed. This is useful for validating that a real call would succeed if made at that moment. It allows you to debug issues with your contract without spending gas. ```rust,ignore {{#include ../../../examples/contracts/src/lib.rs:simulate}} ``` * `.simulate(Execution::state_read_only())` disables many validations, adds fake gas, extra variable outputs, blank witnesses, etc., enabling you to read state even with an account that has no funds. ```rust,ignore {{#include ../../../examples/contracts/src/lib.rs:simulate_read_state}} ``` If the node supports historical execution (the node is using `rocksdb` and the `historical_execution` flag has been set), then both execution types can be chained with `at_height` to simulate the call at a specific block height. ```rust,ignore {{#include ../../../examples/contracts/src/lib.rs:simulate_read_state_at_height}} ``` ================================================ FILE: docs/src/calling-contracts/tx-dependency-estimation.md ================================================ # Transaction dependency estimation Previously, we mentioned that a contract call might require you to manually specify external contracts, variable outputs, or output messages. The SDK can also attempt to estimate and set these dependencies for you at the cost of running multiple simulated calls in the background. The following example uses a contract call that calls an external contract and later mints assets to a specified address. Calling it without including the dependencies will result in a revert: ```rust,ignore {{#include ../../../examples/contracts/src/lib.rs:dependency_estimation_fail}} ``` As mentioned in previous chapters, you can specify the external contract and add an output variable to resolve this: ```rust,ignore {{#include ../../../examples/contracts/src/lib.rs:dependency_estimation_manual}} ``` But this requires you to know the contract ID of the external contract and the needed number of output variables. Alternatively, by chaining - `.with_variable_output_policy(VariableOutputPolicy::EstimateMinimum)` and - `.determine_missing_contracts()` the dependencies will be estimated by the SDK and set automatically. ```rust,ignore {{#include ../../../examples/contracts/src/lib.rs:dependency_estimation}} ``` > **Note:** Both `with_variable_output_policy` and `determine_missing_contracts` can also be used when working with script calls or multi calls. `determine_missing_contracts()` will not enable logging from an external contract. For more information, see [here](./other-contracts.md). ================================================ FILE: docs/src/calling-contracts/tx-policies.md ================================================ # Transaction policies Transaction policies are defined as follows: ```rust,ignore {{#include ../../../packages/fuels-core/src/types/wrappers/transaction.rs:tx_policies_struct}} ``` Where: 1. **Tip** - amount to pay the block producer to prioritize the transaction. 2. **Witness Limit** - The maximum amount of witness data allowed for the transaction. 3. **Maturity** - Block until which the transaction cannot be included. 4. **Expiration** - Block after which the transaction cannot be included. 5. **Max Fee** - The maximum fee payable by this transaction. 6. **Script Gas Limit** - The maximum amount of gas the transaction may consume for executing its script code. When the **Script Gas Limit** is not set, the Rust SDK will estimate the consumed gas in the background and set it as the limit. If the **Witness Limit** is not set, the SDK will set it to the size of all witnesses and signatures defined in the transaction builder. You can configure these parameters by creating an instance of `TxPolicies` and passing it to a chain method called `with_tx_policies`: ```rust,ignore {{#include ../../../examples/contracts/src/lib.rs:tx_policies}} ``` You can also use `TxPolicies::default()` to use the default values. This way: ```rust,ignore {{#include ../../../examples/contracts/src/lib.rs:tx_policies_default}} ``` As you might have noticed, `TxPolicies` can also be specified when deploying contracts or transferring assets by passing it to the respective methods. ================================================ FILE: docs/src/calling-contracts/variable-outputs.md ================================================ # Output variables Sometimes, the contract you call might transfer funds to a specific address, depending on its execution. The underlying transaction for such a contract call has to have the appropriate number of [variable outputs](https://docs.fuel.network/docs/specs/tx-format/output/#outputvariable) to succeed. Let's say you deployed a contract with the following method: ```rust,ignore {{#include ../../../e2e/sway/contracts/token_ops/src/main.sw:variable_outputs}} ``` When calling `transfer_coins_to_output` with the SDK, you can specify the number of variable outputs: ```rust,ignore {{#include ../../../examples/contracts/src/lib.rs:variable_outputs}} ``` `with_variable_output_policy` sets the policy regarding variable outputs. You can either set the number of variable outputs yourself by providing `VariableOutputPolicy::Exactly(n)` or let the SDK estimate it for you with `VariableOutputPolicy::EstimateMinimum`. A variable output indicates that the amount and the owner may vary based on transaction execution. > **Note:** that the Sway `lib-std` function `mint_to_address` calls `transfer_to_address` under the hood, so you need to call `with_variable_output_policy` in the Rust SDK tests like you would for `transfer_to_address`. ================================================ FILE: docs/src/cli/fuels-abi-cli.md ================================================ # `fuels-abi-cli` Simple CLI program to encode Sway function calls and decode their output. The ABI being encoded and decoded is specified [here](https://docs.fuel.network/docs/specs/abi/). ## Usage ```plaintext sway-abi-cli 0.1.0 FuelVM ABI coder USAGE: sway-abi-cli FLAGS: -h, --help Prints help information -V, --version Prints version information SUBCOMMANDS: codegen Output Rust types file decode Decode ABI call result encode Encode ABI call help Prints this message or the help of the given subcommand(s) ``` ## Examples You can choose to encode only the given params or you can go a step further and have a full JSON ABI file and encode the whole input to a certain function call defined in the JSON file. ### Encoding params only ```console $ cargo run -- encode params -v bool true 0000000000000001 ``` ```console $ cargo run -- encode params -v bool true -v u32 42 -v u32 100 0000000000000001000000000000002a0000000000000064 ``` Note that for every parameter you want to encode, you must pass a `-v` flag followed by the type, and then the value: `-v -v -v ` ### Encoding function call `example/simple.json`: ```json [ { "type":"function", "inputs":[ { "name":"arg", "type":"u32" } ], "name":"takes_u32_returns_bool", "outputs":[ { "name":"", "type":"bool" } ] } ] ``` ```console $ cargo run -- encode function examples/simple.json takes_u32_returns_bool -p 4 000000006355e6ee0000000000000004 ``` `example/array.json` ```json [ { "type":"function", "inputs":[ { "name":"arg", "type":"u16[3]" } ], "name":"takes_array", "outputs":[ { "name":"", "type":"u16[2]" } ] } ] ``` ```console $ cargo run -- encode function examples/array.json takes_array -p '[1,2]' 00000000f0b8786400000000000000010000000000000002 ``` Note that the first word (8 bytes) of the output is reserved for the function selector, which is captured in the last 4 bytes, which is simply the 256hash of the function signature. Example with nested struct: ```json [ { "type":"contract", "inputs":[ { "name":"MyNestedStruct", "type":"struct", "components":[ { "name":"x", "type":"u16" }, { "name":"y", "type":"struct", "components":[ { "name":"a", "type":"bool" }, { "name":"b", "type":"u8[2]" } ] } ] } ], "name":"takes_nested_struct", "outputs":[ ] } ] ``` ```console $ cargo run -- encode function examples/nested_struct.json takes_nested_struct -p '(10, (true, [1,2]))' 00000000e8a04d9c000000000000000a000000000000000100000000000000010000000000000002 ``` ### Decoding params only Similar to encoding parameters only: ```console $ cargo run -- decode params -t bool -t u32 -t u32 0000000000000001000000000000002a0000000000000064 Bool(true) U32(42) U32(100) ``` ### Decoding function output ```console $ cargo run -- decode function examples/simple.json takes_u32_returns_bool 0000000000000001 Bool(true) ``` ================================================ FILE: docs/src/cli/index.md ================================================ # `fuels-rs` Rust Workspaces This section gives you a little overview of the role and function of every workspace in the `fuels-rs` repository. - [`fuels-abi-cli`](./fuels-abi-cli.md) ================================================ FILE: docs/src/codec/decoding.md ================================================ # Decoding Be sure to read the [prerequisites](./index.md#prerequisites-for-decodingencoding) to decoding. Decoding is done via the [`ABIDecoder`](https://docs.rs/fuels/latest/fuels/core/codec/struct.ABIDecoder.html): ```rust,ignore {{#include ../../../examples/codec/src/lib.rs:decoding_example}} ``` First into a [`Token`](https://docs.rs/fuels/latest/fuels/types/enum.Token.html), then via the [`Tokenizable`](https://docs.rs/fuels/latest/fuels/core/traits/trait.Tokenizable.html) trait, into the desired type. If the type came from [`abigen!`](../abigen/index.md) (or uses the [`::fuels::macros::TryFrom`](https://docs.rs/fuels/latest/fuels/macros/derive.TryFrom.html) derivation) then you can also use `try_into` to convert bytes into a type that implements both [`Parameterize`](https://docs.rs/fuels/latest/fuels/core/traits/trait.Parameterize.html) and [`Tokenizable`](https://docs.rs/fuels/latest/fuels/core/traits/trait.Tokenizable.html): ```rust,ignore {{#include ../../../examples/codec/src/lib.rs:decoding_example_try_into}} ``` Under the hood, [`try_from_bytes`](https://docs.rs/fuels/latest/fuels/core/codec/fn.try_from_bytes.html) is being called, which does what the preceding example did. ## Configuring the decoder The decoder can be configured to limit its resource expenditure: ```rust,ignore {{#include ../../../examples/codec/src/lib.rs:configuring_the_decoder}} ``` For an explanation of each configuration value visit the `DecoderConfig`. The default values for the `DecoderConfig` are: ```rust,ignore {{#include ../../../packages/fuels-core/src/codec/abi_decoder.rs:default_decoder_config}} ``` ## Configuring the decoder for contract/script calls You can also configure the decoder used to decode the return value of the contract method: ```rust,ignore {{#include ../../../examples/contracts/src/lib.rs:contract_decoder_config}} ``` The same method is available for script calls. ================================================ FILE: docs/src/codec/encoding.md ================================================ # Encoding Be sure to read the [prerequisites](./index.md#prerequisites-for-decodingencoding) to encoding. Encoding is done via the [`ABIEncoder`](https://docs.rs/fuels/latest/fuels/core/codec/struct.ABIEncoder.html): ```rust,ignore {{#include ../../../examples/codec/src/lib.rs:encoding_example}} ``` There is also a shortcut-macro that can encode multiple types which implement [`Tokenizable`](https://docs.rs/fuels/latest/fuels/core/traits/trait.Tokenizable.html): ```rust,ignore {{#include ../../../examples/codec/src/lib.rs:encoding_example_w_macro}} ``` ## Configuring the encoder The encoder can be configured to limit its resource expenditure: ```rust,ignore {{#include ../../../examples/codec/src/lib.rs:configuring_the_encoder}} ``` The default values for the `EncoderConfig` are: ```rust,ignore {{#include ../../../packages/fuels-core/src/codec/abi_encoder.rs:default_encoder_config}} ``` ## Configuring the encoder for contract/script calls You can also configure the encoder used to encode the arguments of the contract method: ```rust,ignore {{#include ../../../examples/contracts/src/lib.rs:contract_encoder_config}} ``` The same method is available for script calls. ================================================ FILE: docs/src/codec/index.md ================================================ # Codec Encoding and decoding are done as per [the fuel spec](https://docs.fuel.network/docs/specs/abi/argument-encoding/). To this end, `fuels` makes use of the [`ABIEncoder`](https://docs.rs/fuels/latest/fuels/core/codec/struct.ABIEncoder.html) and the [`ABIDecoder`](https://docs.rs/fuels/latest/fuels/core/codec/struct.ABIDecoder.html). ## Prerequisites for decoding/encoding To encode a type, you must first convert it into a [`Token`](https://docs.rs/fuels/latest/fuels/types/enum.Token.html). This is commonly done by implementing the [`Tokenizable`](https://docs.rs/fuels/latest/fuels/core/traits/trait.Tokenizable.html) trait. To decode, you also need to provide a [`ParamType`](https://docs.rs/fuels/latest/fuels/types/param_types/enum.ParamType.html) describing the schema of the type in question. This is commonly done by implementing the [Parameterize](https://docs.rs/fuels/latest/fuels/core/traits/trait.Parameterize.html) trait. All types generated by the [`abigen!`](../abigen/index.md) macro implement both the [`Tokenizable`](https://docs.rs/fuels/latest/fuels/core/traits/trait.Tokenizable.html) and [`Parameterize`](https://docs.rs/fuels/latest/fuels/core/traits/trait.Parameterize.html) traits. `fuels` also contains implementations for: - [`Tokenizable`](https://docs.rs/fuels/latest/fuels/core/traits/trait.Tokenizable.html) for the `fuels`-owned types listed [here](https://docs.rs/fuels/latest/fuels/core/traits/trait.Tokenizable.html#implementers) as well as [for some foreign types](https://docs.rs/fuels/latest/fuels/core/traits/trait.Tokenizable.html#foreign-impls) (such as `u8`, `u16`, `std::vec::Vec`, etc.). - [`Parameterize`](https://docs.rs/fuels/latest/fuels/core/traits/trait.Parameterize.html) for the `fuels`-owned types listed [here](https://docs.rs/fuels/latest/fuels/core/traits/trait.Parameterize.html#implementers) as well as [for some foreign types](https://docs.rs/fuels/latest/fuels/core/traits/trait.Parameterize.html#foreign-impls) (such as `u8`, `u16`, `std::vec::Vec`, etc.). ## Deriving the traits Both [`Tokenizable`](https://docs.rs/fuels/latest/fuels/core/traits/trait.Tokenizable.html) and [`Parameterize`](https://docs.rs/fuels/latest/fuels/core/traits/trait.Parameterize.html) can be derived for `struct`s and `enum`s if all inner types implement the derived traits: ```rust,ignore {{#include ../../../examples/macros/src/lib.rs:deriving_traits}} ``` > Note: > Deriving [`Tokenizable`](https://docs.rs/fuels/latest/fuels/core/traits/trait.Tokenizable.html) on `enum`s requires that all variants also implement [`Parameterize`](https://docs.rs/fuels/latest/fuels/core/traits/trait.Parameterize.html). ### Tweaking the derivation #### Changing the location of imports The derived code expects that the `fuels` package is accessible through `::fuels`. If this is not the case then the derivation macro needs to be given the locations of `fuels::types` and `fuels::core`. ```rust,ignore {{#include ../../../examples/macros/src/lib.rs:deriving_traits_paths}} ``` #### Generating no-std code If you want `no-std` generated code: ```rust,ignore {{#include ../../../examples/macros/src/lib.rs:deriving_traits_nostd}} ``` ================================================ FILE: docs/src/connecting/external-node.md ================================================ # Connecting to the Testnet or an external node We can interact with the `Testnet` node by using the following example. ```rust,ignore {{#include ../../../examples/providers/src/lib.rs:connect_to_testnet}} ``` > > For detailed information about various testnet networks and their optimal toolchain configurations for your project, please visit the following link: > > [networks](https://fuelbook.fuel.network/master/networks/networks.html) In the code example, we connected a new provider to the Testnet node and created a new wallet from a private key. > **Note:** New wallets on the Testnet will not have any assets! They can be obtained by providing the wallet address to the faucet at > >[faucet-testnet.fuel.network](https://faucet-testnet.fuel.network) > > Once the assets have been transferred to the wallet, you can reuse it in other tests by providing the private key! > > In addition to the faucet, there is a block explorer for the Testnet at > > [block-explorer](https://fuellabs.github.io/block-explorer-v2) If you want to connect to another node just change the URL or IP and port. For example, to connect to a local node that was created with `fuel-core` you can use: ```rust,ignore {{#include ../../../examples/providers/src/lib.rs:local_node_address}} ``` ================================================ FILE: docs/src/connecting/index.md ================================================ # Connecting to a Fuel node At a high level, you can use the Fuel Rust SDK to build Rust-based applications that can run computations on the Fuel Virtual Machine through interactions with smart contracts written in Sway. For this interaction to work, the SDK must be able to communicate with a `fuel-core` node; you have two options at your disposal: 1. Use the testnet or run a Fuel node (using `fuel-core`) and instantiate a provider that points to that node's IP and port. 2. Use the SDK's native `launch_provider_and_get_wallet()` that runs a short-lived test Fuel node; The second option is ideal for smart contract testing, as you can quickly spin up and tear down nodes between specific test cases. For application building, you should use the first option. ================================================ FILE: docs/src/connecting/querying.md ================================================ # Querying the blockchain Once you set up a provider, you can interact with the Fuel blockchain. Here are a few examples of what you can do with a provider; for a more in-depth overview of the API, check the [official provider API documentation](https://docs.rs/fuels/latest/fuels/accounts/provider/struct.Provider.html). - [Set up](#set-up) - [Get all coins from an address](#get-all-coins-from-an-address) - [Get spendable resources owned by an address](#get-spendable-resources-owned-by-an-address) - [Get balances from an address](#get-balances-from-an-address) ## Set up You might need to set up a test blockchain first. You can skip this step if you're connecting to an external blockchain. ```rust,ignore {{#include ../../../examples/providers/src/lib.rs:setup_test_blockchain}} ``` ## Get all coins from an address This method returns all unspent coins (of a given asset ID) from a wallet. ```rust,ignore {{#include ../../../examples/providers/src/lib.rs:get_coins}} ``` ## Get spendable resources owned by an address The following example shows how to fetch resources owned by an address. First, you create a `ResourceFilter` which specifies the target address, asset ID, and amount. You can also define UTXO IDs and message IDs that should be excluded when retrieving the resources: ```rust,ignore {{#include ../../../packages/fuels-accounts/src/provider.rs:resource_filter}} ``` The example uses default values for the asset ID and the exclusion lists. This resolves to the base asset ID and empty vectors for the ID lists respectively: ```rust,ignore {{#include ../../../examples/providers/src/lib.rs:get_spendable_resources}} ``` ## Get balances from an address Get all the spendable balances of all assets for an address. This is different from getting the coins because we only return the numbers (the sum of UTXOs coins amount for each asset ID) and not the UTXOs coins themselves. ```rust,ignore {{#include ../../../examples/providers/src/lib.rs:get_balances}} ``` ================================================ FILE: docs/src/connecting/retrying.md ================================================ # Retrying requests The [`Provider`](https://docs.rs/fuels/0.62.0/fuels/accounts/provider/struct.Provider.html) can be configured to retry a request upon receiving a `io::Error`. > Note: Currently all node errors are received as `io::Error`s. So, if configured, a retry will happen even if, for example, a transaction failed to verify. We can configure the number of retry attempts and the retry strategy as detailed below. ## `RetryConfig` The retry behavior can be altered by giving a custom `RetryConfig`. It allows for configuring the maximum number of attempts and the interval strategy used. ```rust, ignore {{#include ../../../packages/fuels-accounts/src/provider/retry_util.rs:retry_config}} ``` ```rust, ignore {{#include ../../../examples/providers/src/lib.rs:configure_retry}} ``` ## Interval strategy - `Backoff` `Backoff` defines different strategies for managing intervals between retry attempts. Each strategy allows you to customize the waiting time before a new attempt based on the number of attempts made. ### Variants - `Linear(Duration)`: `Default` Increases the waiting time linearly with each attempt. - `Exponential(Duration)`: Doubles the waiting time with each attempt. - `Fixed(Duration)`: Uses a constant waiting time between attempts. ```rust, ignore {{#include ../../../packages/fuels-accounts/src/provider/retry_util.rs:backoff}} ``` ================================================ FILE: docs/src/connecting/rocksdb.md ================================================ # RocksDB RocksDB enables the preservation of the blockchain's state locally, facilitating its future utilization. To create or use a local database, follow these instructions: ```rust,ignore {{#include ../../../examples/cookbook/src/lib.rs:create_or_use_rocksdb}} ``` > Note: If the specified database does not exist, a new database will be created at that path. To utilize the code snippets above, either the `fuel-core` binary must be present, or both the `fuel-core-lib` and `rocksdb` features need to be enabled. ================================================ FILE: docs/src/connecting/short-lived.md ================================================ # Running a short-lived Fuel node with the SDK You can use the SDK to spin up a local, ideally short-lived Fuel node. Then, you can instantiate a Fuel client, pointing to this node. ```rust,ignore {{#include ../../../examples/contracts/src/lib.rs:instantiate_client}} ``` This approach is ideal for contract testing. You can also use the test helper `setup_test_provider()` for this: ```rust,ignore {{#include ../../../examples/wallets/src/lib.rs:create_random_wallet}} ``` You can also use `launch_provider_and_get_wallet()`, which abstracts away the `setup_test_provider()` and the wallet creation, all in one single method: ```rust,ignore let wallet = launch_provider_and_get_wallet().await?; ``` ## Features ### Fuel-core lib The `fuel-core-lib` feature allows us to run a `fuel-core` node without installing the `fuel-core` binary on the local machine. Using the `fuel-core-lib` feature flag entails downloading all the dependencies needed to run the fuel-core node. ```rust,ignore fuels = { version = "0.76.0", features = ["fuel-core-lib"] } ``` ### RocksDB The `rocksdb` is an additional feature that, when combined with `fuel-core-lib`, provides persistent storage capabilities while using `fuel-core` as a library. ```rust,ignore fuels = { version = "0.76.0", features = ["rocksdb"] } ``` ================================================ FILE: docs/src/contributing/CONTRIBUTING.md ================================================ # Contributing to the Fuel Rust SDK Thanks for your interest in contributing to the Fuel Rust SDK! This document outlines the process for installing dependencies, setting up for development, and conventions for contributing.` If you run into any difficulties getting started, you can always ask questions on our [Discourse](https://forum.fuel.network/). ## Finding something to work on You may contribute to the project in many ways, some of which involve coding knowledge and some which do not. A few examples include: - Reporting bugs - Adding new features or bug fixes for which there is already an open issue - Making feature requests Check out our [Help Wanted](https://github.com/FuelLabs/fuels-rs/labels/help%20wanted) or [Good First Issues](https://github.com/FuelLabs/fuels-rs/labels/good%20first%20issue) to find a suitable task. If you are planning something big, for example, changes related to multiple components or changes to current behaviors, make sure to [open an issue](https://github.com/FuelLabs/fuels-rs/issues/new) to discuss with us before starting on the implementation. ## Contribution flow This is a rough outline of what a contributor's workflow looks like: - Make sure what you want to contribute is already tracked as an issue. - We may discuss the problem and solution in the issue. - Create a Git branch from where you want to base your work. This is usually master. - Write code, add test cases, and commit your work. - Run tests and make sure all tests pass. - Add the breaking label to your PR if the PR contains any breaking changes. - Push your changes to a branch in your fork of the repository and submit a pull request. - Make sure to mention the issue created in step 1 in the commit message. - Your PR will be reviewed, and some changes may be requested. - Your PR must be re-reviewed and approved once you've made changes. - Use GitHub's 'update branch' button if the PR becomes outdated. - If there are conflicts, you can merge and resolve them locally. Then push to your PR branch. Any changes to the branch will require a re-review. - Our CI system (Github Actions) automatically tests all authorized pull requests. - Use GitHub to merge the PR once approved. Thanks for your contributions! ## Linking issues Pull requests should be linked to at least one issue in the same repo. If the pull request resolves the relevant issues, and you want GitHub to close these issues automatically after it merged into the default branch, you can use the syntax (`KEYWORD #ISSUE-NUMBER`) like this: ```sh close #123 ``` If the pull request links an issue but does not close it, you can use the keyword `ref` like this: ```sh ref #456 ``` Multiple issues should use full syntax for each issue and be separated by a comma, like: ```sh close #123, ref #456 ``` ================================================ FILE: docs/src/contributing/tests-structure.md ================================================ # Integration tests structure in `fuels-rs` The integration tests of `fuels-rs` cover almost all aspects of the SDK and have grown significantly as more functionality was added. To make the tests and associated `Sway` projects more manageable they were split into several categories. A category consist of a `.rs` file for the tests and, if needed, a separate directory for the `Sway` projects. Currently have the following structure: ```shell . ├─ bindings/ ├─ contracts/ ├─ logs/ ├─ predicates/ ├─ storage/ ├─ types/ ├─ bindings.rs ├─ contracts.rs ├─ from_token.rs ├─ logs.rs ├─ predicates.rs ├─ providers.rs ├─ scripts.rs ├─ storage.rs ├─ types.rs └─ wallets.rs ``` Even though test organization is subjective, please consider these guidelines before adding a new category: - Add a new category when creating a new section in the `Fuels Rust SDK` book - e.g. `Types` - Add a new category if there are more than 3 test and more than 100 lines of code and they form a group of tests - e.g. `storage.rs` Otherwise, we recommend putting the integration test inside the existing categories above. ================================================ FILE: docs/src/cookbook/custom-chain.md ================================================ # Custom chain This example demonstrates how to start a short-lived Fuel node with custom consensus parameters for the underlying chain. First, we have to import `ConsensusParameters` and `ChainConfig`: ```rust,ignore {{#include ../../../examples/cookbook/src/lib.rs:custom_chain_import}} ``` Next, we can define some values for the consensus parameters: ```rust,ignore {{#include ../../../examples/cookbook/src/lib.rs:custom_chain_consensus}} ``` Before we can start a node, we probably also want to define some genesis coins and assign them to an address: ```rust,ignore {{#include ../../../examples/cookbook/src/lib.rs:custom_chain_coins}} ``` Finally, we call `setup_test_provider()`, which starts a node with the given configurations and returns a provider attached to that node: ```rust,ignore {{#include ../../../examples/cookbook/src/lib.rs:custom_chain_provider}} ``` ================================================ FILE: docs/src/cookbook/deposit-and-withdraw.md ================================================ # Deposit and withdraw Consider the following contract: ```rust,ignore {{#include ../../../e2e/sway/contracts/liquidity_pool/src/main.sw}} ``` As its name suggests, it represents a simplified example of a liquidity pool contract. The method `deposit()` expects you to supply an arbitrary amount of the `BASE_TOKEN`. As a result, it mints double the amount of the liquidity asset to the calling address. Analogously, if you call `withdraw()` supplying it with the liquidity asset, it will transfer half that amount of the `BASE_TOKEN` back to the calling address except for deducting it from the contract balance instead of minting it. The first step towards interacting with any contract in the Rust SDK is calling the `abigen!` macro to generate type-safe Rust bindings for the contract methods: ```rust,ignore {{#include ../../../examples/cookbook/src/lib.rs:liquidity_abigen}} ``` Next, we set up a wallet with custom-defined assets. We give our wallet some of the contracts `BASE_TOKEN` and the default asset (required for contract deployment): ```rust,ignore {{#include ../../../examples/cookbook/src/lib.rs:liquidity_wallet}} ``` Having launched a provider and created the wallet, we can deploy our contract and create an instance of its methods: ```rust,ignore {{#include ../../../examples/cookbook/src/lib.rs:liquidity_deploy}} ``` With the preparations out of the way, we can finally deposit to the liquidity pool by calling `deposit()` via the contract instance. Since we have to transfer assets to the contract, we create the appropriate `CallParameters` and chain them to the method call. To receive the minted liquidity pool asset, we have to append a variable output to our contract call. ```rust,ignore {{#include ../../../examples/cookbook/src/lib.rs:liquidity_deposit}} ``` As a final demonstration, let's use all our liquidity asset balance to withdraw from the pool and confirm we retrieved the initial amount. For this, we get our liquidity asset balance and supply it to the `withdraw()` call via `CallParameters`. ```rust,ignore {{#include ../../../examples/cookbook/src/lib.rs:liquidity_withdraw}} ``` ================================================ FILE: docs/src/cookbook/index.md ================================================ # Cookbook This section covers more advanced use cases that can be satisfied by combining various features of the Rust SDK. As such, it assumes that you have already made yourself familiar with the previous chapters of this book. > **Note** This section is still a work in progress and more recipes may be added in the future. ================================================ FILE: docs/src/cookbook/transfer-all-assets.md ================================================ # Transfer all assets The `transfer()` method lets you transfer a single asset, but what if you needed to move all of your assets to a different wallet? You could repeatably call `transfer()`, initiating a transaction each time, or you bundle all the transfers into a single transaction. This chapter guides you through crafting your custom transaction for transferring all assets owned by a wallet. Lets quickly go over the setup: ```rust,ignore {{#include ../../../examples/cookbook/src/lib.rs:transfer_multiple_setup}} ``` We prepare two wallets with randomized addresses. Next, we want one of our wallets to have some random assets, so we set them up with `setup_multiple_assets_coins()`. Transactions require us to define input and output coins. Let's assume we do not know the assets owned by `wallet_1`. We retrieve its balances, i.e. tuples consisting of a string representing the asset ID and the respective amount. This lets us use the helpers `get_asset_inputs_for_amount()`, `get_asset_outputs_for_amount()` to create the appropriate inputs and outputs. We transfer only a part of the base asset balance so that the rest can cover transaction fees: ```rust,ignore {{#include ../../../examples/cookbook/src/lib.rs:transfer_multiple_input}} ``` All that is left is to build the transaction via `ScriptTransactionBuilder`, have `wallet_1` add a witness to it and we can send it. We confirm this by checking the number of balances present in the receiving wallet and their amount: ```rust,ignore {{#include ../../../examples/cookbook/src/lib.rs:transfer_multiple_transaction}} ``` ================================================ FILE: docs/src/custom-transactions/custom-calls.md ================================================ # Custom contract and script calls When preparing a contract call via `CallHandler`, the Rust SDK uses a transaction builder in the background. You can fetch this builder and customize it before submitting it to the network. After the transaction is executed successfully, you can use the corresponding `CallHandler` to generate a [call response](../calling-contracts/call-response.md). The call response can be used to decode return values and logs. Below are examples for both contract and script calls. ## Custom contract call ```rust,ignore {{#include ../../../examples/contracts/src/lib.rs:contract_call_tb}} ``` ## Custom script call ```rust,ignore {{#include ../../../e2e/tests/scripts.rs:script_call_tb}} ``` ================================================ FILE: docs/src/custom-transactions/index.md ================================================ # Custom transactions Until now, we have used helpers to create transactions, send them with a provider, and parse the results. However, sometimes we must make custom transactions with specific inputs, outputs, witnesses, etc. In the next chapter, we will show how to use the Rust SDKs transaction builders to accomplish this. ================================================ FILE: docs/src/custom-transactions/transaction-builders.md ================================================ # Transaction Builders The Rust SDK simplifies the creation of **Create** and **Script** transactions through two handy builder structs `CreateTransactionBuilder`, `ScriptTransactionBuilder`, and the `TransactionBuilder` trait. Calling `build(&provider)` on a builder will result in the corresponding `CreateTransaction` or `ScriptTransaction` that can be submitted to the network. ## Role of the transaction builders > **Note** This section contains additional information about the inner workings of the builders. If you are just interested in how to use them, you can skip to the next section. The builders take on the heavy lifting behind the scenes, offering two standout advantages: handling predicate data offsets and managing witness indexing. When your transaction involves predicates with dynamic data as inputs, like vectors, the dynamic data contains a pointer pointing to the beginning of the raw data. This pointer's validity hinges on the order of transaction inputs, and any shifting could render it invalid. However, the transaction builders conveniently postpone the resolution of these pointers until you finalize the build process. Similarly, adding signatures for signed coins requires the signed coin input to hold an index corresponding to the signature in the witnesses array. These indexes can also become invalid if the witness order changes. The Rust SDK again defers the resolution of these indexes until the transaction is finalized. It handles the assignment of correct index witnesses behind the scenes, sparing you the hassle of dealing with indexing intricacies during input definition. Another added benefit of the builder pattern is that it guards against changes once the transaction is finalized. The transactions resulting from a builder don't permit any changes to the struct that could cause the transaction ID to be modified. This eliminates the headache of calculating and storing a transaction ID for future use, only to accidentally modify the transaction later, resulting in a different transaction ID. ## Creating a custom transaction Here is an example outlining some of the features of the transaction builders. In this scenario, we have a predicate that holds some bridged asset with ID **bridged_asset_id**. It releases it's locked assets if the transaction sends **ask_amount** of the base asset to the **receiver** address: ```rust,ignore {{#include ../../../examples/cookbook/src/lib.rs:custom_tx_receiver}} ``` Our goal is to create a transaction that will use our hot wallet to transfer the **ask_amount** to the **receiver** and then send the unlocked predicate assets to a second wallet that acts as our cold storage. Let's start by instantiating a builder. Since we don't plan to deploy a contract, the `ScriptTransactionBuilder` is the appropriate choice: ```rust,ignore {{#include ../../../examples/cookbook/src/lib.rs:custom_tx}} ``` Next, we need to define transaction inputs of the base asset that sum up to **ask_amount**. We also need transaction outputs that will assign those assets to the predicate address and thereby unlock it. The methods `get_asset_inputs_for_amount` and `get_asset_outputs_for_amount` can help with that. We need to specify the asset ID, the target amount, and the target address: ```rust,ignore {{#include ../../../examples/cookbook/src/lib.rs:custom_tx_io_base}} ``` Let's repeat the same process but this time for transferring the assets held by the predicate to our cold storage: ```rust,ignore {{#include ../../../examples/cookbook/src/lib.rs:custom_tx_io_other}} ``` We combine all of the inputs and outputs and set them on the builder: ```rust,ignore {{#include ../../../examples/cookbook/src/lib.rs:custom_tx_io}} ``` As we have used coins that require a signature, we have to add the signer to the transaction builder with: ```rust,ignore {{#include ../../../examples/cookbook/src/lib.rs:custom_tx_add_signer}} ``` > **Note** The signature is not created until the transaction is finalized with `build(&provider)` We need to do one more thing before we stop thinking about transaction inputs. Executing the transaction also incurs a fee that is paid with the base asset. Our base asset inputs need to be large enough so that the total amount covers the transaction fee and any other operations we are doing. The `ViewOnlyAccount` trait lets us use `adjust_for_fee()` for adjusting the transaction inputs if needed to cover the fee. The second argument to `adjust_for_fee()` is the total amount of the base asset that we expect our transaction to spend regardless of fees. In our case, this is the **ask_amount** we are transferring to the predicate. ```rust,ignore {{#include ../../../examples/cookbook/src/lib.rs:custom_tx_adjust}} ``` > **Note** It is recommended to add signers before calling `adjust_for_fee()` as the estimation will include the size of the witnesses. We can also define transaction policies. For example, we can set the maturity and expiration with: ```rust,ignore {{#include ../../../examples/cookbook/src/lib.rs:custom_tx_policies}} ``` Our builder needs a signature from the hot wallet to unlock its coins before we call `build()` and submit the resulting transaction through the provider: ```rust,ignore {{#include ../../../examples/cookbook/src/lib.rs:custom_tx_build}} ``` Finally, we verify the transaction succeeded and that the cold storage indeed holds the bridged asset now: ```rust,ignore {{#include ../../../examples/cookbook/src/lib.rs:custom_tx_verify}} ``` ## Building a transaction without signatures If you need to build the transaction without signatures, which is useful when estimating transaction costs or simulations, you can change the build strategy used: ```rust,ignore {{#include ../../../e2e/tests/contracts.rs:tb_no_signatures_strategy}} ``` > **Note** In contrast to adding signers to a transaction builder, when signing a built transaction, you must ensure that the order of signatures matches the order of signed inputs. Multiple signed inputs with the same owner will have the same witness index. ================================================ FILE: docs/src/debugging/decoding-script-transactions.md ================================================ # Decoding script transactions The SDK offers some tools that can help you make fuel script transactions more human readable. You can determine whether the script transaction is: * calling a contract method(s), * is a loader script and you can see the blob id * is neither of the above In the case of contract call(s), if you have the ABI file, you can also decode the arguments to the function by making use of the `AbiFormatter`: ```rust,ignore {{#include ../../../examples/contracts/src/lib.rs:decoding_script_transactions}} ``` prints: ```text The script called: initialize_counter(42) ``` The `AbiFormatter` can also decode configurables, refer to the rust docs for more information. ================================================ FILE: docs/src/debugging/function-selector.md ================================================ # Function selector Whenever you call a contract method the SDK will generate a function selector according to the fuel specs which will be used by the node to identify which method we wish to execute. If, for whatever reason, you wish to generate the function selector yourself you can do so: ```rust,ignore {{#include ../../../examples/debugging/src/lib.rs:example_fn_selector}} ``` ================================================ FILE: docs/src/debugging/index.md ================================================ # Debugging > **note** This section is still a work in progress. - [The Function Selector](./function-selector.md) ================================================ FILE: docs/src/deploying/configurable-constants.md ================================================ # Configurable constants In Sway, you can define `configurable` constants which can be changed during the contract deployment in the SDK. Here is an example how the constants are defined. ```rust,ignore {{#include ../../../e2e/sway/contracts/configurables/src/main.sw}} ``` Each of the configurable constants will get a dedicated `with` method in the SDK. For example, the constant `STR_4` will get the `with_STR_4` method which accepts the same type as defined in the contract code. Below is an example where we chain several `with` methods and deploy the contract with the new constants. ```rust,ignore {{#include ../../../e2e/tests/configurables.rs:contract_configurables}} ``` ================================================ FILE: docs/src/deploying/index.md ================================================ # Deploying contracts There are two main ways of working with contracts in the SDK: deploying a contract with SDK or using the SDK to interact with existing contracts. ## Deploying a contract binary Once you've written a contract in Sway and compiled it with `forc build`, you'll have in your hands two important artifacts: the compiled binary file and the JSON ABI file. > Note: Read [here](https://docs.fuel.network/guides/quickstart/) for more on how to work with Sway. Below is how you can deploy your contracts using the SDK. For more details about each component in this process, read [The abigen macro](../abigen/the-abigen-macro.md), [The FuelVM binary file](./the-fuelvm-binary-file.md), and [The JSON ABI file](../abigen/the-json-abi-file.md). First, the `Contract::load_from` function is used to load a contract binary with a `LoadConfiguration`. If you are only interested in a single instance of your contract, use the default configuration: `LoadConfiguration::default()`. After the contract binary is loaded, you can use the `deploy()` method to deploy the contract to the blockchain. ```rust,ignore {{#include ../../../examples/contracts/src/lib.rs:deploy_contract}} ``` Alternatively, you can use `LoadConfiguration` to configure how the contract is loaded. `LoadConfiguration` let's you: - Load the same contract binary with `Salt` to get a new `contract_id` - Change the contract's storage slots - Update the contract's configurables > Note: The next section will give more information on how `configurables` can be used. Additionally, you can set custom `TxParameters` when deploying the loaded contract. ```rust,ignore {{#include ../../../examples/contracts/src/lib.rs:deploy_with_parameters}} ``` After the contract is deployed, you can use the contract's methods like this: ```rust,ignore {{#include ../../../examples/contracts/src/lib.rs:use_deployed_contract}} ``` > Note: When redeploying an existing `Contract`, ensure that you initialize it with a unique salt to prevent deployment failures caused by a contract ID collision. To accomplish this, utilize the `with_salt` method to clone the existing `Contract` with a new salt. ================================================ FILE: docs/src/deploying/interacting-with-contracts.md ================================================ # Interacting with contracts If you already have a deployed contract and want to call its methods using the SDK, but without deploying it again, all you need is the contract ID of your deployed contract. You can skip the whole deployment setup and call `::new(contract_id, wallet)` directly. For example: ```rust,ignore {{#include ../../../examples/contracts/src/lib.rs:deployed_contracts}} ``` ================================================ FILE: docs/src/deploying/large_contracts.md ================================================ # Deploying Large Contracts If your contract exceeds the size limit for a single deployment: ```rust,ignore {{#include ../../../examples/contracts/src/lib.rs:show_contract_is_too_big}} ``` you can deploy it in segments using a partitioned approach: ```rust,ignore {{#include ../../../examples/contracts/src/lib.rs:deploy_via_loader}} ``` When you convert a standard contract into a loader contract, the following changes occur: * The original contract code is replaced with the loader contract code. * The original contract code is split into blobs, which will be deployed via blob transactions before deploying the contract itself. * The new loader code, when invoked, loads these blobs into memory and executes your original contract. After deploying the loader contract, you can interact with it just as you would with a standard contract: ```rust,ignore {{#include ../../../examples/contracts/src/lib.rs:use_loader}} ``` A helper function is available to deploy your contract normally if it is within the size limit, or as a loader contract if it exceeds the limit: ```rust,ignore {{#include ../../../examples/contracts/src/lib.rs:auto_convert_to_loader}} ``` You also have the option to separate the blob upload from the contract deployment for more granular control: ```rust,ignore {{#include ../../../examples/contracts/src/lib.rs:upload_blobs_then_deploy}} ``` Alternatively, you can manually split your contract code into blobs and then create and deploy a loader: ```rust,ignore {{#include ../../../examples/contracts/src/lib.rs:manual_blobs_then_deploy}} ``` Or you can upload the blobs yourself and proceed with just the loader deployment: ```rust,ignore {{#include ../../../examples/contracts/src/lib.rs:manual_blob_upload_then_deploy}} ``` ## Blob Size Considerations The size of a Blob transaction is constrained by three factors: 1. The maximum size of a single transaction: ```rust,ignore {{#include ../../../examples/contracts/src/lib.rs:show_max_tx_size}} ``` 2. The maximum gas usage for a single transaction: ```rust,ignore {{#include ../../../examples/contracts/src/lib.rs:show_max_tx_gas}} ``` 3. The maximum HTTP body size accepted by the Fuel node. To estimate an appropriate size for your blobs, you can run: ```rust,ignore {{#include ../../../examples/contracts/src/lib.rs:estimate_max_blob_size}} ``` However, keep in mind the following limitations: * The estimation only considers the maximum transaction size, not the max gas usage or HTTP body limit. * It does not account for any size increase that may occur after the transaction is funded. Therefore, it is advisable to make your blobs a few percent smaller than the estimated maximum size. ================================================ FILE: docs/src/deploying/storage-slots.md ================================================ # Overriding storage slots If you use storage in your contract, the default storage values will be generated in a JSON file (e.g. `my_contract-storage_slots.json`) by the Sway compiler. These are loaded automatically for you when you load a contract binary. If you wish to override some of the defaults, you need to provide the corresponding storage slots manually: ```rust,ignore {{#include ../../../examples/contracts/src/lib.rs:storage_slots_override}} ``` If you don't have the slot storage file (`my_contract-storage_slots.json` example from above) for some reason, or you don't wish to load any of the default values, you can disable the auto-loading of storage slots: ```rust,ignore {{#include ../../../examples/contracts/src/lib.rs:storage_slots_disable_autoload}} ``` ================================================ FILE: docs/src/deploying/the-fuelvm-binary-file.md ================================================ # The FuelVM binary file The command `forc build` compiles your Sway code and generates the bytecode: the binary code that the Fuel Virtual Machine will interpret. For instance, the smart contract below: ```Rust contract; abi MyContract { fn test_function() -> bool; } impl MyContract for Contract { fn test_function() -> bool { true } } ``` After `forc build`, will have a binary file that contains: ```terminal $ cat out/release/my-test.bin G4]�]D`I]C�As@ 6]C�$@!QK% ``` This seems very unreadable! But, `forc` has a nice interpreter for this bytecode: `forc parse-bytecode`, which will interpret that binary data and output the equivalent FuelVM assembly: ```terminal $ forc parse-bytecode out/release/my-test.bin half-word byte op raw notes 0 0 JI(4) 90 00 00 04 jump to byte 16 1 4 NOOP 47 00 00 00 2 8 Undefined 00 00 00 00 data section offset lo (0) 3 12 Undefined 00 00 00 34 data section offset hi (52) 4 16 LW(63, 12, 1) 5d fc c0 01 5 20 ADD(63, 63, 12) 10 ff f3 00 6 24 LW(17, 6, 73) 5d 44 60 49 7 28 LW(16, 63, 1) 5d 43 f0 01 8 32 EQ(16, 17, 16) 13 41 14 00 9 36 JNZI(16, 11) 73 40 00 0b conditionally jump to byte 44 10 40 RVRT(0) 36 00 00 00 11 44 LW(16, 63, 0) 5d 43 f0 00 12 48 RET(16) 24 40 00 00 13 52 Undefined 00 00 00 00 14 56 Undefined 00 00 00 01 15 60 Undefined 00 00 00 00 16 64 XOR(20, 27, 53) 21 51 bd 4b ``` If you want to deploy your smart contract using the SDK, this binary file is important; it's what we'll be sending to the FuelVM in a transaction. ================================================ FILE: docs/src/getting-started.md ================================================ # Getting Started ## Installation Guide Please visit the Fuel [installation guide](https://docs.fuel.network/guides/installation) to install the Fuel toolchain binaries and prerequisites. `forc` is Sway equivalent of Rust's `cargo`. `fuel-core` is a Fuel full node implementation. There are two main ways you can use the Fuel Rust SDK: 1. Creating a new Sway project with `forc` and running the tests 2. Creating a standalone project and importing the `fuels-rs` crate ## Creating a new project with Forc You can create a new Sway project with ```shell forc new ``` Or you can initialize a project within an existing folder with ```shell forc init ``` ### Adding a Rust integration test to the Sway project Now that we have a new project, we can add a Rust integration test using a `cargo generate` template. If `cargo generate` is not already installed, you can install it with: ```shell cargo install cargo-generate ``` > **Note** You can learn more about cargo generate by visiting its [repository](https://github.com/cargo-generate/cargo-generate). Let's generate the default test harness with the following command: ```shell cargo generate --init fuellabs/sway templates/sway-test-rs --name --force ``` `--force` forces your `--name` input to retain your desired casing for the `{{project-name}}` placeholder in the template. Otherwise, `cargo-generate` automatically converts it to kebab-case. With `--force`, this means that both `my_fuel_project` and `my-fuel-project` are valid project names, depending on your needs. Before running test, we need to build the Sway project with: ```shell forc build ``` Afterwards, we can run the test with: ```shell cargo test ``` > **Note** If you need to capture output from the tests, use one of the following commands: ```shell cargo test -- --nocapture ``` ## Importing the Fuel Rust SDK Add these dependencies on your `Cargo.toml`: ```toml fuels = "0.66.0" ``` > **Note** We're using version `0.66.0` of the SDK, which is the latest version at the time of this writing. And then, in your Rust file that's going to make use of the SDK: ```rust,ignore use fuels::prelude::*; ``` ## The Fuel Rust SDK source code Another way to experience the SDK is to look at the source code. The `e2e/tests/` folder is full of integration tests that go through almost all aspects of the SDK. > **Note** Before running the tests, we need to build all the Sway test projects. The file `packages/fuels/Forc.toml` contains a `[workspace], which members are the paths to all integration tests. > To build these tests, run the following command: ```shell forc build --release --path e2e ``` > `forc` can also be used to clean and format the test projects. Check the `help` output for more info. After building the projects, we can run the tests with ```shell cargo test ``` If you need all targets and all features, you can run ```shell cargo test --all-targets --all-features ``` > **Note** If you need to capture output from the tests, you can run ```shell cargo test -- --nocapture ``` ## More in-depth Fuel and Sway knowledge Read [The Sway Book](https://docs.fuel.network/docs/sway/) for more in-depth knowledge about Sway, the official smart contract language for the Fuel Virtual Machine. ================================================ FILE: docs/src/glossary.md ================================================ # Glossary ## Contract A contract, in the SDK, is an abstraction that represents a connection to a specific smart contract deployed on the Fuel Network. This contract instance can be used as a regular Rust object, with methods attached to it that reflect those in its smart contract equivalent. ## Provider A Provider is a struct that provides an abstraction for a connection to a Fuel node. It provides read-only access to the node. You can use this provider as-is or through the wallet. ================================================ FILE: docs/src/index.md ================================================ # The Fuel Rust SDK The Fuel Rust SDK can be used for a variety of things, including: - Compiling, deploying, and testing [Sway](https://github.com/FuelLabs/sway) contracts - Using the testnet or running a local Fuel node - Crafting and signing transactions with hand-crafted scripts or contract calls - Generating type-safe Rust bindings of contract ABI methods This book is an overview of the different things one can achieve using the Rust SDK, and how to implement them. Keep in mind that both the SDK and the documentation are works-in-progress! ================================================ FILE: docs/src/predicates/index.md ================================================ # Predicates Predicates, in Sway, are programs that return a Boolean value and do not have any side effects (they are pure). A predicate address can own assets. The predicate address is generated from the compiled byte code and is the same as the `P2SH` address used in Bitcoin. Users can seamlessly send assets to the predicate address as they do for any other address. To spend the predicate funds, the user has to provide the original `byte code` of the predicate together with the `predicate data`. The `predicate data` will be used when executing the `byte code`, and the funds can be transferred if the predicate is validated successfully. ## Instantiating predicates Let's consider the following predicate example: ```rust,ignore {{#include ../../../e2e/sway/predicates/basic_predicate/src/main.sw}} ``` We will look at a complete example of using the SDK to send and receive funds from a predicate. First, we set up the wallets and a node instance. The call to the `abigen!` macro will generate all the types specified in the predicate plus two custom structs: - an encoder with an `encode_data` function that will conveniently encode all the arguments of the main function for us. - a configurables struct which holds methods for setting all the configurables mentioned in the predicate > Note: The `abigen!` macro will append `Encoder` and `Configurables` to the predicate's `name` field. Fox example, `name="MyPredicate"` will result in two structs called `MyPredicateEncoder` and `MyPredicateConfigurables`. ```rust,ignore {{#include ../../../examples/predicates/src/lib.rs:predicate_data_setup}} ``` Once we've compiled our predicate with `forc build`, we can create a `Predicate` instance via `Predicate::load_from`. The resulting data from `encode_data` can then be set on the loaded predicate. ```rust,ignore {{#include ../../../examples/predicates/src/lib.rs:with_predicate_data}} ``` Next, we lock some assets in this predicate using the first wallet: ```rust,ignore {{#include ../../../examples/predicates/src/lib.rs:predicate_data_lock_amount}} ``` Then we can transfer assets owned by the predicate via the [Account](../accounts.md) trait: ```rust,ignore {{#include ../../../examples/predicates/src/lib.rs:predicate_data_unlock}} ``` ## Configurable constants Same as contracts and scripts, you can define configurable constants in `predicates`, which can be changed during the predicate execution. Here is an example of how the constants are defined. ```rust,ignore {{#include ../../../e2e/sway/predicates/predicate_configurables/src/main.sw:predicate_configurables}} ``` Each configurable constant will get a dedicated `with` method in the SDK. For example, the constant `U8` will get the `with_U8` method which accepts the same type defined in sway. Below is an example where we chain several `with` methods and update the predicate with the new constants. ```rust,ignore {{#include ../../../e2e/tests/predicates.rs:predicate_configurables}} ``` ================================================ FILE: docs/src/predicates/send-spend-predicate.md ================================================ # Signatures in predicates example This is a more involved example where the predicate accepts three signatures and matches them to three predefined public keys. The `ec_recover_address` function is used to recover the public key from the signatures. If two of the three extracted public keys match the predefined public keys, the funds can be spent. Note that the signature order has to match the order of the predefined public keys. ```rust,ignore {{#include ../../../e2e/sway/predicates/signatures/src/main.sw}} ``` Let's use the SDK to interact with the predicate. First, let's create three signers with specific keys. Their hashed public keys are already hard-coded in the predicate. Then we create the receiver signer, which we will use to spend the predicate funds. ```rust,ignore {{#include ../../../examples/predicates/src/lib.rs:predicate_signers}} ``` Next, let's add some coins, start a provider and create the wallets. ```rust,ignore {{#include ../../../examples/predicates/src/lib.rs:predicate_coins}} ``` Now we can use the predicate abigen to create a predicate encoder instance for us. To spend the funds now locked in the predicate, we must provide two out of three signatures whose public keys match the ones we defined in the predicate. In this example, the signatures are generated from an array of zeros. ```rust,ignore {{#include ../../../examples/predicates/src/lib.rs:predicate_load}} ``` Next, we transfer some assets from a wallet to the created predicate. We also confirm that the funds are indeed transferred. ```rust,ignore {{#include ../../../examples/predicates/src/lib.rs:predicate_receive}} ``` We can use the `transfer` method from the [Account](../accounts.md) trait to transfer the assets. If the predicate data is correct, the `receiver` wallet will get the funds, and we will verify that the amount is correct. ```rust,ignore {{#include ../../../examples/predicates/src/lib.rs:predicate_spend}} ``` ================================================ FILE: docs/src/preuploading-code.md ================================================ # Pre-uploading code If you have a script or predicate that is larger than normal or which you plan on calling often, you can pre-upload its code as a blob to the network and run a loader script/predicate instead. The loader can be configured with the script/predicate configurables, so you can change how the script/predicate is configured on each run without having large transactions due to the code duplication. ## Scripts A high level pre-upload: ```rust,ignore {{#include ../../e2e/tests/scripts.rs:preload_high_level}} ``` The upload of the blob is handled inside of the `convert_into_loader` method. If you want more fine-grained control over it, you can create the script transaction manually: ```rust,ignore {{#include ../../e2e/tests/scripts.rs:preload_low_level}} ``` ## Predicates You can prepare a predicate for pre-uploading without doing network requests: ```rust,ignore {{#include ../../e2e/tests/predicates.rs:preparing_the_predicate}} ``` Once you want to execute the predicate, you must beforehand upload the blob containing its code: ```rust,ignore {{#include ../../e2e/tests/predicates.rs:uploading_the_blob}} ``` By pre-uploading the predicate code, you allow for cheaper calls to the predicate from subsequent callers. ================================================ FILE: docs/src/reference.md ================================================ # API Reference For a more in-depth look at the APIs provided by the Fuel Rust SDK, head over to the [official documentation](https://docs.rs/fuels/latest/fuels/). In the actual Rust docs, you can see the most up-to-date information about the API, which is synced with the code as it changes. ================================================ FILE: docs/src/running-scripts.md ================================================ # Running scripts You can run a script using its JSON-ABI and the path to its binary file. You can run the scripts with arguments. For this, you have to use the `abigen!` macro seen [previously](./abigen/the-abigen-macro.md). ```rust,ignore {{#include ../../e2e/tests/scripts.rs:script_with_arguments}} ``` Furthermore, if you need to separate submission from value retrieval for any reason, you can do so as follows: ```rust,ignore {{#include ../../e2e/tests/scripts.rs:submit_response_script}} ``` ## Running scripts with transaction policies The method for passing transaction policies is the same as [with contracts](./calling-contracts/tx-policies.md). As a reminder, the workflow would look like this: ```rust,ignore {{#include ../../e2e/tests/scripts.rs:script_with_tx_policies}} ``` ## Custom inputs and outputs If you need to add specific inputs and outputs to script calls, you can use the `with_inputs` and `with_outputs` methods. ```rust,ignore {{#include ../../e2e/tests/scripts.rs:script_custom_inputs_outputs}} ``` > **Note:** if custom inputs include coins that need to be signed, use the `add_signer` method to add the appropriate signer. ## Logs Script calls provide the same logging functions, `decode_logs()` and `decode_logs_with_type()`, as contract calls. As a reminder, the workflow looks like this: ```rust,ignore {{#include ../../e2e/tests/logs.rs:script_logs}} ``` ## Calling contracts from scripts Scripts use the same interfaces for setting external contracts as [contract methods](./calling-contracts/other-contracts.md). Below is an example that uses `with_contracts(&[&contract_instance, ...])`. ```rust,ignore {{#include ../../e2e/tests/logs.rs:external_contract}} ``` And this is an example that uses `with_contract_ids(&[&contract_id, ...])`. ```rust,ignore {{#include ../../e2e/tests/logs.rs:external_contract_ids}} ``` ## Configurable constants Same as contracts, you can define `configurable` constants in `scripts` which can be changed during the script execution. Here is an example how the constants are defined. ```rust,ignore {{#include ../../e2e/sway/scripts/script_configurables/src/main.sw}} ``` Each configurable constant will get a dedicated `with` method in the SDK. For example, the constant `STR_4` will get the `with_STR_4` method which accepts the same type defined in sway. Below is an example where we chain several `with` methods and execute the script with the new constants. ```rust,ignore {{#include ../../e2e/tests/configurables.rs:script_configurables}} ``` ================================================ FILE: docs/src/testing/basics.md ================================================ # Testing Basics If you're new to Rust, you'll want to review these important tools to help you build tests. ## The `assert!` macro You can use the `assert!` macro to assert certain conditions in your test. This macro invokes `panic!()` and fails the test if the expression inside evaluates to `false`. ```rust, ignore assert!(value == 5); ``` ## The `assert_eq!` macro The `assert_eq!` macro works a lot like the `assert` macro, however instead it accepts two values, and panics if those values are not equal. ```rust, ignore assert_eq!(balance, 100); ``` ## The `assert_ne!` macro The `assert_ne!` macro works just like the `assert_eq!` macro, but it will panic if the two values are equal. ```rust, ignore assert_ne!(address, 0); ``` ## The `println!` macro You can use the `println!` macro to print values to the console. ```rust, ignore println!("WALLET 1 ADDRESS {}", wallet_1.address()); println!("WALLET 1 ADDRESS {:?}", wallet_1.address()); ``` Using `{}` will print the value, and using `{:?}` will print the value plus its type. Using `{:?}` will also allow you to print values that do not have the `Display` trait implemented but do have the `Debug` trait. Alternatively you can use the `dbg!` macro to print these types of variables. ```rust, ignore println!("WALLET 1 PROVIDER {:?}", wallet_1.provider()); dbg!("WALLET 1 PROVIDER {}", wallet_1.provider()); ``` To print more complex types that don't have it already, you can implement your own formatted display method with the `fmt` module from the Rust standard library. ```rust, ignore use std::fmt; struct Point { x: u64, y: u64, } // add print functionality with the fmt module impl fmt::Display for Point { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "value of x: {}, value of y: {}", self.x, self.y) } } let p = Point {x: 1, y: 2}; println!("POINT: {}", p); ``` ## Run Commands You can run your tests to see if they pass or fail with ```shell cargo test ``` Outputs will be hidden if the test passes. If you want to see outputs printed from your tests regardless of whether they pass or fail, use the `nocapture` flag. ```shell cargo test -- --nocapture ``` ================================================ FILE: docs/src/testing/chains.md ================================================ # Increasing the block height You can use `produce_blocks` to help achieve an arbitrary block height; this is useful when you want to do any testing regarding transaction maturity. > **Note**: For the `produce_blocks` API to work, it is imperative to have `manual_blocks_enabled = true` in the config for the running node. See example below. ```rust,ignore {{#include ../../../e2e/tests/providers.rs:use_produce_blocks_to_increase_block_height}} ``` You can also set a custom block time as the second, optional argument. Here is an example: ```rust,ignore {{#include ../../../e2e/tests/providers.rs:use_produce_blocks_custom_time}} ``` ================================================ FILE: docs/src/testing/index.md ================================================ # `fuels-rs` Testing > **note** This section is still a work in progress. - [Testing Basics](./basics.md) - [`setup_program_test!` Macro](./the-setup-program-test-macro.md) - [Tweaking the Blockchain](./chains.md) ================================================ FILE: docs/src/testing/the-setup-program-test-macro.md ================================================ # The setup_program_test! macro When deploying contracts with the `abigen!` macro, as shown in the previous sections, the user can: - change the default configuration parameters - launch several providers - create multiple wallets - create specific assets, etc. However, it is often the case that we want to quickly set up a test with default values and work directly with contract or script instances. The `setup_program_test!` can do exactly that. --- Used to reduce boilerplate in integration tests. Accepts input in the form of `COMMAND(ARG...)...` `COMMAND` is either `Wallets`, `Abigen`, `LoadScript` or `Deploy`. `ARG` is either a: - name-value (e.g. `name="MyContract"`), or, - a literal (e.g. `"some_str_literal"`, `true`, `5`, ...) - a sub-command (e.g. `Abigen(Contract(name="MyContract", project="some_project"))`) Available `COMMAND`s: ## Options Example: `Options(profile="debug")` Description: Sets options from `ARG`s to be used by other `COMMAND`s. Available options: - `profile`: sets the `cargo` build profile. Variants: `"release"` (default), `"debug"` Cardinality: 0 or 1. ## Wallets Example: `Wallets("a_wallet", "another_wallet"...)` Description: Launches a local provider and generates wallets with names taken from the provided `ARG`s. Cardinality: 0 or 1. ## Abigen Example: ```rust,ignore Abigen( Contract( name = "MyContract", project = "some_folder" ), Script( name = "MyScript", project = "some_folder" ), Predicate( name = "MyPredicate", project = "some_folder" ), ) ``` Description: Generates the program bindings under the name `name`. `project` should point to root of the `forc` project. The project must be compiled in `release` mode (`--release` flag) for `Abigen` command to work. Cardinality: 0 or N. ## Deploy Example: `Deploy(name="instance_name", contract="MyContract", wallet="a_wallet")` Description: Deploys the `contract` (with salt) using `wallet`. Will create a contract instance accessible via `name`. Due to salt usage, the same contract can be deployed multiple times. Requires that an `Abigen` command be present with `name` equal to `contract`. `wallet` can either be one of the wallets in the `Wallets` `COMMAND` or the name of a wallet you've previously generated yourself. Cardinality: 0 or N. ## `LoadScript` Example: `LoadScript(name = "script_instance", script = "MyScript", wallet = "wallet")` Description: Creates a script instance of `script` under `name` using `wallet`. Cardinality: 0 or N. --- The setup code that you have seen in previous sections gets reduced to: ```rust,ignore {{#include ../../../examples/contracts/src/lib.rs:deploy_contract_setup_macro_short}} ``` > **Note** The same contract can be deployed several times as the macro deploys the contracts with salt. You can also deploy different contracts to the same provider by referencing the same wallet in the `Deploy` command. ```rust,ignore {{#include ../../../e2e/tests/contracts.rs:contract_setup_macro_multi}} ``` In this example, three contracts are deployed on the same provider using the `wallet` generated by the `Wallets` command. The second and third macros use the same contract but have different IDs because of the deployment with salt. Both of them can call the first contract by using their ID. In addition, you can manually create the `wallet` variable and then use it inside the macro. This is useful if you want to create custom wallets or providers but still want to use the macro to reduce boilerplate code. Below is an example of this approach. ```rust,ignore {{#include ../../../e2e/tests/contracts.rs:contract_setup_macro_manual_wallet}} ``` ================================================ FILE: docs/src/types/B512.md ================================================ # `B512` In the Rust SDK, the `B512` definition matches the Sway standard library type with the same name and will be converted accordingly when interacting with contracts: ```rust,ignore {{#include ../../../packages/fuels-core/src/types/core/bits.rs:b512}} ``` Here's an example: ```rust,ignore {{#include ../../../e2e/tests/types_contracts.rs:b512_example}} ``` ================================================ FILE: docs/src/types/address.md ================================================ # `Address` Like `Bytes32`, `Address` is a wrapper on `[u8; 32]` with similar methods and implements the same traits (see [fuel-types documentation](https://docs.rs/fuel-types/latest/fuel_types/struct.Address.html)). These are the main ways of creating an `Address`: ```rust,ignore {{#include ../../../examples/types/src/lib.rs:address}} ``` ================================================ FILE: docs/src/types/asset-id.md ================================================ # `AssetId` Like `Bytes32`, `AssetId` is a wrapper on `[u8; 32]` with similar methods and implements the same traits (see [fuel-types documentation](https://docs.rs/fuel-types/0.49.0/fuel_types/struct.AssetId.html)). These are the main ways of creating an `AssetId`: ```rust,ignore {{#include ../../../examples/types/src/lib.rs:asset_id}} ``` ================================================ FILE: docs/src/types/bits256.md ================================================ # `Bits256` In Fuel, a type called `b256` represents hashes and holds a 256-bit value. The Rust SDK represents `b256` as `Bits256(value)` where `value` is a `[u8; 32]`. If your contract method takes a `b256` as input, you must pass a `Bits256([u8; 32])` when calling it from the SDK. Here's an example: ```rust,ignore {{#include ../../../e2e/tests/types_contracts.rs:256_arg}} ``` If you have a hexadecimal value as a string and wish to convert it to `Bits256`, you may do so with `from_hex_str`: ```rust,ignore {{#include ../../../packages/fuels-core/src/types/core/bits.rs:from_hex_str}} ``` ================================================ FILE: docs/src/types/bytes.md ================================================ # `Bytes` In Fuel, a type called `Bytes` represents a collection of tightly-packed bytes. The Rust SDK represents `Bytes` as `Bytes(Vec)`. Here's an example of using `Bytes` in a contract call: ```rust,ignore {{#include ../../../e2e/tests/types_contracts.rs:bytes_arg}} ``` If you have a hexadecimal value as a string and wish to convert it to `Bytes`, you may do so with `from_hex_str`: ```rust,ignore {{#include ../../../packages/fuels-core/src/types/core/bytes.rs:bytes_from_hex_str}} ``` ================================================ FILE: docs/src/types/bytes32.md ================================================ # `Bytes32` In Sway and the FuelVM, `Bytes32` represents hashes. They hold a 256-bit (32-byte) value. `Bytes32` is a wrapper on a 32-sized slice of `u8`: `pub struct Bytes32([u8; 32]);`. These are the main ways of creating a `Bytes32`: ```rust,ignore {{#include ../../../examples/types/src/lib.rs:bytes32}} ``` `Bytes32` also implements the `fmt` module's `Debug`, `Display`, `LowerHex` and `UpperHex` traits. For example, you can get the display and hex representations with: ```rust,ignore {{#include ../../../examples/types/src/lib.rs:bytes32_format}} ``` For a full list of implemented methods and traits, see the [fuel-types documentation](https://docs.rs/fuel-types/latest/fuel_types/struct.Bytes32.html). > **Note:** In Fuel, there's a special type called `b256`, which is similar to `Bytes32`; also used to represent hashes, and it holds a 256-bit value. In Rust, through the SDK, this is represented as `Bits256(value)` where `value` is a `[u8; 32]`. If your contract method takes a `b256` as input, all you need to do is pass a `Bits256([u8; 32])` when calling it from the SDK. ================================================ FILE: docs/src/types/contract-id.md ================================================ # `ContractId` Like `Bytes32`, `ContractId` is a wrapper on `[u8; 32]` with similar methods and implements the same traits (see [fuel-types documentation](https://docs.rs/fuel-types/0.49.0/fuel_types/struct.ContractId.html)). These are the main ways of creating a `ContractId`: ```rust,ignore {{#include ../../../examples/types/src/lib.rs:contract_id}} ``` ================================================ FILE: docs/src/types/conversion.md ================================================ # Converting Types Below you can find examples for common type conversions: - [Convert Between Native Types](#convert-between-native-types) - [Convert to `Bytes32`](#convert-to-bytes32) - [Convert to `Address`](#convert-to-address) - [Convert to `ContractId`](#convert-to-contractid) - [Convert to `Identity`](#convert-to-identity) - [Convert to `AssetId`](#convert-to-assetid) - [Convert to `str`](#convert-to-str) - [Convert to `Bits256`](#convert-to-bits256) - [Convert to `Bytes`](#convert-to-bytes) - [Convert to `B512`](#convert-to-b512) - [Convert to `EvmAddress`](#convert-to-evmaddress) ## Convert Between Native Types You might want to convert between the native types (`Bytes32`, `Address`, `ContractId`, and `AssetId`). Because these types are wrappers on `[u8; 32]`, converting is a matter of dereferencing one and instantiating the other using the dereferenced value. Here's an example: ```rust,ignore {{#include ../../../examples/types/src/lib.rs:type_conversion}} ``` ## Convert to `Bytes32` Convert a `[u8; 32]` array to `Bytes32`: ```rust,ignore {{#include ../../../examples/types/src/lib.rs:array_to_bytes32}} ``` Convert a hex string to `Bytes32`: ```rust,ignore {{#include ../../../examples/types/src/lib.rs:hex_string_to_bytes32}} ``` ## Convert to `Address` Convert a `[u8; 32]` array to an `Address`: ```rust,ignore {{#include ../../../examples/types/src/lib.rs:array_to_address}} ``` Convert a wallet to an `Address`: ```rust,ignore {{#include ../../../examples/wallets/src/lib.rs:wallet_to_address}} ``` Convert a hex string to an `Address`: ```rust,ignore {{#include ../../../examples/types/src/lib.rs:hex_string_to_address}} ``` ## Convert to `ContractId` Convert a `[u8; 32]` array to `ContractId`: ```rust,ignore {{#include ../../../examples/types/src/lib.rs:array_to_contract_id}} ``` Convert a hex string to a `ContractId`: ```rust,ignore {{#include ../../../examples/types/src/lib.rs:string_to_contract_id}} ``` Convert a contract instance to a `ContractId`: ```rust,ignore {{#include ../../../e2e/tests/logs.rs:instance_to_contract_id}} ``` ## Convert to `Identity` Convert an `Address` to an `Identity`: ```rust,ignore {{#include ../../../examples/types/src/lib.rs:address_to_identity}} ``` Convert a `ContractId` to an `Identity`: ```rust,ignore {{#include ../../../examples/types/src/lib.rs:contract_id_to_identity}} ``` ## Convert to `AssetId` Convert a `[u8; 32]` array to an `AssetId`: ```rust,ignore {{#include ../../../examples/types/src/lib.rs:array_to_asset_id}} ``` Convert a hex string to an `AssetId`: ```rust,ignore {{#include ../../../examples/types/src/lib.rs:string_to_asset_id}} ``` ## Convert to `str` Convert a `ContractId` to a `str`: ```rust,ignore {{#include ../../../examples/types/src/lib.rs:contract_id_to_str}} ``` Convert an `Address` to a `str`: ```rust,ignore {{#include ../../../examples/types/src/lib.rs:address_to_str}} ``` Convert an `AssetId` to a `str`: ```rust,ignore {{#include ../../../examples/types/src/lib.rs:asset_id_to_str}} ``` Convert `Bytes32` to a `str`: ```rust,ignore {{#include ../../../examples/types/src/lib.rs:bytes32_to_str}} ``` ## Convert to `Bits256` Convert a hex string to `Bits256`: ```rust,ignore {{#include ../../../packages/fuels-core/src/types/core/bits.rs:hex_str_to_bits256}} ``` Convert a `ContractId` to `Bits256`: ```rust,ignore {{#include ../../../examples/types/src/lib.rs:contract_id_to_bits256}} ``` Convert an `Address` to `Bits256`: ```rust,ignore {{#include ../../../examples/types/src/lib.rs:address_to_bits256}} ``` Convert an `AssetId` to `Bits256`: ```rust,ignore {{#include ../../../examples/types/src/lib.rs:asset_id_to_bits256}} ``` ## Convert to `Bytes` Convert a string to `Bytes`: ```rust,ignore {{#include ../../../packages/fuels-core/src/types/core/bytes.rs:hex_string_to_bytes32}} ``` ## Convert to `B512` Convert two hex strings to `B512`: ```rust,ignore {{#include ../../../e2e/tests/types_contracts.rs:b512_example}} ``` ## Convert to `EvmAddress` Convert a `Bits256` address to an `EvmAddress`: ```rust,ignore {{#include ../../../examples/types/src/lib.rs:b256_to_evm_address}} ``` ================================================ FILE: docs/src/types/custom_types.md ================================================ # Structs and enums The structs and enums you define in your Sway code have equivalents automatically generated by the SDK's `abigen!` macro. For instance, if in your Sway code you have a struct called `CounterConfig` that looks like this: ```rust,ignore struct CounterConfig { dummy: bool, initial_value: u64, } ``` After using the `abigen!` macro, `CounterConfig` will be accessible in your Rust file! Here's an example: ```rust,ignore {{#include ../../../e2e/tests/types_contracts.rs:struct_generation}} ``` You can freely use your custom types (structs or enums) within this scope. That also means passing custom types to functions and receiving custom types from function calls. ## Generics The Fuel Rust SDK supports both generic enums and generic structs. If you're already familiar with Rust, it's your typical `struct MyStruct` type of generics support. For instance, your Sway contract could look like this: ```Rust contract; use std::hash::sha256; struct SimpleGeneric { single_generic_param: T, } abi MyContract { fn struct_w_generic(arg1: SimpleGeneric) -> SimpleGeneric; } impl MyContract for Contract { fn struct_w_generic(arg1: SimpleGeneric) -> SimpleGeneric { let expected = SimpleGeneric { single_generic_param: 123u64, }; assert(arg1.single_generic_param == expected.single_generic_param); expected } } ``` Your Rust code would look like this: ```rust,ignore {{#include ../../../e2e/tests/types_contracts.rs:generic}} ``` ### Unused generic type parameters Sway supports unused generic type parameters when declaring structs/enums: ```Rust struct SomeStruct { field: u64 } enum SomeEnum { One: u64 } ``` If you tried the same in Rust you'd get complaints that `T` and `K` must be used or removed. When generating Rust bindings for such types we make use of the [`PhantomData`](https://doc.rust-lang.org/std/marker/struct.PhantomData.html#unused-type-parameters) type. The generated bindings for the above example would look something like this: ```Rust struct SomeStruct { pub field: u64, pub _unused_generic_0: PhantomData pub _unused_generic_1: PhantomData } enum SomeEnum { One(u64), IgnoreMe(PhantomData, PhantomData) } ``` To lessen the impact to developer experience you may use the `new` method to initialize a structure without bothering with the `PhantomData`s.: ```rust,ignore {{#include ../../../examples/types/src/lib.rs:unused_generics_struct}} ``` If your struct doesn't have any fields we'll also derive `Default`. As for enums all `PhantomData`s are placed inside a new variant called `IgnoreMe` which you'll need to ignore in your matches: ```rust,ignore {{#include ../../../examples/types/src/lib.rs:unused_generics_enum}} ``` ================================================ FILE: docs/src/types/evm_address.md ================================================ # `EvmAddress` In the Rust SDK, Ethereum Virtual Machine (EVM) addresses can be represented with the `EvmAddress` type. Its definition matches with the Sway standard library type with the same name and will be converted accordingly when interacting with contracts: ```rust,ignore {{#include ../../../packages/fuels-core/src/types/core/bits.rs:evm_address}} ``` Here's an example: ```rust,ignore {{#include ../../../e2e/tests/types_contracts.rs:evm_address_arg}} ``` > **Note:** when creating an `EvmAddress` from `Bits256`, the first 12 bytes will be cleared because an EVM address is only 20 bytes long. ================================================ FILE: docs/src/types/index.md ================================================ # Types The FuelVM and Sway have many internal types. These types have equivalents in the SDK. This section discusses these types, how to use them, and how to convert them. ================================================ FILE: docs/src/types/string.md ================================================ # `String` The Rust SDK represents Fuel's `String`s as `SizedAsciiString`, where the generic parameter `LEN` is the length of a given string. This abstraction is necessary because all strings in Fuel and Sway are statically-sized, i.e., you must know the size of the string beforehand. Here's how you can create a simple string using `SizedAsciiString`: ```rust,ignore {{#include ../../../packages/fuels-core/src/types/core/sized_ascii_string.rs:string_simple_example}} ``` To make working with `SizedAsciiString`s easier, you can use `try_into()` to convert from Rust's `String` to `SizedAsciiString`, and you can use `into()` to convert from `SizedAsciiString` to Rust's `String`. Here are a few examples: ```rust,ignore {{#include ../../../packages/fuels-core/src/types/core/sized_ascii_string.rs:conversion}} ``` If your contract's method takes and returns, for instance, a Sway's `str[23]`. When using the SDK, this method will take and return a `SizedAsciiString<23>`. ================================================ FILE: docs/src/types/vectors.md ================================================ # Vectors ## Passing in vectors You can pass a Rust `std::vec::Vec` into your contract method transparently. The following code calls a Sway contract method which accepts a `Vec>`. ```rust,ignore {{#include ../../../e2e/tests/types_contracts.rs:passing_in_vec}} ``` You can use a vector just like you would use any other type -- e.g. a `[Vec; 2]` or a `SomeStruct>` etc. ## Returning vectors Returning vectors from contract methods is supported transparently, with the caveat that you cannot have them nested inside another type. This limitation is temporary. ```rust,ignore {{#include ../../../e2e/tests/types_contracts.rs:returning_vec}} ``` > **Note: you can still interact with contracts containing methods that return vectors nested inside another type, just not interact with the methods themselves** ================================================ FILE: docs/src/wallets/access.md ================================================ # Wallet Access The kinds of operations we can perform with a `Wallet` instance depend on whether or not the wallet has a signer attached to it. In order to differentiate between `Wallet` instances that have a signer and those that do not, we use the `Wallet>` and `Wallet` types respectively. ## Wallet States The `Wallet>` type represents a wallet that has a signer. A wallet must be of type `Wallet>` in order to perform operations that involve signing messages or transactions. You can learn more about signing [here](./signing.md). The `Wallet` type represents a wallet without a signer. Instead, `Wallet` only knows its public address. A `Wallet` cannot be used to sign transactions, however it may still perform a whole suite of useful operations including listing transactions, assets, querying balances, and so on. ## Transitioning States A `Wallet>` instance can be locked using the `lock` method: ```rust,ignore let wallet_locked = wallet_unlocked.lock(); ``` ## Design Guidelines When designing APIs that accept a wallet as an input, we should think carefully about the kind of access that we require. API developers should aim to minimise their usage of `Wallet>` in order to ensure signers are stored in memory no longer than necessary to reduce the surface area for attacks and vulnerabilities in downstream libraries and applications. ================================================ FILE: docs/src/wallets/checking-balances-and-coins.md ================================================ # Checking balances and coins In the Fuel network, each UTXO corresponds to a unique _coin_, and said _coin_ has a corresponding _amount_ (the same way a dollar bill has either 10$ or 5$ face value). So, when you want to query the balance for a given asset ID, you want to query the sum of the amount in each unspent coin. This querying is done very easily with a wallet: ```rust,ignore {{#include ../../../examples/wallets/src/lib.rs:get_asset_balance}} ``` If you want to query all the balances (i.e., get the balance for each asset ID in that wallet), you can use the `get_balances` method: ```rust,ignore {{#include ../../../examples/wallets/src/lib.rs:get_balances}} ``` The return type is a `HashMap`, where the key is the _asset ID's_ hex string, and the value is the corresponding balance. For example, we can get the base asset balance with: ```rust,ignore {{#include ../../../examples/wallets/src/lib.rs:get_balance_hashmap}} ``` ================================================ FILE: docs/src/wallets/fake_signer.md ================================================ # Fake signer (impersonating another account) To facilitate account impersonation, the Rust SDK provides the `FakeSigner`. We can use it to simulate ownership of assets held by an account with a given address. This also implies that we can impersonate contract calls from that address. A wallet with a `FakeSigner` will only succeed in unlocking assets if the network is set up with `utxo_validation = false`. ```rust,ignore {{#include ../../../examples/contracts/src/lib.rs:utxo_validation_off}} ``` ```rust,ignore {{#include ../../../examples/contracts/src/lib.rs:utxo_validation_off_node_start}} ``` ```rust,ignore {{#include ../../../examples/contracts/src/lib.rs:contract_call_impersonation}} ``` ================================================ FILE: docs/src/wallets/index.md ================================================ # Wallets Wallets serve as a centralized interface for all account-related behaviors. They allow you to: - **Inspect UTXOs:** Check available coins for spending. - **Prepare and send transactions:** Build, sign, and submit transfers. - **Manage network fees:** Pay for transaction execution and contract deployment. Every wallet requires a **provider** to communicate with the network. --- ## Types of Wallets There are two primary types of wallets available in the SDK: ### [Locked Wallets](./access.md) - **Purpose:** Used for read-only operations. - **Interface:** Implements the [`ViewOnlyAccount`](../accounts.md) trait. - **Use Cases:** Checking balances, viewing UTXOs, and monitoring transactions without the ability to sign or submit transactions. ### [Unlocked Wallets](./access.md) - **Purpose:** Supports full account functionality. - **Interface:** Implements the [`ViewOnlyAccount`](../accounts.md) and [`Account`](../accounts.md) traits. - **Additional Requirement:** In addition to a provider, an unlocked wallet must include a **signer**. - **Use Cases:** Transferring funds, signing messages, submitting transactions, and performing state-changing operations. --- ## Signer Options The SDK offers multiple signing methods to suit different scenarios: - [**Private Key Signer:**](./private_key_signer.md) Use when you have direct access to your account’s private key. - [**AWS KMS Signer:**](./kms.md) Delegate signing operations to AWS Key Management Service, enhancing key security by offloading cryptographic operations. - [**Google KMS Signer:**](./kms.md) Similar to AWS KMS, this option delegates signing to Google’s Key Management Service. - [**Fake Signer:**](./fake_signer.md) Generates dummy signatures, which is useful for impersonation while testing. Only possible when using a network that does not enforce signature validation. --- ================================================ FILE: docs/src/wallets/keystore.md ================================================ # Encrypting and Storing Keys The code below shows how to: - Encrypt and store your key using a master password. - Ensure that the key can be retrieved later with the proper credentials. ```rust,ignore {{#include ../../../examples/wallets/src/lib.rs:create_and_store_mnemonic_key}} ``` ================================================ FILE: docs/src/wallets/kms.md ================================================ # Using KMS Wallets Key Management Service (KMS) is a robust and secure solution for managing cryptographic keys for your Fuel wallets. Instead of keeping private keys on your local system, KMS Wallets leverage secure infrastructure to handle both key storage and signing operations. The SDK provides signers for AWS and Google KMS. Below is an example of how to initialize a wallet with a AWS KMS signer: ```rust,ignore {{#include ../../../e2e/tests/aws.rs:use_kms_wallet}} ``` ================================================ FILE: docs/src/wallets/private_key_signer.md ================================================ # Using private keys to create wallets ## Directly from a private key An example of how to create a wallet that uses a private key signer: ```rust,ignore {{#include ../../../examples/wallets/src/lib.rs:create_wallet_from_secret_key}} ``` There is also a helper for generating a wallet with a random private key signer: ```rust,ignore {{#include ../../../examples/wallets/src/lib.rs:create_random_wallet}} ``` ## From a mnemonic phrase A mnemonic phrase is a cryptographically generated sequence of words used to create a master seed. This master seed, when combined with a [derivation path](https://thebitcoinmanual.com/articles/btc-derivation-path/), enables the generation of one or more specific private keys. The derivation path acts as a roadmap within the [hierarchical deterministic (HD) wallet structure](https://www.ledger.com/academy/crypto/what-are-hierarchical-deterministic-hd-wallets), determining which branch of the key tree produces the desired private key. ```rust,ignore {{#include ../../../examples/wallets/src/lib.rs:create_wallet_from_mnemonic}} ``` ## Security Best Practices - **Never Share Sensitive Information:** Do not disclose your private key or mnemonic phrase to anyone. - **Secure Storage:** When storing keys on disk, **always encrypt** them (the SDK provides a [`Keystore`](./keystore.md). This applies to both plain private/secret keys and mnemonic phrases. ================================================ FILE: docs/src/wallets/signing.md ================================================ # Signing An example of how you might sign a message using any of the SDK signers (or your own, custom ones, that implement `Signer`): ```rust,ignore {{#include ../../../packages/fuels-accounts/src/signers/private_key.rs:sign_message}} ``` ## Adding `Signers` to a transaction builder Every signed resource in the inputs needs to have a witness index that points to a valid witness. Changing the witness index inside an input will change the transaction ID. This means that we need to set all witness indexes before finally signing the transaction. We have to make sure that the witness indexes and the order of the witnesses are correct. To automate this process, the SDK will keep track of the signers in the transaction builder and resolve the final transaction automatically. This is done by storing signers until the final transaction is built. Below is a full example of how to create a transaction builder and add signers to it. > Note: When you add a `Signer` to a transaction builder, the signer is stored inside it and the transaction will not be resolved until you call `build()`! ```rust,ignore {{#include ../../../packages/fuels-accounts/src/account.rs:sign_tb}} ``` ## Signing a built transaction If you have a built transaction and want to add a signature, you can use the `sign_with` method. ```rust,ignore {{#include ../../../e2e/tests/contracts.rs:tx_sign_with}} ``` ================================================ FILE: docs/src/wallets/test-wallets.md ================================================ # Setting up test wallets You'll often want to create one or more test wallets when testing your contracts. Here's how to do it. ## Setting up multiple test wallets If you need multiple test wallets, they can be set up using the `launch_custom_provider_and_get_wallets` method. ```rust,ignore {{#include ../../../examples/wallets/src/lib.rs:multiple_wallets_helper}} ``` You can customize your test wallets via `WalletsConfig`. ```rust,ignore {{#include ../../../examples/wallets/src/lib.rs:setup_5_wallets}} ``` >**Note** Wallets generated with `launch_provider_and_get_wallet` or `launch_custom_provider_and_get_wallets` will have deterministic addresses. ## Setting up a test wallet with multiple random assets You can create a test wallet containing multiple assets (including the base asset to pay for gas). ```rust,ignore {{#include ../../../examples/wallets/src/lib.rs:multiple_assets_wallet}} ``` - coins: `Vec<(UtxoId, Coin)>` has `num_assets` * `coins_per_assets` coins (UTXOs) - asset_ids: `Vec` contains the `num_assets` randomly generated `AssetId`s (always includes the base asset) ## Setting up a test wallet with multiple custom assets You can also create assets with specific `AssetId`s, coin amounts, and number of coins. ```rust,ignore {{#include ../../../examples/wallets/src/lib.rs:custom_assets_wallet}} ``` This can also be achieved directly with the `WalletsConfig`. ```rust,ignore {{#include ../../../examples/wallets/src/lib.rs:custom_assets_wallet_short}} ``` >**Note** In this case, you need to manually add the base asset and the corresponding number of >coins and coin amount ## Setting up assets The Fuel blockchain holds many different assets; you can create your asset with its unique `AssetId` or create random assets for testing purposes. You can use only one asset to pay for transaction fees and gas: the base asset, whose `AssetId` is `0x000...0`, a 32-byte zeroed value. For testing purposes, you can configure coins and amounts for assets. You can use `setup_multiple_assets_coins`: ```rust,ignore {{#include ../../../examples/wallets/src/lib.rs:multiple_assets_coins}} ``` >**Note** If setting up multiple assets, one of these assets will always be the base asset. If you want to create coins only with the base asset, then you can use: ```rust,ignore {{#include ../../../examples/providers/src/lib.rs:setup_single_asset}} ``` >**Note** Choosing a large number of coins and assets for `setup_multiple_assets_coins` or `setup_single_asset_coins` can lead to considerable runtime for these methods. This will be improved in the future but for now, we recommend using up to **1_000_000** coins, or **1000** coins and assets simultaneously. ================================================ FILE: docs/theme/highlight.js ================================================ /*! Highlight.js v11.3.1 (git: 2a972d8658) (c) 2006-2022 Ivan Sagalaev and other contributors License: BSD-3-Clause */ var hljs=function(){"use strict";var e={exports:{}};function t(e){ return e instanceof Map?e.clear=e.delete=e.set=()=>{ throw Error("map is read-only")}:e instanceof Set&&(e.add=e.clear=e.delete=()=>{ throw Error("set is read-only") }),Object.freeze(e),Object.getOwnPropertyNames(e).forEach((n=>{var s=e[n] ;"object"!=typeof s||Object.isFrozen(s)||t(s)})),e} e.exports=t,e.exports.default=t;var n=e.exports;class s{constructor(e){ void 0===e.data&&(e.data={}),this.data=e.data,this.isMatchIgnored=!1} ignoreMatch(){this.isMatchIgnored=!0}}function i(e){ return e.replace(/&/g,"&").replace(//g,">").replace(/"/g,""").replace(/'/g,"'") }function a(e,...t){const n=Object.create(null);for(const t in e)n[t]=e[t] ;return t.forEach((e=>{for(const t in e)n[t]=e[t]})),n}const r=e=>!!e.kind ;class o{constructor(e,t){ this.buffer="",this.classPrefix=t.classPrefix,e.walk(this)}addText(e){ this.buffer+=i(e)}openNode(e){if(!r(e))return;let t=e.kind ;t=e.sublanguage?"language-"+t:((e,{prefix:t})=>{if(e.includes(".")){ const n=e.split(".") ;return[`${t}${n.shift()}`,...n.map(((e,t)=>`${e}${"_".repeat(t+1)}`))].join(" ") }return`${t}${e}`})(t,{prefix:this.classPrefix}),this.span(t)}closeNode(e){ r(e)&&(this.buffer+="")}value(){return this.buffer}span(e){ this.buffer+=``}}class l{constructor(){this.rootNode={ children:[]},this.stack=[this.rootNode]}get top(){ return this.stack[this.stack.length-1]}get root(){return this.rootNode}add(e){ this.top.children.push(e)}openNode(e){const t={kind:e,children:[]} ;this.add(t),this.stack.push(t)}closeNode(){ if(this.stack.length>1)return this.stack.pop()}closeAllNodes(){ for(;this.closeNode(););}toJSON(){return JSON.stringify(this.rootNode,null,4)} walk(e){return this.constructor._walk(e,this.rootNode)}static _walk(e,t){ return"string"==typeof t?e.addText(t):t.children&&(e.openNode(t), t.children.forEach((t=>this._walk(e,t))),e.closeNode(t)),e}static _collapse(e){ "string"!=typeof e&&e.children&&(e.children.every((e=>"string"==typeof e))?e.children=[e.children.join("")]:e.children.forEach((e=>{ l._collapse(e)})))}}class c extends l{constructor(e){super(),this.options=e} addKeyword(e,t){""!==e&&(this.openNode(t),this.addText(e),this.closeNode())} addText(e){""!==e&&this.add(e)}addSublanguage(e,t){const n=e.root ;n.kind=t,n.sublanguage=!0,this.add(n)}toHTML(){ return new o(this,this.options).value()}finalize(){return!0}}function d(e){ return e?"string"==typeof e?e:e.source:null}function u(e){return b("(?=",e,")")} function g(e){return b("(?:",e,")*")}function h(e){return b("(?:",e,")?")} function b(...e){return e.map((e=>d(e))).join("")}function p(...e){const t=(e=>{ const t=e[e.length-1] ;return"object"==typeof t&&t.constructor===Object?(e.splice(e.length-1,1),t):{} })(e);return"("+(t.capture?"":"?:")+e.map((e=>d(e))).join("|")+")"} function f(e){return RegExp(e.toString()+"|").exec("").length-1} const m=/\[(?:[^\\\]]|\\.)*\]|\(\??|\\([1-9][0-9]*)|\\./ ;function E(e,{joinWith:t}){let n=0;return e.map((e=>{n+=1;const t=n ;let s=d(e),i="";for(;s.length>0;){const e=m.exec(s);if(!e){i+=s;break} i+=s.substring(0,e.index), s=s.substring(e.index+e[0].length),"\\"===e[0][0]&&e[1]?i+="\\"+(Number(e[1])+t):(i+=e[0], "("===e[0]&&n++)}return i})).map((e=>`(${e})`)).join(t)} const _="[a-zA-Z]\\w*",w="[a-zA-Z_]\\w*",y="\\b\\d+(\\.\\d+)?",N="(-?)(\\b0[xX][a-fA-F0-9]+|(\\b\\d+(\\.\\d*)?|\\.\\d+)([eE][-+]?\\d+)?)",x="\\b(0b[01]+)",k={ begin:"\\\\[\\s\\S]",relevance:0},v={scope:"string",begin:"'",end:"'", illegal:"\\n",contains:[k]},O={scope:"string",begin:'"',end:'"',illegal:"\\n", contains:[k]},S=(e,t,n={})=>{const s=a({scope:"comment",begin:e,end:t, contains:[]},n);s.contains.push({scope:"doctag", begin:"[ ]*(?=(TODO|FIXME|NOTE|BUG|OPTIMIZE|HACK|XXX):)", end:/(TODO|FIXME|NOTE|BUG|OPTIMIZE|HACK|XXX):/,excludeBegin:!0,relevance:0}) ;const i=p("I","a","is","so","us","to","at","if","in","it","on",/[A-Za-z]+['](d|ve|re|ll|t|s|n)/,/[A-Za-z]+[-][a-z]+/,/[A-Za-z][a-z]{2,}/) ;return s.contains.push({begin:b(/[ ]+/,"(",i,/[.]?[:]?([.][ ]|[ ])/,"){3}")}),s },R=S("//","$"),M=S("/\\*","\\*/"),I=S("#","$");var A=Object.freeze({ __proto__:null,MATCH_NOTHING_RE:/\b\B/,IDENT_RE:_,UNDERSCORE_IDENT_RE:w, NUMBER_RE:y,C_NUMBER_RE:N,BINARY_NUMBER_RE:x, RE_STARTERS_RE:"!|!=|!==|%|%=|&|&&|&=|\\*|\\*=|\\+|\\+=|,|-|-=|/=|/|:|;|<<|<<=|<=|<|===|==|=|>>>=|>>=|>=|>>>|>>|>|\\?|\\[|\\{|\\(|\\^|\\^=|\\||\\|=|\\|\\||~", SHEBANG:(e={})=>{const t=/^#![ ]*\// ;return e.binary&&(e.begin=b(t,/.*\b/,e.binary,/\b.*/)),a({scope:"meta",begin:t, end:/$/,relevance:0,"on:begin":(e,t)=>{0!==e.index&&t.ignoreMatch()}},e)}, BACKSLASH_ESCAPE:k,APOS_STRING_MODE:v,QUOTE_STRING_MODE:O,PHRASAL_WORDS_MODE:{ begin:/\b(a|an|the|are|I'm|isn't|don't|doesn't|won't|but|just|should|pretty|simply|enough|gonna|going|wtf|so|such|will|you|your|they|like|more)\b/ },COMMENT:S,C_LINE_COMMENT_MODE:R,C_BLOCK_COMMENT_MODE:M,HASH_COMMENT_MODE:I, NUMBER_MODE:{scope:"number",begin:y,relevance:0},C_NUMBER_MODE:{scope:"number", begin:N,relevance:0},BINARY_NUMBER_MODE:{scope:"number",begin:x,relevance:0}, REGEXP_MODE:{begin:/(?=\/[^/\n]*\/)/,contains:[{scope:"regexp",begin:/\//, end:/\/[gimuy]*/,illegal:/\n/,contains:[k,{begin:/\[/,end:/\]/,relevance:0, contains:[k]}]}]},TITLE_MODE:{scope:"title",begin:_,relevance:0}, UNDERSCORE_TITLE_MODE:{scope:"title",begin:w,relevance:0},METHOD_GUARD:{ begin:"\\.\\s*[a-zA-Z_]\\w*",relevance:0},END_SAME_AS_BEGIN:e=>Object.assign(e,{ "on:begin":(e,t)=>{t.data._beginMatch=e[1]},"on:end":(e,t)=>{ t.data._beginMatch!==e[1]&&t.ignoreMatch()}})});function T(e,t){ "."===e.input[e.index-1]&&t.ignoreMatch()}function D(e,t){ void 0!==e.className&&(e.scope=e.className,delete e.className)}function j(e,t){ t&&e.beginKeywords&&(e.begin="\\b("+e.beginKeywords.split(" ").join("|")+")(?!\\.)(?=\\b|\\s)", e.__beforeBegin=T,e.keywords=e.keywords||e.beginKeywords,delete e.beginKeywords, void 0===e.relevance&&(e.relevance=0))}function C(e,t){ Array.isArray(e.illegal)&&(e.illegal=p(...e.illegal))}function B(e,t){ if(e.match){ if(e.begin||e.end)throw Error("begin & end are not supported with match") ;e.begin=e.match,delete e.match}}function L(e,t){ void 0===e.relevance&&(e.relevance=1)}const z=(e,t)=>{if(!e.beforeMatch)return ;if(e.starts)throw Error("beforeMatch cannot be used with starts") ;const n=Object.assign({},e);Object.keys(e).forEach((t=>{delete e[t] })),e.keywords=n.keywords,e.begin=b(n.beforeMatch,u(n.begin)),e.starts={ relevance:0,contains:[Object.assign(n,{endsParent:!0})] },e.relevance=0,delete n.beforeMatch },$=["of","and","for","in","not","or","if","then","parent","list","value"] ;function U(e,t,n="keyword"){const s=Object.create(null) ;return"string"==typeof e?i(n,e.split(" ")):Array.isArray(e)?i(n,e):Object.keys(e).forEach((n=>{ Object.assign(s,U(e[n],t,n))})),s;function i(e,n){ t&&(n=n.map((e=>e.toLowerCase()))),n.forEach((t=>{const n=t.split("|") ;s[n[0]]=[e,H(n[0],n[1])]}))}}function H(e,t){ return t?Number(t):(e=>$.includes(e.toLowerCase()))(e)?0:1}const P={},K=e=>{ console.error(e)},G=(e,...t)=>{console.log("WARN: "+e,...t)},F=(e,t)=>{ P[`${e}/${t}`]||(console.log(`Deprecated as of ${e}. ${t}`),P[`${e}/${t}`]=!0) },Z=Error();function W(e,t,{key:n}){let s=0;const i=e[n],a={},r={} ;for(let e=1;e<=t.length;e++)r[e+s]=i[e],a[e+s]=!0,s+=f(t[e-1]) ;e[n]=r,e[n]._emit=a,e[n]._multi=!0}function X(e){(e=>{ e.scope&&"object"==typeof e.scope&&null!==e.scope&&(e.beginScope=e.scope, delete e.scope)})(e),"string"==typeof e.beginScope&&(e.beginScope={ _wrap:e.beginScope}),"string"==typeof e.endScope&&(e.endScope={_wrap:e.endScope }),(e=>{if(Array.isArray(e.begin)){ if(e.skip||e.excludeBegin||e.returnBegin)throw K("skip, excludeBegin, returnBegin not compatible with beginScope: {}"), Z ;if("object"!=typeof e.beginScope||null===e.beginScope)throw K("beginScope must be object"), Z;W(e,e.begin,{key:"beginScope"}),e.begin=E(e.begin,{joinWith:""})}})(e),(e=>{ if(Array.isArray(e.end)){ if(e.skip||e.excludeEnd||e.returnEnd)throw K("skip, excludeEnd, returnEnd not compatible with endScope: {}"), Z ;if("object"!=typeof e.endScope||null===e.endScope)throw K("endScope must be object"), Z;W(e,e.end,{key:"endScope"}),e.end=E(e.end,{joinWith:""})}})(e)}function q(e){ function t(t,n){ return RegExp(d(t),"m"+(e.case_insensitive?"i":"")+(e.unicodeRegex?"u":"")+(n?"g":"")) }class n{constructor(){ this.matchIndexes={},this.regexes=[],this.matchAt=1,this.position=0} addRule(e,t){ t.position=this.position++,this.matchIndexes[this.matchAt]=t,this.regexes.push([t,e]), this.matchAt+=f(e)+1}compile(){0===this.regexes.length&&(this.exec=()=>null) ;const e=this.regexes.map((e=>e[1]));this.matcherRe=t(E(e,{joinWith:"|" }),!0),this.lastIndex=0}exec(e){this.matcherRe.lastIndex=this.lastIndex ;const t=this.matcherRe.exec(e);if(!t)return null ;const n=t.findIndex(((e,t)=>t>0&&void 0!==e)),s=this.matchIndexes[n] ;return t.splice(0,n),Object.assign(t,s)}}class s{constructor(){ this.rules=[],this.multiRegexes=[], this.count=0,this.lastIndex=0,this.regexIndex=0}getMatcher(e){ if(this.multiRegexes[e])return this.multiRegexes[e];const t=new n ;return this.rules.slice(e).forEach((([e,n])=>t.addRule(e,n))), t.compile(),this.multiRegexes[e]=t,t}resumingScanAtSamePosition(){ return 0!==this.regexIndex}considerAll(){this.regexIndex=0}addRule(e,t){ this.rules.push([e,t]),"begin"===t.type&&this.count++}exec(e){ const t=this.getMatcher(this.regexIndex);t.lastIndex=this.lastIndex ;let n=t.exec(e) ;if(this.resumingScanAtSamePosition())if(n&&n.index===this.lastIndex);else{ const t=this.getMatcher(0);t.lastIndex=this.lastIndex+1,n=t.exec(e)} return n&&(this.regexIndex+=n.position+1, this.regexIndex===this.count&&this.considerAll()),n}} if(e.compilerExtensions||(e.compilerExtensions=[]), e.contains&&e.contains.includes("self"))throw Error("ERR: contains `self` is not supported at the top-level of a language. See documentation.") ;return e.classNameAliases=a(e.classNameAliases||{}),function n(i,r){const o=i ;if(i.isCompiled)return o ;[D,B,X,z].forEach((e=>e(i,r))),e.compilerExtensions.forEach((e=>e(i,r))), i.__beforeBegin=null,[j,C,L].forEach((e=>e(i,r))),i.isCompiled=!0;let l=null ;return"object"==typeof i.keywords&&i.keywords.$pattern&&(i.keywords=Object.assign({},i.keywords), l=i.keywords.$pattern, delete i.keywords.$pattern),l=l||/\w+/,i.keywords&&(i.keywords=U(i.keywords,e.case_insensitive)), o.keywordPatternRe=t(l,!0), r&&(i.begin||(i.begin=/\B|\b/),o.beginRe=t(o.begin),i.end||i.endsWithParent||(i.end=/\B|\b/), i.end&&(o.endRe=t(o.end)), o.terminatorEnd=d(o.end)||"",i.endsWithParent&&r.terminatorEnd&&(o.terminatorEnd+=(i.end?"|":"")+r.terminatorEnd)), i.illegal&&(o.illegalRe=t(i.illegal)), i.contains||(i.contains=[]),i.contains=[].concat(...i.contains.map((e=>(e=>(e.variants&&!e.cachedVariants&&(e.cachedVariants=e.variants.map((t=>a(e,{ variants:null},t)))),e.cachedVariants?e.cachedVariants:V(e)?a(e,{ starts:e.starts?a(e.starts):null }):Object.isFrozen(e)?a(e):e))("self"===e?i:e)))),i.contains.forEach((e=>{n(e,o) })),i.starts&&n(i.starts,r),o.matcher=(e=>{const t=new s ;return e.contains.forEach((e=>t.addRule(e.begin,{rule:e,type:"begin" }))),e.terminatorEnd&&t.addRule(e.terminatorEnd,{type:"end" }),e.illegal&&t.addRule(e.illegal,{type:"illegal"}),t})(o),o}(e)}function V(e){ return!!e&&(e.endsWithParent||V(e.starts))}class Q extends Error{ constructor(e,t){super(e),this.name="HTMLInjectionError",this.html=t}} const J=i,Y=a,ee=Symbol("nomatch");var te=(e=>{ const t=Object.create(null),i=Object.create(null),a=[];let r=!0 ;const o="Could not find the language '{}', did you forget to load/include a language module?",l={ disableAutodetect:!0,name:"Plain text",contains:[]};let d={ ignoreUnescapedHTML:!1,throwUnescapedHTML:!1,noHighlightRe:/^(no-?highlight)$/i, languageDetectRe:/\blang(?:uage)?-([\w-]+)\b/i,classPrefix:"hljs-", cssSelector:"pre code",languages:null,__emitter:c};function f(e){ return d.noHighlightRe.test(e)}function m(e,t,n){let s="",i="" ;"object"==typeof t?(s=e, n=t.ignoreIllegals,i=t.language):(F("10.7.0","highlight(lang, code, ...args) has been deprecated."), F("10.7.0","Please use highlight(code, options) instead.\nhttps://github.com/highlightjs/highlight.js/issues/2277"), i=e,s=t),void 0===n&&(n=!0);const a={code:s,language:i};O("before:highlight",a) ;const r=a.result?a.result:E(a.language,a.code,n) ;return r.code=a.code,O("after:highlight",r),r}function E(e,n,i,a){ const l=Object.create(null);function c(){if(!v.keywords)return void S.addText(R) ;let e=0;v.keywordPatternRe.lastIndex=0;let t=v.keywordPatternRe.exec(R),n="" ;for(;t;){n+=R.substring(e,t.index) ;const i=y.case_insensitive?t[0].toLowerCase():t[0],a=(s=i,v.keywords[s]);if(a){ const[e,s]=a ;if(S.addText(n),n="",l[i]=(l[i]||0)+1,l[i]<=7&&(M+=s),e.startsWith("_"))n+=t[0];else{ const n=y.classNameAliases[e]||e;S.addKeyword(t[0],n)}}else n+=t[0] ;e=v.keywordPatternRe.lastIndex,t=v.keywordPatternRe.exec(R)}var s ;n+=R.substr(e),S.addText(n)}function u(){null!=v.subLanguage?(()=>{ if(""===R)return;let e=null;if("string"==typeof v.subLanguage){ if(!t[v.subLanguage])return void S.addText(R) ;e=E(v.subLanguage,R,!0,O[v.subLanguage]),O[v.subLanguage]=e._top }else e=_(R,v.subLanguage.length?v.subLanguage:null) ;v.relevance>0&&(M+=e.relevance),S.addSublanguage(e._emitter,e.language) })():c(),R=""}function g(e,t){let n=1;for(;void 0!==t[n];){if(!e._emit[n]){n++ ;continue}const s=y.classNameAliases[e[n]]||e[n],i=t[n] ;s?S.addKeyword(i,s):(R=i,c(),R=""),n++}}function h(e,t){ return e.scope&&"string"==typeof e.scope&&S.openNode(y.classNameAliases[e.scope]||e.scope), e.beginScope&&(e.beginScope._wrap?(S.addKeyword(R,y.classNameAliases[e.beginScope._wrap]||e.beginScope._wrap), R=""):e.beginScope._multi&&(g(e.beginScope,t),R="")),v=Object.create(e,{parent:{ value:v}}),v}function b(e,t,n){let i=((e,t)=>{const n=e&&e.exec(t) ;return n&&0===n.index})(e.endRe,n);if(i){if(e["on:end"]){const n=new s(e) ;e["on:end"](t,n),n.isMatchIgnored&&(i=!1)}if(i){ for(;e.endsParent&&e.parent;)e=e.parent;return e}} if(e.endsWithParent)return b(e.parent,t,n)}function p(e){ return 0===v.matcher.regexIndex?(R+=e[0],1):(T=!0,0)}function f(e){ const t=e[0],s=n.substr(e.index),i=b(v,e,s);if(!i)return ee;const a=v ;v.endScope&&v.endScope._wrap?(u(), S.addKeyword(t,v.endScope._wrap)):v.endScope&&v.endScope._multi?(u(), g(v.endScope,e)):a.skip?R+=t:(a.returnEnd||a.excludeEnd||(R+=t), u(),a.excludeEnd&&(R=t));do{ v.scope&&S.closeNode(),v.skip||v.subLanguage||(M+=v.relevance),v=v.parent }while(v!==i.parent);return i.starts&&h(i.starts,e),a.returnEnd?0:t.length} let m={};function w(t,a){const o=a&&a[0];if(R+=t,null==o)return u(),0 ;if("begin"===m.type&&"end"===a.type&&m.index===a.index&&""===o){ if(R+=n.slice(a.index,a.index+1),!r){const t=Error(`0 width match regex (${e})`) ;throw t.languageName=e,t.badRule=m.rule,t}return 1} if(m=a,"begin"===a.type)return(e=>{ const t=e[0],n=e.rule,i=new s(n),a=[n.__beforeBegin,n["on:begin"]] ;for(const n of a)if(n&&(n(e,i),i.isMatchIgnored))return p(t) ;return n.skip?R+=t:(n.excludeBegin&&(R+=t), u(),n.returnBegin||n.excludeBegin||(R=t)),h(n,e),n.returnBegin?0:t.length})(a) ;if("illegal"===a.type&&!i){ const e=Error('Illegal lexeme "'+o+'" for mode "'+(v.scope||"")+'"') ;throw e.mode=v,e}if("end"===a.type){const e=f(a);if(e!==ee)return e} if("illegal"===a.type&&""===o)return 1 ;if(A>1e5&&A>3*a.index)throw Error("potential infinite loop, way more iterations than matches") ;return R+=o,o.length}const y=x(e) ;if(!y)throw K(o.replace("{}",e)),Error('Unknown language: "'+e+'"') ;const N=q(y);let k="",v=a||N;const O={},S=new d.__emitter(d);(()=>{const e=[] ;for(let t=v;t!==y;t=t.parent)t.scope&&e.unshift(t.scope) ;e.forEach((e=>S.openNode(e)))})();let R="",M=0,I=0,A=0,T=!1;try{ for(v.matcher.considerAll();;){ A++,T?T=!1:v.matcher.considerAll(),v.matcher.lastIndex=I ;const e=v.matcher.exec(n);if(!e)break;const t=w(n.substring(I,e.index),e) ;I=e.index+t}return w(n.substr(I)),S.closeAllNodes(),S.finalize(),k=S.toHTML(),{ language:e,value:k,relevance:M,illegal:!1,_emitter:S,_top:v}}catch(t){ if(t.message&&t.message.includes("Illegal"))return{language:e,value:J(n), illegal:!0,relevance:0,_illegalBy:{message:t.message,index:I, context:n.slice(I-100,I+100),mode:t.mode,resultSoFar:k},_emitter:S};if(r)return{ language:e,value:J(n),illegal:!1,relevance:0,errorRaised:t,_emitter:S,_top:v} ;throw t}}function _(e,n){n=n||d.languages||Object.keys(t);const s=(e=>{ const t={value:J(e),illegal:!1,relevance:0,_top:l,_emitter:new d.__emitter(d)} ;return t._emitter.addText(e),t})(e),i=n.filter(x).filter(v).map((t=>E(t,e,!1))) ;i.unshift(s);const a=i.sort(((e,t)=>{ if(e.relevance!==t.relevance)return t.relevance-e.relevance ;if(e.language&&t.language){if(x(e.language).supersetOf===t.language)return 1 ;if(x(t.language).supersetOf===e.language)return-1}return 0})),[r,o]=a,c=r ;return c.secondBest=o,c}function w(e){let t=null;const n=(e=>{ let t=e.className+" ";t+=e.parentNode?e.parentNode.className:"" ;const n=d.languageDetectRe.exec(t);if(n){const t=x(n[1]) ;return t||(G(o.replace("{}",n[1])), G("Falling back to no-highlight mode for this block.",e)),t?n[1]:"no-highlight"} return t.split(/\s+/).find((e=>f(e)||x(e)))})(e);if(f(n))return ;if(O("before:highlightElement",{el:e,language:n }),e.children.length>0&&(d.ignoreUnescapedHTML||(console.warn("One of your code blocks includes unescaped HTML. This is a potentially serious security risk."), console.warn("https://github.com/highlightjs/highlight.js/issues/2886"), console.warn(e)), d.throwUnescapedHTML))throw new Q("One of your code blocks includes unescaped HTML.",e.innerHTML) ;t=e;const s=t.textContent,a=n?m(s,{language:n,ignoreIllegals:!0}):_(s) ;e.innerHTML=a.value,((e,t,n)=>{const s=t&&i[t]||n ;e.classList.add("hljs"),e.classList.add("language-"+s) })(e,n,a.language),e.result={language:a.language,re:a.relevance, relevance:a.relevance},a.secondBest&&(e.secondBest={ language:a.secondBest.language,relevance:a.secondBest.relevance }),O("after:highlightElement",{el:e,result:a,text:s})}let y=!1;function N(){ "loading"!==document.readyState?document.querySelectorAll(d.cssSelector).forEach(w):y=!0 }function x(e){return e=(e||"").toLowerCase(),t[e]||t[i[e]]} function k(e,{languageName:t}){"string"==typeof e&&(e=[e]),e.forEach((e=>{ i[e.toLowerCase()]=t}))}function v(e){const t=x(e) ;return t&&!t.disableAutodetect}function O(e,t){const n=e;a.forEach((e=>{ e[n]&&e[n](t)}))} "undefined"!=typeof window&&window.addEventListener&&window.addEventListener("DOMContentLoaded",(()=>{ y&&N()}),!1),Object.assign(e,{highlight:m,highlightAuto:_,highlightAll:N, highlightElement:w, highlightBlock:e=>(F("10.7.0","highlightBlock will be removed entirely in v12.0"), F("10.7.0","Please use highlightElement now."),w(e)),configure:e=>{d=Y(d,e)}, initHighlighting:()=>{ N(),F("10.6.0","initHighlighting() deprecated. Use highlightAll() now.")}, initHighlightingOnLoad:()=>{ N(),F("10.6.0","initHighlightingOnLoad() deprecated. Use highlightAll() now.") },registerLanguage:(n,s)=>{let i=null;try{i=s(e)}catch(e){ if(K("Language definition for '{}' could not be registered.".replace("{}",n)), !r)throw e;K(e),i=l} i.name||(i.name=n),t[n]=i,i.rawDefinition=s.bind(null,e),i.aliases&&k(i.aliases,{ languageName:n})},unregisterLanguage:e=>{delete t[e] ;for(const t of Object.keys(i))i[t]===e&&delete i[t]}, listLanguages:()=>Object.keys(t),getLanguage:x,registerAliases:k, autoDetection:v,inherit:Y,addPlugin:e=>{(e=>{ e["before:highlightBlock"]&&!e["before:highlightElement"]&&(e["before:highlightElement"]=t=>{ e["before:highlightBlock"](Object.assign({block:t.el},t)) }),e["after:highlightBlock"]&&!e["after:highlightElement"]&&(e["after:highlightElement"]=t=>{ e["after:highlightBlock"](Object.assign({block:t.el},t))})})(e),a.push(e)} }),e.debugMode=()=>{r=!1},e.safeMode=()=>{r=!0 },e.versionString="11.3.1",e.regex={concat:b,lookahead:u,either:p,optional:h, anyNumberOfTimes:g};for(const e in A)"object"==typeof A[e]&&n(A[e]) ;return Object.assign(e,A),e})({}),ne=Object.freeze({__proto__:null, grmr_sway:e=>{const t={className:"title.function.invoke",relevance:0, begin:b(/\b/,/(?!let\b)/,e.IDENT_RE,u(/\s*\(/))},n="([u](8|16|32|64))?";return{ name:"Sway",aliases:["sw"],keywords:{$pattern:e.IDENT_RE+"!?", keyword:["abi","as","asm","const","contract","deref","enum","fn","if","impl","let","library","match","mut","else","predicate","ref","return","script","Self","self","str","struct","trait","use","where","while"], literal:["true","false"], built_in:["bool","char","u8","u16","u32","u64","b256","str","Self"]}, illegal:""},t]}}, grmr_rust:e=>{const t=e.regex,n={className:"title.function.invoke",relevance:0, begin:t.concat(/\b/,/(?!let\b)/,e.IDENT_RE,t.lookahead(/\s*\(/)) },s="([ui](8|16|32|64|128|size)|f(32|64))?",i=["drop ","Copy","Send","Sized","Sync","Drop","Fn","FnMut","FnOnce","ToOwned","Clone","Debug","PartialEq","PartialOrd","Eq","Ord","AsRef","AsMut","Into","From","Default","Iterator","Extend","IntoIterator","DoubleEndedIterator","ExactSizeIterator","SliceConcatExt","ToString","assert!","assert_eq!","bitflags!","bytes!","cfg!","col!","concat!","concat_idents!","debug_assert!","debug_assert_eq!","env!","panic!","file!","format!","format_args!","include_bin!","include_str!","line!","local_data_key!","module_path!","option_env!","print!","println!","select!","stringify!","try!","unimplemented!","unreachable!","vec!","write!","writeln!","macro_rules!","assert_ne!","debug_assert_ne!"] ;return{name:"Rust",aliases:["rs"],keywords:{$pattern:e.IDENT_RE+"!?", type:["i8","i16","i32","i64","i128","isize","u8","u16","u32","u64","u128","usize","f32","f64","str","char","bool","Box","Option","Result","String","Vec"], keyword:["abstract","as","async","await","become","box","break","const","continue","crate","do","dyn","else","enum","extern","false","final","fn","for","if","impl","in","let","loop","macro","match","mod","move","mut","override","priv","pub","ref","return","self","Self","static","struct","super","trait","true","try","type","typeof","unsafe","unsized","use","virtual","where","while","yield"], literal:["true","false","Some","None","Ok","Err"],built_in:i},illegal:""},n]}}, grmr_ini:e=>{const t=e.regex,n={className:"number",relevance:0,variants:[{ begin:/([+-]+)?[\d]+_[\d_]+/},{begin:e.NUMBER_RE}]},s=e.COMMENT();s.variants=[{ begin:/;/,end:/$/},{begin:/#/,end:/$/}];const i={className:"variable", variants:[{begin:/\$[\w\d"][\w\d_]*/},{begin:/\$\{(.*?)\}/}]},a={ className:"literal",begin:/\bon|off|true|false|yes|no\b/},r={className:"string", contains:[e.BACKSLASH_ESCAPE],variants:[{begin:"'''",end:"'''",relevance:10},{ begin:'"""',end:'"""',relevance:10},{begin:'"',end:'"'},{begin:"'",end:"'"}] },o={begin:/\[/,end:/\]/,contains:[s,a,i,r,n,"self"],relevance:0 },l=t.either(/[A-Za-z0-9_-]+/,/"(\\"|[^"])*"/,/'[^']*'/);return{ name:"TOML, also INI",aliases:["toml"],case_insensitive:!0,illegal:/\S/, contains:[s,{className:"section",begin:/\[+/,end:/\]+/},{ begin:t.concat(l,"(\\s*\\.\\s*",l,")*",t.lookahead(/\s*=\s*[^#\s]/)), className:"attr",starts:{end:/$/,contains:[s,o,a,i,r,n]}}]}},grmr_bash:e=>{ const t=e.regex,n={},s={begin:/\$\{/,end:/\}/,contains:["self",{begin:/:-/, contains:[n]}]};Object.assign(n,{className:"variable",variants:[{ begin:t.concat(/\$[\w\d#@][\w\d_]*/,"(?![\\w\\d])(?![$])")},s]});const i={ className:"subst",begin:/\$\(/,end:/\)/,contains:[e.BACKSLASH_ESCAPE]},a={ begin:/<<-?\s*(?=\w+)/,starts:{contains:[e.END_SAME_AS_BEGIN({begin:/(\w+)/, end:/(\w+)/,className:"string"})]}},r={className:"string",begin:/"/,end:/"/, contains:[e.BACKSLASH_ESCAPE,n,i]};i.contains.push(r);const o={begin:/\$\(\(/, end:/\)\)/,contains:[{begin:/\d+#[0-9a-f]+/,className:"number"},e.NUMBER_MODE,n] },l=e.SHEBANG({binary:"(fish|bash|zsh|sh|csh|ksh|tcsh|dash|scsh)",relevance:10 }),c={className:"function",begin:/\w[\w\d_]*\s*\(\s*\)\s*\{/,returnBegin:!0, contains:[e.inherit(e.TITLE_MODE,{begin:/\w[\w\d_]*/})],relevance:0};return{ name:"Bash",aliases:["sh"],keywords:{$pattern:/\b[a-z._-]+\b/, keyword:["if","then","else","elif","fi","for","while","in","do","done","case","esac","function"], literal:["true","false"], built_in:["break","cd","continue","eval","exec","exit","export","getopts","hash","pwd","readonly","return","shift","test","times","trap","umask","unset","alias","bind","builtin","caller","command","declare","echo","enable","help","let","local","logout","mapfile","printf","read","readarray","source","type","typeset","ulimit","unalias","set","shopt","autoload","bg","bindkey","bye","cap","chdir","clone","comparguments","compcall","compctl","compdescribe","compfiles","compgroups","compquote","comptags","comptry","compvalues","dirs","disable","disown","echotc","echoti","emulate","fc","fg","float","functions","getcap","getln","history","integer","jobs","kill","limit","log","noglob","popd","print","pushd","pushln","rehash","sched","setcap","setopt","stat","suspend","ttyctl","unfunction","unhash","unlimit","unsetopt","vared","wait","whence","where","which","zcompile","zformat","zftp","zle","zmodload","zparseopts","zprof","zpty","zregexparse","zsocket","zstyle","ztcp","chcon","chgrp","chown","chmod","cp","dd","df","dir","dircolors","ln","ls","mkdir","mkfifo","mknod","mktemp","mv","realpath","rm","rmdir","shred","sync","touch","truncate","vdir","b2sum","base32","base64","cat","cksum","comm","csplit","cut","expand","fmt","fold","head","join","md5sum","nl","numfmt","od","paste","ptx","pr","sha1sum","sha224sum","sha256sum","sha384sum","sha512sum","shuf","sort","split","sum","tac","tail","tr","tsort","unexpand","uniq","wc","arch","basename","chroot","date","dirname","du","echo","env","expr","factor","groups","hostid","id","link","logname","nice","nohup","nproc","pathchk","pinky","printenv","printf","pwd","readlink","runcon","seq","sleep","stat","stdbuf","stty","tee","test","timeout","tty","uname","unlink","uptime","users","who","whoami","yes"] },contains:[l,e.SHEBANG(),c,o,e.HASH_COMMENT_MODE,a,{match:/(\/[a-z._-]+)+/},r,{ className:"",begin:/\\"/},{className:"string",begin:/'/,end:/'/},n]}}, grmr_shell:e=>({name:"Shell Session",aliases:["console","shellsession"], contains:[{className:"meta",begin:/^\s{0,3}[/~\w\d[\]()@-]*[>%$#][ ]?/,starts:{ end:/[^\\](?=\s*$)/,subLanguage:"bash"}}]}),grmr_json:e=>({name:"JSON", contains:[{className:"attr",begin:/"(\\.|[^\\"\r\n])*"(?=\s*:)/,relevance:1.01 },{match:/[{}[\],:]/,className:"punctuation",relevance:0},e.QUOTE_STRING_MODE,{ beginKeywords:"true false null" },e.C_NUMBER_MODE,e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE],illegal:"\\S"}) });const se=te;for(const e of Object.keys(ne)){ const t=e.replace("grmr_","").replace("_","-");se.registerLanguage(t,ne[e])} return se}() ;"object"==typeof exports&&"undefined"!=typeof module&&(module.exports=hljs); ================================================ FILE: e2e/Cargo.toml ================================================ [package] name = "e2e" authors = { workspace = true } edition = { workspace = true } homepage = { workspace = true } readme = { workspace = true } license = { workspace = true } repository = { workspace = true } rust-version = { workspace = true } version = { workspace = true } publish = false build = "build.rs" [dev-dependencies] # used in test assertions chrono = { workspace = true } rand = { workspace = true } fuel-asm = { workspace = true } # TODO: [issue](https://github.com/FuelLabs/fuels-rs/issues/1375) needs to be removed, `ScriptTransaction` and `CreateTransaction` in `fuels` use `fuel_tx::Input` but don't reexport or convert it into a `fuels` owned type fuel-tx = { workspace = true } # used in test assertions tai64 = { workspace = true } tempfile = { workspace = true } [build-dependencies] anyhow = { workspace = true, features = ["std"] } flate2 = { workspace = true, features = ["zlib"] } fuels-accounts = { workspace = true, features = ["std"] } reqwest = { workspace = true, features = ["blocking", "default-tls"] } semver = { workspace = true } tar = { workspace = true } [dependencies] anyhow = { workspace = true } fuels = { workspace = true, features = [ "accounts-signer-aws-kms", "test-helpers", ] } futures = { workspace = true } testcontainers = { workspace = true } tokio = { workspace = true, features = ["test-util"] } [features] default = ["fuels/default", "coin-cache"] fuel-core-lib = ["fuels/fuel-core-lib"] rocksdb = ["fuels/rocksdb"] coin-cache = ["fuels/coin-cache"] ================================================ FILE: e2e/Forc.toml ================================================ [workspace] members = [ 'sway/bindings/sharing_types/contract_a', 'sway/bindings/sharing_types/contract_b', 'sway/bindings/sharing_types/shared_lib', 'sway/bindings/simple_contract', 'sway/bindings/type_paths', 'sway/contracts/asserts', 'sway/contracts/auth_testing_abi', 'sway/contracts/auth_testing_contract', 'sway/contracts/block_timestamp', 'sway/contracts/configurables', 'sway/contracts/contract_test', 'sway/contracts/huge_contract', 'sway/contracts/large_return_data', 'sway/contracts/lib_contract', 'sway/contracts/lib_contract_abi', 'sway/contracts/lib_contract_caller', 'sway/contracts/library_test', 'sway/contracts/liquidity_pool', 'sway/contracts/low_level_caller', 'sway/contracts/msg_methods', 'sway/contracts/multiple_read_calls', 'sway/contracts/needs_custom_decoder', 'sway/contracts/payable_annotation', 'sway/contracts/proxy', 'sway/contracts/revert_transaction_error', 'sway/contracts/storage', 'sway/contracts/token_ops', 'sway/contracts/transaction_block_height', 'sway/contracts/tx_input_output', 'sway/contracts/var_outputs', 'sway/logs/contract_logs', 'sway/logs/contract_logs_abi', 'sway/logs/contract_revert_logs', 'sway/logs/contract_with_contract_logs', 'sway/logs/script_heap_logs', 'sway/logs/script_logs', 'sway/logs/script_revert_logs', 'sway/logs/script_with_contract_logs', 'sway/predicates/basic_predicate', 'sway/predicates/predicate_blobs', 'sway/predicates/predicate_configurables', 'sway/predicates/predicate_witnesses', 'sway/predicates/signatures', 'sway/predicates/swap', 'sway/predicates/predicate_tx_input_output', 'sway/scripts/arguments', 'sway/scripts/basic_script', 'sway/scripts/empty', 'sway/scripts/require_from_contract', 'sway/scripts/reverting', 'sway/scripts/script_array', 'sway/scripts/script_asserts', 'sway/scripts/script_blobs', 'sway/scripts/script_configurables', 'sway/scripts/script_enum', 'sway/scripts/script_needs_custom_decoder', 'sway/scripts/script_proxy', 'sway/scripts/script_struct', 'sway/scripts/script_tx_input_output', 'sway/scripts/transfer_script', 'sway/types/contracts/b256', 'sway/types/contracts/b512', 'sway/types/contracts/bytes', 'sway/types/contracts/call_empty_return', 'sway/types/contracts/complex_types_contract', 'sway/types/contracts/empty_arguments', 'sway/types/contracts/enum_as_input', 'sway/types/contracts/enum_encoding', 'sway/types/contracts/enum_inside_struct', 'sway/types/contracts/evm_address', 'sway/types/contracts/generics', 'sway/types/contracts/heap_type_in_enums', 'sway/types/contracts/heap_types', 'sway/types/contracts/identity', 'sway/types/contracts/native_types', 'sway/types/contracts/nested_structs', 'sway/types/contracts/options', 'sway/types/contracts/raw_slice', 'sway/types/contracts/results', 'sway/types/contracts/std_lib_string', 'sway/types/contracts/str_in_array', 'sway/types/contracts/string_slice', 'sway/types/contracts/tuples', 'sway/types/contracts/two_structs', 'sway/types/contracts/type_inside_enum', 'sway/types/contracts/u128', 'sway/types/contracts/u256', 'sway/types/contracts/vector_output', 'sway/types/contracts/vectors', 'sway/types/predicates/address', 'sway/types/predicates/enums', 'sway/types/predicates/predicate_b256', 'sway/types/predicates/predicate_bytes', 'sway/types/predicates/predicate_bytes_hash', 'sway/types/predicates/predicate_generics', 'sway/types/predicates/predicate_raw_slice', 'sway/types/predicates/predicate_std_lib_string', 'sway/types/predicates/predicate_string_slice', 'sway/types/predicates/predicate_tuples', 'sway/types/predicates/predicate_u128', 'sway/types/predicates/predicate_u256', 'sway/types/predicates/predicate_vector', 'sway/types/predicates/predicate_vectors', 'sway/types/predicates/structs', 'sway/types/predicates/u64', 'sway/types/scripts/options_results', 'sway/types/scripts/script_b256', 'sway/types/scripts/script_bytes', 'sway/types/scripts/script_generics', 'sway/types/scripts/script_heap_types', 'sway/types/scripts/script_raw_slice', 'sway/types/scripts/script_std_lib_string', 'sway/types/scripts/script_string_slice', 'sway/types/scripts/script_tuples', 'sway/types/scripts/script_u128', 'sway/types/scripts/script_u256', 'sway/types/scripts/script_vectors', ] [patch.'https://github.com/fuellabs/sway'] std = { git = "https://github.com/fuellabs/sway" } ================================================ FILE: e2e/build.rs ================================================ use std::{ io::Cursor, path::{Path, PathBuf}, }; use flate2::read::GzDecoder; use fuels_accounts::provider::SUPPORTED_FUEL_CORE_VERSION; use tar::Archive; struct Downloader { dir: PathBuf, } impl Downloader { const EXECUTOR_FILE_NAME: &'static str = "fuel-core-wasm-executor.wasm"; pub fn new() -> Self { let env = std::env::var("OUT_DIR").unwrap(); let out_dir = Path::new(&env); Self { dir: out_dir.to_path_buf(), } } pub fn should_download(&self) -> anyhow::Result { if !self.executor_path().exists() { return Ok(true); } if !self.version_path().exists() { return Ok(true); } let saved_version = semver::Version::parse(&std::fs::read_to_string(self.version_path())?)?; if saved_version != SUPPORTED_FUEL_CORE_VERSION { return Ok(true); } Ok(false) } pub fn download(&self) -> anyhow::Result<()> { std::fs::create_dir_all(&self.dir)?; const LINK_TEMPLATE: &str = "https://github.com/FuelLabs/fuel-core/releases/download/vVERSION/fuel-core-VERSION-x86_64-unknown-linux-gnu.tar.gz"; let link = LINK_TEMPLATE.replace("VERSION", &SUPPORTED_FUEL_CORE_VERSION.to_string()); let response = reqwest::blocking::Client::builder() .timeout(std::time::Duration::from_secs(60)) .build()? .get(link) .send()?; if !response.status().is_success() { anyhow::bail!("Failed to download wasm executor: {}", response.status()); } let mut content = Cursor::new(response.bytes()?); let mut archive = Archive::new(GzDecoder::new(&mut content)); let mut extracted = false; let executor_in_tar = Path::new(&format!( "fuel-core-{SUPPORTED_FUEL_CORE_VERSION}-x86_64-unknown-linux-gnu" )) .join(Self::EXECUTOR_FILE_NAME); for entry in archive.entries()? { let mut entry = entry?; if entry.path()? == executor_in_tar { entry.unpack(self.executor_path())?; std::fs::write( self.version_path(), format!("{SUPPORTED_FUEL_CORE_VERSION}"), )?; extracted = true; break; } } if !extracted { anyhow::bail!("Failed to extract wasm executor from the archive"); } Ok(()) } fn make_cargo_watch_downloaded_files(&self) { let executor_path = self.executor_path(); println!("cargo:rerun-if-changed={}", executor_path.display()); let version_path = self.version_path(); println!("cargo:rerun-if-changed={}", version_path.display()); } fn executor_path(&self) -> PathBuf { self.dir.join(Self::EXECUTOR_FILE_NAME) } fn version_path(&self) -> PathBuf { self.dir.join("version.rs") } } fn main() { println!("cargo:rerun-if-changed=build.rs"); let downloader = Downloader::new(); downloader.make_cargo_watch_downloaded_files(); if downloader.should_download().unwrap() { downloader.download().unwrap(); } } ================================================ FILE: e2e/src/aws_kms.rs ================================================ use fuels::{ accounts::signers::kms::aws::{ AwsKmsSigner, aws_config::{BehaviorVersion, Region, defaults}, aws_sdk_kms::{ Client, config::Credentials, types::{KeySpec, KeyUsageType}, }, }, prelude::Error, types::errors::{Context, Result}, }; use testcontainers::{core::ContainerPort, runners::AsyncRunner}; use tokio::io::AsyncBufReadExt; #[derive(Default)] pub struct AwsKms { show_logs: bool, } struct AwsKmsImage; impl testcontainers::Image for AwsKmsImage { fn name(&self) -> &str { "localstack/localstack" } fn tag(&self) -> &str { "latest" } fn ready_conditions(&self) -> Vec { vec![testcontainers::core::WaitFor::message_on_stdout("Ready.")] } fn expose_ports(&self) -> &[ContainerPort] { &[ContainerPort::Tcp(4566)] } } impl AwsKms { pub fn with_show_logs(mut self, show_logs: bool) -> Self { self.show_logs = show_logs; self } pub async fn start(self) -> Result { let container = AwsKmsImage .start() .await .map_err(|e| Error::Other(e.to_string())) .with_context(|| "Failed to start KMS container")?; if self.show_logs { spawn_log_printer(&container); } let port = container .get_host_port_ipv4(4566) .await .map_err(|e| Error::Other(e.to_string()))?; let url = format!("http://localhost:{}", port); let credentials = Credentials::new("test", "test", None, None, "Static Test Credentials"); let region = Region::new("us-east-1"); let config = defaults(BehaviorVersion::latest()) .credentials_provider(credentials) .endpoint_url(url.clone()) .region(region) .load() .await; let client = Client::new(&config); Ok(AwsKmsProcess { _container: container, client, url, }) } } fn spawn_log_printer(container: &testcontainers::ContainerAsync) { let stderr = container.stderr(true); let stdout = container.stdout(true); tokio::spawn(async move { let mut stderr_lines = stderr.lines(); let mut stdout_lines = stdout.lines(); let mut other_stream_closed = false; loop { tokio::select! { stderr_result = stderr_lines.next_line() => { match stderr_result { Ok(Some(line)) => eprintln!("KMS (stderr): {}", line), Ok(None) if other_stream_closed => break, Ok(None) => other_stream_closed = true, Err(e) => { eprintln!("KMS: Error reading from stderr: {:?}", e); break; } } } stdout_result = stdout_lines.next_line() => { match stdout_result { Ok(Some(line)) => eprintln!("KMS (stdout): {}", line), Ok(None) if other_stream_closed => break, Ok(None) => other_stream_closed = true, Err(e) => { eprintln!("KMS: Error reading from stdout: {:?}", e); break; } } } } } Ok::<(), std::io::Error>(()) }); } pub struct AwsKmsProcess { _container: testcontainers::ContainerAsync, client: Client, url: String, } impl AwsKmsProcess { pub async fn create_signer(&self) -> anyhow::Result { let response = self .client .create_key() .key_usage(KeyUsageType::SignVerify) .key_spec(KeySpec::EccSecgP256K1) .send() .await?; let id = response .key_metadata .and_then(|metadata| metadata.arn) .ok_or_else(|| anyhow::anyhow!("key arn missing from response"))?; let kms_signer = AwsKmsSigner::new(id.clone(), &self.client).await?; Ok(kms_signer) } pub fn client(&self) -> &Client { &self.client } pub fn url(&self) -> &str { &self.url } } ================================================ FILE: e2e/src/e2e_helpers.rs ================================================ use fuels::types::errors::Result; use crate::aws_kms::{AwsKms, AwsKmsProcess}; pub async fn start_aws_kms(logs: bool) -> Result { AwsKms::default().with_show_logs(logs).start().await } ================================================ FILE: e2e/src/lib.rs ================================================ mod aws_kms; pub mod e2e_helpers; ================================================ FILE: e2e/sway/abi/simple_contract/Forc.toml ================================================ [project] authors = ["Fuel Labs "] entry = "main.sw" license = "Apache-2.0" name = "simple_contract" ================================================ FILE: e2e/sway/abi/simple_contract/src/main.sw ================================================ contract; abi SimpleContract { fn takes_u32_returns_bool(arg: u32) -> bool; } impl SimpleContract for Contract { fn takes_u32_returns_bool(_arg: u32) -> bool { true } } ================================================ FILE: e2e/sway/abi/wasm_contract/Forc.toml ================================================ [project] authors = ["Fuel Labs "] entry = "main.sw" license = "Apache-2.0" name = "wasm_contract" ================================================ FILE: e2e/sway/abi/wasm_contract/src/main.sw ================================================ contract; enum SomeEnum { V1: (), V2: T, } #[allow(dead_code)] struct SomeStruct { a: u32, b: bool, } abi TestContract { fn test_function(arg: SomeEnum); } impl TestContract for Contract { fn test_function(_arg: SomeEnum) {} } ================================================ FILE: e2e/sway/abi/wasm_predicate/Forc.toml ================================================ [project] authors = ["Fuel Labs "] entry = "main.sw" license = "Apache-2.0" name = "wasm_predicate" ================================================ FILE: e2e/sway/abi/wasm_predicate/src/main.sw ================================================ predicate; configurable { U64: u64 = 128, } fn main(val: u64) -> bool { val == U64 } ================================================ FILE: e2e/sway/bindings/sharing_types/contract_a/Forc.toml ================================================ [project] authors = ["Fuel Labs "] entry = "main.sw" license = "Apache-2.0" name = "contract_a" [dependencies] shared_lib = { path = "../shared_lib" } ================================================ FILE: e2e/sway/bindings/sharing_types/contract_a/src/main.sw ================================================ contract; use shared_lib::*; struct UniqueStructToContractA { a: T, } struct StructSameNameButDifferentInternals { a: u32, } #[allow(dead_code)] enum EnumSameNameButDifferentInternals { a: u32, } abi MyContract { fn uses_shared_type( arg1: SharedStruct2, arg2: SharedEnum, ) -> (SharedStruct2, SharedEnum); fn uses_types_that_share_only_names( arg1: StructSameNameButDifferentInternals, arg2: EnumSameNameButDifferentInternals, ) -> (StructSameNameButDifferentInternals, EnumSameNameButDifferentInternals); fn uses_shared_type_inside_owned_one( arg1: UniqueStructToContractA>, ) -> UniqueStructToContractA>; } impl MyContract for Contract { fn uses_shared_type( arg1: SharedStruct2, arg2: SharedEnum, ) -> (SharedStruct2, SharedEnum) { (arg1, arg2) } fn uses_types_that_share_only_names( arg1: StructSameNameButDifferentInternals, arg2: EnumSameNameButDifferentInternals, ) -> (StructSameNameButDifferentInternals, EnumSameNameButDifferentInternals) { (arg1, arg2) } fn uses_shared_type_inside_owned_one( arg1: UniqueStructToContractA>, ) -> UniqueStructToContractA> { arg1 } } ================================================ FILE: e2e/sway/bindings/sharing_types/contract_b/Forc.toml ================================================ [project] authors = ["Fuel Labs "] entry = "main.sw" license = "Apache-2.0" name = "contract_b" [dependencies] shared_lib = { path = "../shared_lib" } ================================================ FILE: e2e/sway/bindings/sharing_types/contract_b/src/main.sw ================================================ contract; use shared_lib::*; struct UniqueStructToContractB { a: T, } struct StructSameNameButDifferentInternals { a: [u64; 1], } #[allow(dead_code)] enum EnumSameNameButDifferentInternals { a: [u64; 1], } abi MyContract { fn uses_shared_type( arg1: SharedStruct2, arg2: SharedEnum, ) -> (SharedStruct2, SharedEnum); fn uses_types_that_share_only_names( arg1: StructSameNameButDifferentInternals, arg2: EnumSameNameButDifferentInternals, ) -> (StructSameNameButDifferentInternals, EnumSameNameButDifferentInternals); fn uses_shared_type_inside_owned_one( arg1: UniqueStructToContractB>, ) -> UniqueStructToContractB>; } impl MyContract for Contract { fn uses_shared_type( arg1: SharedStruct2, arg2: SharedEnum, ) -> (SharedStruct2, SharedEnum) { (arg1, arg2) } fn uses_types_that_share_only_names( arg1: StructSameNameButDifferentInternals, arg2: EnumSameNameButDifferentInternals, ) -> (StructSameNameButDifferentInternals, EnumSameNameButDifferentInternals) { (arg1, arg2) } fn uses_shared_type_inside_owned_one( arg1: UniqueStructToContractB>, ) -> UniqueStructToContractB> { arg1 } } ================================================ FILE: e2e/sway/bindings/sharing_types/shared_lib/Forc.toml ================================================ [project] authors = ["Fuel Labs "] entry = "lib.sw" license = "Apache-2.0" name = "shared_lib" ================================================ FILE: e2e/sway/bindings/sharing_types/shared_lib/src/lib.sw ================================================ library; pub struct SharedStruct1 { a: T, } pub struct SharedStruct2 { a: u32, b: SharedStruct1, } #[allow(dead_code)] pub enum SharedEnum { a: u64, b: SharedStruct2, } ================================================ FILE: e2e/sway/bindings/simple_contract/Forc.toml ================================================ [project] authors = ["Fuel Labs "] entry = "main.sw" license = "Apache-2.0" name = "simple_contract" ================================================ FILE: e2e/sway/bindings/simple_contract/src/main.sw ================================================ contract; abi MyContract { fn takes_int_returns_bool(arg: u32) -> bool; } impl MyContract for Contract { fn takes_int_returns_bool(arg: u32) -> bool { arg == 32u32 } } ================================================ FILE: e2e/sway/bindings/type_paths/Forc.toml ================================================ [project] authors = ["Fuel Labs "] entry = "main.sw" license = "Apache-2.0" name = "type_paths" ================================================ FILE: e2e/sway/bindings/type_paths/src/another_lib.sw ================================================ library; pub struct VeryCommonNameStruct { pub field_a: u32, } ================================================ FILE: e2e/sway/bindings/type_paths/src/contract_a_types.sw ================================================ library; pub struct VeryCommonNameStruct { another_field: u32, } pub struct AWrapper { field: VeryCommonNameStruct, } ================================================ FILE: e2e/sway/bindings/type_paths/src/main.sw ================================================ contract; mod contract_a_types; mod another_lib; use contract_a_types::AWrapper; use another_lib::VeryCommonNameStruct; abi MyContract { fn test_function(arg: AWrapper) -> VeryCommonNameStruct; } impl MyContract for Contract { fn test_function(_arg: AWrapper) -> VeryCommonNameStruct { VeryCommonNameStruct { field_a: 10u32 } } } ================================================ FILE: e2e/sway/contracts/asserts/Forc.toml ================================================ [project] authors = ["Fuel Labs "] entry = "main.sw" license = "Apache-2.0" name = "asserts" ================================================ FILE: e2e/sway/contracts/asserts/src/main.sw ================================================ contract; struct TestStruct { field_1: bool, field_2: u64, } #[allow(dead_code)] enum TestEnum { VariantOne: (), VariantTwo: (), } impl PartialEq for TestStruct { fn eq(self, other: Self) -> bool { self.field_1 == other.field_1 && self.field_2 == other.field_2 } } impl Eq for TestStruct {} impl PartialEq for TestEnum { fn eq(self, other: Self) -> bool { match (self, other) { (TestEnum::VariantOne, TestEnum::VariantOne) => true, (TestEnum::VariantTwo, TestEnum::VariantTwo) => true, _ => false, } } } impl Eq for TestEnum {} abi TestContract { fn assert_primitive(a: u64, b: u64); fn assert_eq_primitive(a: u64, b: u64); fn assert_eq_struct(test_struct: TestStruct, test_struct2: TestStruct); fn assert_eq_enum(test_enum: TestEnum, test_enum2: TestEnum); fn assert_ne_primitive(a: u64, b: u64); fn assert_ne_struct(test_struct: TestStruct, test_struct2: TestStruct); fn assert_ne_enum(test_enum: TestEnum, test_enum2: TestEnum); } impl TestContract for Contract { fn assert_primitive(a: u64, b: u64) { assert(a == b); } fn assert_eq_primitive(a: u64, b: u64) { assert_eq(a, b); } fn assert_eq_struct(test_struct: TestStruct, test_struct2: TestStruct) { assert_eq(test_struct, test_struct2); } fn assert_eq_enum(test_enum: TestEnum, test_enum2: TestEnum) { assert_eq(test_enum, test_enum2); } fn assert_ne_primitive(a: u64, b: u64) { assert_ne(a, b); } fn assert_ne_struct(test_struct: TestStruct, test_struct2: TestStruct) { assert_ne(test_struct, test_struct2); } fn assert_ne_enum(test_enum: TestEnum, test_enum2: TestEnum) { assert_ne(test_enum, test_enum2); } } ================================================ FILE: e2e/sway/contracts/auth_testing_abi/Forc.toml ================================================ [project] authors = ["Fuel Labs "] entry = "main.sw" license = "Apache-2.0" name = "auth_testing_abi" ================================================ FILE: e2e/sway/contracts/auth_testing_abi/src/main.sw ================================================ library; abi AuthTesting { fn is_caller_external() -> bool; fn check_msg_sender(expected_id: Address) -> bool; } ================================================ FILE: e2e/sway/contracts/auth_testing_contract/Forc.toml ================================================ [project] authors = ["Fuel Labs "] entry = "main.sw" license = "Apache-2.0" name = "auth_testing_contract" [dependencies] auth_testing_abi = { path = "../auth_testing_abi" } ================================================ FILE: e2e/sway/contracts/auth_testing_contract/src/main.sw ================================================ contract; use std::auth::{AuthError, caller_is_external, msg_sender}; use auth_testing_abi::*; impl AuthTesting for Contract { fn is_caller_external() -> bool { caller_is_external() } fn check_msg_sender(expected_id: Address) -> bool { let result: Result = msg_sender(); let mut ret = false; if result.is_err() { ret = false; } else { let unwrapped = result.unwrap(); if let Identity::Address(v) = unwrapped { assert(v == expected_id); ret = true; } else { ret = false; } }; ret } } ================================================ FILE: e2e/sway/contracts/block_timestamp/Forc.toml ================================================ [project] authors = ["Fuel Labs "] entry = "main.sw" license = "Apache-2.0" name = "block_timestamp" ================================================ FILE: e2e/sway/contracts/block_timestamp/src/main.sw ================================================ contract; use std::block::timestamp; abi MyContract { fn return_timestamp() -> u64; } impl MyContract for Contract { fn return_timestamp() -> u64 { timestamp() } } ================================================ FILE: e2e/sway/contracts/configurables/Forc.toml ================================================ [project] authors = ["Fuel Labs "] entry = "main.sw" license = "Apache-2.0" name = "configurables" ================================================ FILE: e2e/sway/contracts/configurables/src/main.sw ================================================ contract; #[allow(dead_code)] enum EnumWithGeneric { VariantOne: D, VariantTwo: (), } struct StructWithGeneric { field_1: D, field_2: u64, } configurable { BOOL: bool = true, U8: u8 = 8, U16: u16 = 16, U32: u32 = 32, U64: u64 = 63, U256: u256 = 0x0000000000000000000000000000000000000000000000000000000000000008u256, B256: b256 = 0x0101010101010101010101010101010101010101010101010101010101010101, STR_4: str[4] = __to_str_array("fuel"), TUPLE: (u8, bool) = (8, true), ARRAY: [u32; 3] = [253, 254, 255], STRUCT: StructWithGeneric = StructWithGeneric { field_1: 8, field_2: 16, }, ENUM: EnumWithGeneric = EnumWithGeneric::VariantOne(true), } //U128: u128 = 128, //TODO: add once https://github.com/FuelLabs/sway/issues/5356 is done abi TestContract { fn return_configurables() -> (bool, u8, u16, u32, u64, u256, b256, str[4], (u8, bool), [u32; 3], StructWithGeneric, EnumWithGeneric); } impl TestContract for Contract { fn return_configurables() -> (bool, u8, u16, u32, u64, u256, b256, str[4], (u8, bool), [u32; 3], StructWithGeneric, EnumWithGeneric) { (BOOL, U8, U16, U32, U64, U256, B256, STR_4, TUPLE, ARRAY, STRUCT, ENUM) } } ================================================ FILE: e2e/sway/contracts/contract_test/Forc.toml ================================================ [project] authors = ["Fuel Labs "] entry = "main.sw" license = "Apache-2.0" name = "contract_test" [dependencies] increment_abi = { path = "../library_test", package = "library_test" } ================================================ FILE: e2e/sway/contracts/contract_test/src/main.sw ================================================ contract; use std::storage::storage_api::{read, write}; use std::context::msg_amount; struct MyType { x: u64, y: u64, } #[allow(dead_code)] struct Person { name: str[4], } #[allow(dead_code)] enum State { A: (), B: (), C: (), } abi TestContract { #[storage(write)] fn initialize_counter(value: u64) -> u64; #[storage(read, write)] fn increment_counter(amount: u64) -> u64; #[storage(read)] fn read_counter() -> u64; // ANCHOR: low_level_call #[storage(write)] fn set_value_multiple_complex(a: MyStruct, b: str[4]); // ANCHOR_END: low_level_call #[storage(read)] fn get_str_value() -> str[4]; #[storage(read)] fn get_bool_value() -> bool; fn get(x: u64, y: u64) -> u64; fn get_alt(x: MyType) -> MyType; fn get_single(x: u64) -> u64; fn array_of_structs(p: [Person; 2]) -> [Person; 2]; fn array_of_enums(p: [State; 2]) -> [State; 2]; fn get_array(p: [u64; 2]) -> [u64; 2]; #[payable] fn get_msg_amount() -> u64; fn new() -> u64; } storage { counter: u64 = 0, value_str: str[4] = __to_str_array("none"), value_bool: bool = false, } pub struct MyStruct { a: bool, b: [u64; 3], } impl TestContract for Contract { // ANCHOR: msg_amount #[payable] fn get_msg_amount() -> u64 { msg_amount() } // ANCHOR_END: msg_amount #[storage(write)] fn initialize_counter(value: u64) -> u64 { storage.counter.write(value); value } /// This method will read the counter from storage, increment it /// and write the incremented value to storage #[storage(read, write)] fn increment_counter(amount: u64) -> u64 { let incremented = storage.counter.read() + amount; storage.counter.write(incremented); incremented } #[storage(read)] fn read_counter() -> u64 { storage.counter.read() } #[storage(write)] fn set_value_multiple_complex(a: MyStruct, b: str[4]) { storage.counter.write(a.b[1]); storage.value_str.write(b); storage.value_bool.write(a.a); } #[storage(read)] fn get_str_value() -> str[4] { storage.value_str.read() } #[storage(read)] fn get_bool_value() -> bool { storage.value_bool.read() } fn get(x: u64, y: u64) -> u64 { x + y } fn get_alt(t: MyType) -> MyType { t } fn get_single(x: u64) -> u64 { x } fn array_of_structs(p: [Person; 2]) -> [Person; 2] { p } fn array_of_enums(p: [State; 2]) -> [State; 2] { p } fn get_array(p: [u64; 2]) -> [u64; 2] { p } fn new() -> u64 { 12345u64 } } ================================================ FILE: e2e/sway/contracts/huge_contract/Forc.toml ================================================ [project] authors = ["Fuel Labs "] entry = "main.sw" license = "Apache-2.0" name = "huge_contract" [dependencies] ================================================ FILE: e2e/sway/contracts/huge_contract/src/main.sw ================================================ contract; abi MyContract { fn something() -> u64; #[storage(write)] fn write_some_u64(some: u64); #[storage(read)] fn read_some_u64() -> u64; } storage { some_u64: u64 = 42, } impl MyContract for Contract { fn something() -> u64 { asm() { blob i450000; } 1001 } #[storage(write)] fn write_some_u64(some: u64) { storage.some_u64.write(some); } #[storage(read)] fn read_some_u64() -> u64 { storage.some_u64.read() } } ================================================ FILE: e2e/sway/contracts/large_return_data/Forc.toml ================================================ [project] authors = ["Fuel Labs "] entry = "main.sw" license = "Apache-2.0" name = "large_return_data" ================================================ FILE: e2e/sway/contracts/large_return_data/src/main.sw ================================================ contract; pub struct SmallStruct { foo: u32, } pub struct LargeStruct { foo: u8, bar: u8, } abi TestContract { fn get_id() -> b256; fn get_small_string() -> str[8]; fn get_large_string() -> str[9]; fn get_large_struct() -> LargeStruct; fn get_small_struct() -> SmallStruct; fn get_large_array() -> [u32; 2]; fn get_contract_id() -> ContractId; } impl TestContract for Contract { fn get_id() -> b256 { 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF } fn get_small_string() -> str[8] { let my_string: str[8] = __to_str_array("gggggggg"); my_string } fn get_large_string() -> str[9] { let my_string: str[9] = __to_str_array("ggggggggg"); my_string } fn get_small_struct() -> SmallStruct { SmallStruct { foo: 100u32 } } fn get_large_struct() -> LargeStruct { LargeStruct { foo: 12u8, bar: 42u8, } } fn get_large_array() -> [u32; 2] { let x: [u32; 2] = [1u32, 2u32]; x } fn get_contract_id() -> ContractId { let id = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF; ContractId::from(id) } } ================================================ FILE: e2e/sway/contracts/lib_contract/Forc.toml ================================================ [project] authors = ["Fuel Labs "] entry = "main.sw" license = "Apache-2.0" name = "lib_contract" [dependencies] lib_contract_abi = { path = "../lib_contract_abi/", package = "lib_contract_abi" } ================================================ FILE: e2e/sway/contracts/lib_contract/src/main.sw ================================================ contract; use lib_contract_abi::LibContract; impl LibContract for Contract { fn increment(value: u64) -> u64 { value + 1 } fn require() -> () { require(false, __to_str_array("require from contract")); } } ================================================ FILE: e2e/sway/contracts/lib_contract_abi/Forc.toml ================================================ [project] authors = ["Fuel Labs "] entry = "main.sw" license = "Apache-2.0" name = "lib_contract_abi" ================================================ FILE: e2e/sway/contracts/lib_contract_abi/src/main.sw ================================================ library; abi LibContract { fn increment(value: u64) -> u64; fn require(); } ================================================ FILE: e2e/sway/contracts/lib_contract_caller/Forc.toml ================================================ [project] authors = ["Fuel Labs "] entry = "main.sw" license = "Apache-2.0" name = "lib_contract_caller" [dependencies] lib_contract = { path = "../lib_contract_abi/", package = "lib_contract_abi" } ================================================ FILE: e2e/sway/contracts/lib_contract_caller/src/main.sw ================================================ contract; use lib_contract::LibContract; use std::asset::mint_to; abi ContractCaller { fn increment_from_contract(contract_id: ContractId, value: u64) -> u64; fn increment_from_contracts( contract_id: ContractId, contract_id2: ContractId, value: u64, ) -> u64; fn mint_then_increment_from_contract(contract_id: ContractId, amount: u64, address: Identity); fn require_from_contract(contract_id: ContractId); fn re_entrant(contract_id: ContractId, re_enter: bool) -> u64; } impl ContractCaller for Contract { fn increment_from_contract(contract_id: ContractId, value: u64) -> u64 { let contract_instance = abi(LibContract, contract_id.into()); contract_instance.increment(value) } fn increment_from_contracts( contract_id: ContractId, contract_id2: ContractId, value: u64, ) -> u64 { let contract_instance = abi(LibContract, contract_id.into()); let contract_instance2 = abi(LibContract, contract_id2.into()); contract_instance.increment(value) + contract_instance2.increment(value) } fn mint_then_increment_from_contract(contract_id: ContractId, amount: u64, address: Identity) { mint_to(address, b256::zero(), amount); let contract_instance = abi(LibContract, contract_id.into()); let _ = contract_instance.increment(42); } fn require_from_contract(contract_id: ContractId) { let contract_instance = abi(LibContract, contract_id.into()); contract_instance.require(); } fn re_entrant(contract_id: ContractId, re_enter: bool) -> u64 { if !re_enter { return 101; } let contract_instance = abi(ContractCaller, contract_id.into()); let _ = contract_instance.re_entrant(contract_id, false); 42 } } ================================================ FILE: e2e/sway/contracts/library_test/Forc.toml ================================================ [project] authors = ["Fuel Labs "] entry = "main.sw" license = "Apache-2.0" name = "library_test" ================================================ FILE: e2e/sway/contracts/library_test/src/main.sw ================================================ library; abi Incrementor { fn initialize(gas: u64, amt: u64, coin: b256, initial_value: u64) -> u64; fn increment(gas: u64, amt: u64, coin: b256, initial_value: u64) -> u64; } ================================================ FILE: e2e/sway/contracts/liquidity_pool/Forc.toml ================================================ [project] authors = ["Fuel Labs "] entry = "main.sw" license = "Apache-2.0" name = "liquidity_pool" ================================================ FILE: e2e/sway/contracts/liquidity_pool/src/main.sw ================================================ contract; use std::{asset::{mint_to, transfer}, call_frames::msg_asset_id, context::msg_amount}; abi LiquidityPool { #[payable] fn deposit(recipient: Identity); #[payable] fn withdraw(recipient: Identity); } const BASE_TOKEN: AssetId = AssetId::from(0x9ae5b658754e096e4d681c548daf46354495a437cc61492599e33fc64dcdc30c); impl LiquidityPool for Contract { #[payable] fn deposit(recipient: Identity) { assert(BASE_TOKEN == msg_asset_id()); assert(0 < msg_amount()); // Mint two times the amount. let amount_to_mint = msg_amount() * 2; // Mint some LP token based upon the amount of the base token. mint_to(recipient, b256::zero(), amount_to_mint); } #[payable] fn withdraw(recipient: Identity) { assert(0 < msg_amount()); // Amount to withdraw. let amount_to_transfer = msg_amount() / 2; // Transfer base token to recipient. transfer(recipient, BASE_TOKEN, amount_to_transfer); } } ================================================ FILE: e2e/sway/contracts/low_level_caller/Forc.toml ================================================ [project] authors = ["Fuel Labs "] entry = "main.sw" license = "Apache-2.0" name = "low_level_caller" ================================================ FILE: e2e/sway/contracts/low_level_caller/src/main.sw ================================================ contract; use std::{ bytes::Bytes, constants::ZERO_B256, low_level_call::{ call_with_function_selector, CallParams, }, }; abi MyCallerContract { fn call_low_level_call( target: ContractId, function_selector: Bytes, calldata: Bytes, ); } impl MyCallerContract for Contract { // ANCHOR: low_level_call_contract fn call_low_level_call( target: ContractId, function_selector: Bytes, calldata: Bytes, ) { let call_params = CallParams { coins: 0, asset_id: AssetId::from(ZERO_B256), gas: 10_000, }; call_with_function_selector(target, function_selector, calldata, call_params); } // ANCHOR_END: low_level_call_contract } ================================================ FILE: e2e/sway/contracts/msg_methods/Forc.toml ================================================ [project] authors = ["Fuel Labs "] entry = "main.sw" license = "Apache-2.0" name = "msg_methods" ================================================ FILE: e2e/sway/contracts/msg_methods/src/main.sw ================================================ contract; use std::auth::msg_sender; abi FuelTest { #[payable] fn message_sender() -> Identity; } impl FuelTest for Contract { #[payable] fn message_sender() -> Identity { msg_sender().unwrap() } } ================================================ FILE: e2e/sway/contracts/multiple_read_calls/Forc.toml ================================================ [project] authors = ["Fuel Labs "] entry = "main.sw" license = "Apache-2.0" name = "multiple_read_calls" ================================================ FILE: e2e/sway/contracts/multiple_read_calls/src/main.sw ================================================ contract; use std::storage::storage_api::{read, write}; abi MyContract { #[storage(write)] fn store(input: u64); #[storage(read)] fn read() -> u64; } const COUNTER_KEY = 0x0000000000000000000000000000000000000000000000000000000000000000; impl MyContract for Contract { #[storage(write)] fn store(input: u64) { write(COUNTER_KEY, 0, input); } #[storage(read)] fn read() -> u64 { read::(COUNTER_KEY, 0).unwrap_or(0) } } ================================================ FILE: e2e/sway/contracts/needs_custom_decoder/Forc.toml ================================================ [project] authors = ["Fuel Labs "] entry = "main.sw" license = "Apache-2.0" name = "needs_custom_decoder" [dependencies] ================================================ FILE: e2e/sway/contracts/needs_custom_decoder/src/main.sw ================================================ contract; impl AbiEncode for [u8; 1000] { fn abi_encode(self, buffer: Buffer) -> Buffer { let mut buffer = buffer; let mut i = 0; while i < 1000 { buffer = self[i].abi_encode(buffer); i += 1; }; buffer } } abi MyContract { fn i_return_a_1k_el_array() -> [u8; 1000]; fn i_log_a_1k_el_array(); } impl MyContract for Contract { fn i_log_a_1k_el_array() { let arr: [u8; 1000] = [0; 1000]; log(arr); } fn i_return_a_1k_el_array() -> [u8; 1000] { [0; 1000] } } ================================================ FILE: e2e/sway/contracts/payable_annotation/Forc.toml ================================================ [project] authors = ["Fuel Labs "] entry = "main.sw" license = "Apache-2.0" name = "payable_annotation" ================================================ FILE: e2e/sway/contracts/payable_annotation/src/main.sw ================================================ contract; abi TestContract { #[payable] fn payable() -> u64; fn non_payable() -> u64; } impl TestContract for Contract { #[payable] fn payable() -> u64 { 42 } fn non_payable() -> u64 { 42 } } ================================================ FILE: e2e/sway/contracts/proxy/Forc.toml ================================================ [project] authors = ["Fuel Labs "] entry = "main.sw" license = "Apache-2.0" name = "proxy" [dependencies] ================================================ FILE: e2e/sway/contracts/proxy/src/main.sw ================================================ contract; use std::execution::run_external; abi Proxy { #[storage(write)] fn set_target_contract(id: ContractId); // methods of the `huge_contract` in our e2e sway contracts #[storage(read)] fn something() -> u64; #[storage(read)] fn write_some_u64(some: u64); #[storage(read)] fn read_some_u64() -> u64; } storage { target_contract: Option = None, } impl Proxy for Contract { #[storage(write)] fn set_target_contract(id: ContractId) { storage.target_contract.write(Some(id)); } #[storage(read)] fn something() -> u64 { let target = storage.target_contract.read().unwrap(); run_external(target) } #[storage(read)] fn write_some_u64(_some: u64) { let target = storage.target_contract.read().unwrap(); run_external(target) } #[storage(read)] fn read_some_u64() -> u64 { let target = storage.target_contract.read().unwrap(); run_external(target) } } ================================================ FILE: e2e/sway/contracts/revert_transaction_error/Forc.toml ================================================ [project] authors = ["Fuel Labs "] entry = "main.sw" license = "Apache-2.0" name = "revert_transaction_error" ================================================ FILE: e2e/sway/contracts/revert_transaction_error/src/main.sw ================================================ contract; abi MyContract { fn make_transaction_fail(fail: bool) -> u64; } impl MyContract for Contract { fn make_transaction_fail(fail: bool) -> u64 { if fail { revert(128); } 42 } } ================================================ FILE: e2e/sway/contracts/storage/Forc.toml ================================================ [project] authors = ["Fuel Labs "] entry = "main.sw" license = "Apache-2.0" name = "storage" ================================================ FILE: e2e/sway/contracts/storage/src/main.sw ================================================ contract; use std::storage::storage_api::read; storage { x: u64 = 64, y: b256 = 0x0101010101010101010101010101010101010101010101010101010101010101, } abi MyContract { #[storage(write)] fn set_storage(x: u64, y: b256); #[storage(read)] fn get_value_b256(key: b256) -> b256; #[storage(read)] fn get_value_u64(key: b256) -> u64; } impl MyContract for Contract { #[storage(write)] fn set_storage(x: u64, y: b256) { storage.x.write(x); storage.y.write(y); } #[storage(read)] fn get_value_b256(key: b256) -> b256 { read::(key, 0).unwrap() } #[storage(read)] fn get_value_u64(key: b256) -> u64 { read::(key, 0).unwrap() } } ================================================ FILE: e2e/sway/contracts/token_ops/Forc.toml ================================================ [project] authors = ["Fuel Labs "] entry = "main.sw" license = "Apache-2.0" name = "token_ops" ================================================ FILE: e2e/sway/contracts/token_ops/src/main.sw ================================================ contract; use std::{asset::*, bytes::Bytes, context::balance_of, context::msg_amount, message::send_message}; abi TestFuelCoin { fn mint_coins(mint_amount: u64); fn mint_to_addresses(mint_amount: u64, addresses: [Identity; 3]); fn burn_coins(burn_amount: u64); fn transfer(coins: u64, asset_id: AssetId, target: Identity); fn get_balance(target: ContractId, asset_id: AssetId) -> u64; #[payable] fn get_msg_amount() -> u64; fn send_message(recipient: b256, coins: u64); } impl TestFuelCoin for Contract { fn mint_coins(mint_amount: u64) { mint(b256::zero(), mint_amount); } fn mint_to_addresses(mint_amount: u64, addresses: [Identity; 3]) { let mut counter = 0; while counter < 3 { mint_to(addresses[counter], b256::zero(), mint_amount); counter = counter + 1; } } fn burn_coins(burn_amount: u64) { burn(b256::zero(), burn_amount); } // ANCHOR: variable_outputs fn transfer(coins: u64, asset_id: AssetId, recipient: Identity) { transfer(recipient, asset_id, coins); } // ANCHOR_END: variable_outputs fn get_balance(target: ContractId, asset_id: AssetId) -> u64 { balance_of(target, asset_id) } #[payable] fn get_msg_amount() -> u64 { msg_amount() } fn send_message(recipient: b256, coins: u64) { let mut data = Bytes::new(); data.push(1u8); data.push(2u8); data.push(3u8); send_message(recipient, data, coins); } } ================================================ FILE: e2e/sway/contracts/transaction_block_height/Forc.toml ================================================ [project] authors = ["Fuel Labs "] entry = "main.sw" license = "Apache-2.0" name = "transaction_block_height" ================================================ FILE: e2e/sway/contracts/transaction_block_height/src/main.sw ================================================ contract; abi MyContract { fn get_current_height() -> u32; fn calling_this_will_produce_a_block(); } impl MyContract for Contract { fn get_current_height() -> u32 { std::block::height() } fn calling_this_will_produce_a_block() {} } ================================================ FILE: e2e/sway/contracts/tx_input_output/Forc.toml ================================================ [project] authors = ["Fuel Labs "] entry = "main.sw" license = "Apache-2.0" name = "tx_input_output" ================================================ FILE: e2e/sway/contracts/tx_input_output/src/main.sw ================================================ contract; use std::{inputs::*, outputs::*}; configurable { ASSET_ID: AssetId = AssetId::zero(), OWNER: Address = Address::zero(), } abi TxContractTest { fn check_input(index: u64); fn check_output_is_change(index: u64); } impl TxContractTest for Contract { fn check_input(index: u64) { // Checks if coin and maybe returns owner if let Some(owner) = input_coin_owner(index) { require(owner == OWNER, "wrong owner"); let asset_id = input_asset_id(index).unwrap(); require(asset_id == ASSET_ID, "wrong asset id"); } else { revert_with_log("input is not a coin"); } } fn check_output_is_change(index: u64) { if let Some(Output::Change) = output_type(index) { let asset_to = output_asset_to(index).unwrap(); require(asset_to == OWNER, "wrong change address"); } else { revert_with_log("output is not change"); } } } ================================================ FILE: e2e/sway/contracts/var_outputs/Forc.toml ================================================ [project] authors = ["Fuel Labs "] entry = "main.sw" license = "Apache-2.0" name = "var_outputs" ================================================ FILE: e2e/sway/contracts/var_outputs/src/main.sw ================================================ contract; abi MyContract { fn mint(coins: u64, recipient: Identity); } impl MyContract for Contract { fn mint(coins: u64, recipient: Identity) { let mut counter = 0; while counter < coins { counter += 1; std::asset::mint_to(recipient, b256::zero(), 1); } } } ================================================ FILE: e2e/sway/logs/contract_logs/Forc.toml ================================================ [project] authors = ["Fuel Labs "] entry = "main.sw" license = "Apache-2.0" name = "contract_logs" [dependencies] contract_logs_abi = { path = "../contract_logs_abi/", package = "contract_logs_abi" } ================================================ FILE: e2e/sway/logs/contract_logs/src/main.sw ================================================ contract; use std::{logging::log, string::String}; use contract_logs_abi::ContractLogs; struct B { id: u64, val: u64, } #[error_type] enum MyError { #[error(m = "some error A")] A: (), #[error(m = "some complex error B")] B: B, } #[allow(dead_code)] struct TestStruct { field_1: bool, field_2: b256, field_3: u64, } #[allow(dead_code)] enum TestEnum { VariantOne: (), VariantTwo: (), } #[allow(dead_code)] struct StructWithGeneric { field_1: D, field_2: u64, } #[allow(dead_code)] enum EnumWithGeneric { VariantOne: D, VariantTwo: (), } #[allow(dead_code)] struct StructWithNestedGeneric { field_1: D, field_2: u64, } #[allow(dead_code)] struct StructDeeplyNestedGeneric { field_1: D, field_2: u64, } impl ContractLogs for Contract { fn produce_logs_values() { log(64u64); log(32u32); log(16u16); log(8u8); } // ANCHOR: produce_logs fn produce_logs_variables() { let f: u64 = 64; let u: b256 = 0xef86afa9696cf0dc6385e2c407a6e159a1103cefb7e2ae0636fb33d3cb2a9e4a; let e: str[4] = __to_str_array("Fuel"); let l: [u8; 3] = [1u8, 2u8, 3u8]; log(f); log(u); log(e); log(l); } // ANCHOR_END: produce_logs fn produce_logs_custom_types() { let f: u64 = 64; let u: b256 = 0xef86afa9696cf0dc6385e2c407a6e159a1103cefb7e2ae0636fb33d3cb2a9e4a; let test_struct = TestStruct { field_1: true, field_2: u, field_3: f, }; let test_enum = TestEnum::VariantTwo; log(test_struct); log(test_enum); log((test_struct, test_enum)); } fn produce_logs_generic_types() { let l: [u8; 3] = [1u8, 2u8, 3u8]; let test_struct = StructWithGeneric { field_1: l, field_2: 64, }; let test_enum = EnumWithGeneric::VariantOne(l); let test_struct_nested = StructWithNestedGeneric { field_1: test_struct, field_2: 64, }; let test_deeply_nested_generic = StructDeeplyNestedGeneric { field_1: test_struct_nested, field_2: 64, }; log(test_struct); log(test_enum); log(test_struct_nested); log(test_deeply_nested_generic); } fn produce_multiple_logs() { let f: u64 = 64; let u: b256 = 0xef86afa9696cf0dc6385e2c407a6e159a1103cefb7e2ae0636fb33d3cb2a9e4a; let e: str[4] = __to_str_array("Fuel"); let l: [u8; 3] = [1u8, 2u8, 3u8]; let test_struct = TestStruct { field_1: true, field_2: u, field_3: f, }; let test_enum = TestEnum::VariantTwo; let test_generic_struct = StructWithGeneric { field_1: test_struct, field_2: 64, }; log(64); log(32u32); log(16u16); log(8u8); log(f); log(u); log(e); log(l); log(test_struct); log(test_enum); log(test_generic_struct); } fn produce_bad_logs() { // produce a custom log with log id 128 // this log id will not be present in abi JSON asm(r1: 0, r2: 128, r3: 0, r4: 0) { log r1 r2 r3 r4; } log(123); } fn produce_string_slice_log() { log("fuel"); } fn produce_string_log() { log(String::from_ascii_str("fuel")); } fn produce_bytes_log() { log(String::from_ascii_str("fuel").as_bytes()); } fn produce_raw_slice_log() { log(String::from_ascii_str("fuel").as_raw_slice()); } fn produce_vec_log() { let mut v = Vec::new(); v.push(1u16); v.push(2u16); v.push(3u16); let some_enum = EnumWithGeneric::VariantOne(v); let other_enum = EnumWithGeneric::VariantTwo; let mut v1 = Vec::new(); v1.push(some_enum); v1.push(other_enum); v1.push(some_enum); let mut v2 = Vec::new(); v2.push(v1); v2.push(v1); let mut v3 = Vec::new(); v3.push(v2); log(v3); } fn produce_panic() { panic "some panic message"; } fn produce_panic_with_error() { panic MyError::B(B { id: 42, val: 36, }); } } ================================================ FILE: e2e/sway/logs/contract_logs_abi/Forc.toml ================================================ [project] authors = ["Fuel Labs "] entry = "main.sw" license = "Apache-2.0" name = "contract_logs_abi" ================================================ FILE: e2e/sway/logs/contract_logs_abi/src/main.sw ================================================ library; use std::logging::log; abi ContractLogs { fn produce_logs_values(); fn produce_logs_variables(); fn produce_logs_custom_types(); fn produce_logs_generic_types(); fn produce_multiple_logs(); fn produce_bad_logs(); fn produce_string_slice_log(); fn produce_string_log(); fn produce_bytes_log(); fn produce_raw_slice_log(); fn produce_vec_log(); fn produce_panic(); fn produce_panic_with_error(); } ================================================ FILE: e2e/sway/logs/contract_revert_logs/Forc.toml ================================================ [project] authors = ["Fuel Labs "] entry = "main.sw" license = "Apache-2.0" name = "contract_revert_logs" ================================================ FILE: e2e/sway/logs/contract_revert_logs/src/main.sw ================================================ contract; use std::logging::log; #[allow(dead_code)] enum EnumWithGeneric { VariantOne: D, VariantTwo: (), } #[allow(dead_code)] struct StructWithNestedGeneric { field_1: D, field_2: u64, } #[allow(dead_code)] struct StructDeeplyNestedGeneric { field_1: D, field_2: u64, } abi TestContract { fn require_primitive(); fn require_string(); fn require_custom_generic(); fn require_with_additional_logs(); fn rev_w_log_primitive(); fn rev_w_log_string(); fn rev_w_log_custom_generic(); } impl TestContract for Contract { fn require_primitive() { require(false, 42); } fn require_string() { require(false, __to_str_array("fuel")); } fn require_custom_generic() { let l: [u8; 3] = [1u8, 2u8, 3u8]; let test_enum = EnumWithGeneric::VariantOne(l); let test_struct_nested = StructWithNestedGeneric { field_1: test_enum, field_2: 64, }; let test_deeply_nested_generic = StructDeeplyNestedGeneric { field_1: test_struct_nested, field_2: 64, }; require(false, test_deeply_nested_generic); } fn require_with_additional_logs() { log(42); log(__to_str_array("fuel")); require(false, 64); } fn rev_w_log_primitive() { revert_with_log(42); } fn rev_w_log_string() { revert_with_log(__to_str_array("fuel")); } fn rev_w_log_custom_generic() { let l: [u8; 3] = [1u8, 2u8, 3u8]; let test_enum = EnumWithGeneric::VariantOne(l); let test_struct_nested = StructWithNestedGeneric { field_1: test_enum, field_2: 64, }; let test_deeply_nested_generic = StructDeeplyNestedGeneric { field_1: test_struct_nested, field_2: 64, }; revert_with_log(test_deeply_nested_generic); } } ================================================ FILE: e2e/sway/logs/contract_with_contract_logs/Forc.toml ================================================ [project] authors = ["Fuel Labs "] entry = "main.sw" license = "Apache-2.0" name = "contract_with_contract_logs" [dependencies] library = { path = "../contract_logs_abi/", package = "contract_logs_abi" } ================================================ FILE: e2e/sway/logs/contract_with_contract_logs/src/main.sw ================================================ contract; use std::logging::log; use library::ContractLogs; abi ContractCaller { fn logs_from_external_contract(contract_id: ContractId) -> (); fn panic_from_external_contract(contract_id: ContractId) -> (); fn panic_error_from_external_contract(contract_id: ContractId) -> (); } impl ContractCaller for Contract { fn logs_from_external_contract(contract_id: ContractId) { // Call contract with `contract_id` and make some logs let contract_instance = abi(ContractLogs, contract_id.into()); contract_instance.produce_logs_values(); } fn panic_from_external_contract(contract_id: ContractId) { // Call contract with `contract_id` and make some logs let contract_instance = abi(ContractLogs, contract_id.into()); contract_instance.produce_panic(); } fn panic_error_from_external_contract(contract_id: ContractId) { // Call contract with `contract_id` and make some logs let contract_instance = abi(ContractLogs, contract_id.into()); contract_instance.produce_panic_with_error(); } } ================================================ FILE: e2e/sway/logs/script_heap_logs/Forc.toml ================================================ [project] authors = ["Fuel Labs "] entry = "main.sw" license = "Apache-2.0" name = "script_heap_logs" ================================================ FILE: e2e/sway/logs/script_heap_logs/src/main.sw ================================================ script; use std::{logging::log, string::String}; #[allow(dead_code)] enum EnumWithGeneric { VariantOne: D, VariantTwo: (), } fn main() { // String slice log("fuel"); // String log(String::from_ascii_str("fuel")); // Bytes log(String::from_ascii_str("fuel").as_bytes()); // RawSlice log(String::from_ascii_str("fuel").as_raw_slice()); // Vector let mut v = Vec::new(); v.push(1u16); v.push(2u16); v.push(3u16); let some_enum = EnumWithGeneric::VariantOne(v); let other_enum = EnumWithGeneric::VariantTwo; let mut v1 = Vec::new(); v1.push(some_enum); v1.push(other_enum); v1.push(some_enum); let mut v2 = Vec::new(); v2.push(v1); v2.push(v1); let mut v3 = Vec::new(); v3.push(v2); log(v3); } ================================================ FILE: e2e/sway/logs/script_logs/Forc.toml ================================================ [project] authors = ["Fuel Labs "] entry = "main.sw" license = "Apache-2.0" name = "script_logs" ================================================ FILE: e2e/sway/logs/script_logs/src/main.sw ================================================ script; use std::logging::log; #[allow(dead_code)] struct TestStruct { field_1: bool, field_2: b256, field_3: u64, } #[allow(dead_code)] enum TestEnum { VariantOne: (), VariantTwo: (), } #[allow(dead_code)] struct StructWithGeneric { field_1: D, field_2: u64, } #[allow(dead_code)] enum EnumWithGeneric { VariantOne: D, VariantTwo: (), } #[allow(dead_code)] struct StructWithNestedGeneric { field_1: D, field_2: u64, } #[allow(dead_code)] struct StructDeeplyNestedGeneric { field_1: D, field_2: u64, } fn main() { let f: u64 = 64; let u: b256 = 0xef86afa9696cf0dc6385e2c407a6e159a1103cefb7e2ae0636fb33d3cb2a9e4a; let e: str[4] = __to_str_array("Fuel"); let l: [u8; 3] = [1u8, 2u8, 3u8]; let test_struct = TestStruct { field_1: true, field_2: u, field_3: f, }; let test_enum = TestEnum::VariantTwo; let test_generic_struct = StructWithGeneric { field_1: test_struct, field_2: 64, }; let test_generic_enum = EnumWithGeneric::VariantOne(l); let test_struct_nested = StructWithNestedGeneric { field_1: test_generic_struct, field_2: 64, }; let test_deeply_nested_generic = StructDeeplyNestedGeneric { field_1: test_struct_nested, field_2: 64, }; log(128); log(32u32); log(16u16); log(8u8); log(f); log(u); log(e); log(l); log(test_struct); log(test_enum); log((test_struct, test_enum)); log(test_generic_struct); log(test_generic_enum); log(test_struct_nested); log(test_deeply_nested_generic); } ================================================ FILE: e2e/sway/logs/script_revert_logs/Forc.toml ================================================ [project] authors = ["Fuel Labs "] entry = "main.sw" license = "Apache-2.0" name = "script_revert_logs" ================================================ FILE: e2e/sway/logs/script_revert_logs/src/main.sw ================================================ script; use std::logging::log; #[allow(dead_code)] enum EnumWithGeneric { VariantOne: D, VariantTwo: (), } #[allow(dead_code)] struct StructWithNestedGeneric { field_1: D, field_2: u64, } #[allow(dead_code)] struct StructDeeplyNestedGeneric { field_1: D, field_2: u64, } struct B { id: u64, val: u64, } #[error_type] enum MyError { #[error(m = "some error A")] A: (), #[error(m = "some complex error B")] B: B, } #[allow(dead_code)] enum MatchEnum { RequirePrimitive: (), RequireString: (), RequireCustomGeneric: (), RequireWithAdditionalLogs: (), RevWLogPrimitive: (), RevWLogString: (), RevWLogCustomGeneric: (), Panic: (), PanicError: (), } fn main(match_enum: MatchEnum) { match match_enum { MatchEnum::RequirePrimitive => require(false, 42), MatchEnum::RequireString => require(false, __to_str_array("fuel")), MatchEnum::RequireCustomGeneric => { let l: [u8; 3] = [1u8, 2u8, 3u8]; let test_enum = EnumWithGeneric::VariantOne(l); let test_struct_nested = StructWithNestedGeneric { field_1: test_enum, field_2: 64, }; let test_deeply_nested_generic = StructDeeplyNestedGeneric { field_1: test_struct_nested, field_2: 64, }; require(false, test_deeply_nested_generic); } MatchEnum::RequireWithAdditionalLogs => { log(42); log(__to_str_array("fuel")); require(false, 64); } MatchEnum::RevWLogPrimitive => revert_with_log(42), MatchEnum::RevWLogString => revert_with_log(__to_str_array("fuel")), MatchEnum::RevWLogCustomGeneric => { let l: [u8; 3] = [1u8, 2u8, 3u8]; let test_enum = EnumWithGeneric::VariantOne(l); let test_struct_nested = StructWithNestedGeneric { field_1: test_enum, field_2: 64, }; let test_deeply_nested_generic = StructDeeplyNestedGeneric { field_1: test_struct_nested, field_2: 64, }; revert_with_log(test_deeply_nested_generic); } MatchEnum::Panic => panic "some panic message", MatchEnum::PanicError => panic MyError::B(B { id: 42, val: 36, }), } } ================================================ FILE: e2e/sway/logs/script_with_contract_logs/Forc.toml ================================================ [project] authors = ["Fuel Labs "] entry = "main.sw" license = "Apache-2.0" name = "script_with_contract_logs" [dependencies] library = { path = "../contract_logs_abi/", package = "contract_logs_abi" } ================================================ FILE: e2e/sway/logs/script_with_contract_logs/src/main.sw ================================================ script; use std::logging::log; use library::ContractLogs; #[allow(dead_code)] enum MatchEnum { Logs: (), Panic: (), PanicError: (), } fn main(contract_id: ContractId, match_enum: MatchEnum) { let contract_instance = abi(ContractLogs, contract_id.into()); match match_enum { MatchEnum::Logs => { contract_instance.produce_logs_values(); let f: bool = true; let u: u64 = 42; let e: str[4] = __to_str_array("Fuel"); let l: [u8; 3] = [1u8, 2u8, 3u8]; log(f); log(u); log(e); log(l); } MatchEnum::Panic => { contract_instance.produce_panic(); } MatchEnum::PanicError => { contract_instance.produce_panic_with_error(); } } } ================================================ FILE: e2e/sway/predicates/basic_predicate/Forc.toml ================================================ [project] authors = ["Fuel Labs "] entry = "main.sw" license = "Apache-2.0" name = "basic_predicate" ================================================ FILE: e2e/sway/predicates/basic_predicate/src/main.sw ================================================ predicate; fn main(a: u32, b: u64) -> bool { b == a.as_u64() } ================================================ FILE: e2e/sway/predicates/predicate_blobs/Forc.toml ================================================ [project] authors = ["Fuel Labs "] entry = "main.sw" license = "Apache-2.0" name = "predicate_blobs" ================================================ FILE: e2e/sway/predicates/predicate_blobs/src/main.sw ================================================ predicate; configurable { SECRET_NUMBER: u64 = 9000, } fn main(arg1: u8, arg2: u8) -> bool { arg1 == 1 && arg2 == 19 && SECRET_NUMBER == 10001 } ================================================ FILE: e2e/sway/predicates/predicate_configurables/Forc.toml ================================================ [project] authors = ["Fuel Labs "] entry = "main.sw" license = "Apache-2.0" name = "predicate_configurables" ================================================ FILE: e2e/sway/predicates/predicate_configurables/src/main.sw ================================================ predicate; impl PartialEq for StructWithGeneric { fn eq(self, other: Self) -> bool { self.field_1 == other.field_1 && self.field_2 == other.field_2 } } impl Eq for StructWithGeneric {} impl PartialEq for EnumWithGeneric { fn eq(self, other: Self) -> bool { match (self, other) { (EnumWithGeneric::VariantOne, EnumWithGeneric::VariantOne) => true, (EnumWithGeneric::VariantTwo, EnumWithGeneric::VariantTwo) => true, _ => false, } } } impl Eq for EnumWithGeneric {} // ANCHOR: predicate_configurables #[allow(dead_code)] enum EnumWithGeneric { VariantOne: D, VariantTwo: (), } struct StructWithGeneric { field_1: D, field_2: u64, } configurable { BOOL: bool = true, U8: u8 = 8, TUPLE: (u8, bool) = (8, true), ARRAY: [u32; 3] = [253, 254, 255], STRUCT: StructWithGeneric = StructWithGeneric { field_1: 8, field_2: 16, }, ENUM: EnumWithGeneric = EnumWithGeneric::VariantOne(true), } fn main( switch: bool, u_8: u8, some_tuple: (u8, bool), some_array: [u32; 3], some_struct: StructWithGeneric, some_enum: EnumWithGeneric, ) -> bool { switch == BOOL && u_8 == U8 && some_tuple.0 == TUPLE.0 && some_tuple.1 == TUPLE.1 && some_array[0] == ARRAY[0] && some_array[1] == ARRAY[1] && some_array[2] == ARRAY[2] && some_struct == STRUCT && some_enum == ENUM } // ANCHOR_END: predicate_configurables ================================================ FILE: e2e/sway/predicates/predicate_tx_input_output/Forc.toml ================================================ [project] authors = ["Fuel Labs "] entry = "main.sw" license = "Apache-2.0" name = "predicate_tx_input_output" ================================================ FILE: e2e/sway/predicates/predicate_tx_input_output/src/main.sw ================================================ predicate; use std::{inputs::*, outputs::*}; configurable { ASSET_ID: AssetId = AssetId::zero(), OWNER: Address = Address::zero(), } fn main(input_index: u64, output_index: u64) -> bool { // Checks if coin and maybe returns owner let input_ok = if let Some(owner) = input_coin_owner(input_index) { let is_owner = owner == OWNER; let asset_id = input_asset_id(input_index).unwrap(); is_owner && asset_id == ASSET_ID } else { false }; let output_ok = if let Some(Output::Change) = output_type(output_index) { let asset_to = output_asset_to(output_index).unwrap(); asset_to == OWNER } else { false }; input_ok && output_ok } ================================================ FILE: e2e/sway/predicates/predicate_witnesses/Forc.toml ================================================ [project] authors = ["Fuel Labs "] entry = "main.sw" license = "Apache-2.0" name = "predicate_witnesses" ================================================ FILE: e2e/sway/predicates/predicate_witnesses/src/main.sw ================================================ predicate; use std::tx::tx_witness_data; fn main(witness_index: u64, witness_index2: u64) -> bool { let witness: u8 = tx_witness_data(witness_index).unwrap(); let witness2: u64 = tx_witness_data(witness_index2).unwrap(); witness == 64 && witness2 == 4096 } ================================================ FILE: e2e/sway/predicates/signatures/Forc.toml ================================================ [project] authors = ["Fuel Labs "] entry = "main.sw" license = "Apache-2.0" name = "signatures" ================================================ FILE: e2e/sway/predicates/signatures/src/main.sw ================================================ predicate; use std::{ b512::B512, crypto::{ message::Message, secp256k1::Secp256k1, }, inputs::input_predicate_data, }; fn extract_public_key_and_match(signature: B512, expected_public_key: b256) -> u64 { let signature = Secp256k1::from(signature); if let Result::Ok(pub_key_sig) = signature.address(Message::from(b256::zero())) { if pub_key_sig == Address::from(expected_public_key) { return 1; } } 0 } fn main(signatures: [B512; 3]) -> bool { let public_keys = [ 0xd58573593432a30a800f97ad32f877425c223a9e427ab557aab5d5bb89156db0, 0x14df7c7e4e662db31fe2763b1734a3d680e7b743516319a49baaa22b2032a857, 0x3ff494fb136978c3125844625dad6baf6e87cdb1328c8a51f35bda5afe72425c, ]; let mut matched_keys = 0; matched_keys = extract_public_key_and_match(signatures[0], public_keys[0]); matched_keys = matched_keys + extract_public_key_and_match(signatures[1], public_keys[1]); matched_keys = matched_keys + extract_public_key_and_match(signatures[2], public_keys[2]); matched_keys > 1 } ================================================ FILE: e2e/sway/predicates/swap/Forc.toml ================================================ [project] authors = ["Fuel Labs "] entry = "main.sw" license = "Apache-2.0" name = "swap" [dependencies] ================================================ FILE: e2e/sway/predicates/swap/src/main.sw ================================================ predicate; use std::outputs::{Output, output_amount, output_asset_id, output_asset_to}; fn main() -> bool { let receiver = Address::from(0x09c0b2d1a486c439a87bcba6b46a7a1a23f3897cc83a94521a96da5c23bc58db); let ask_amount = 100; let output_index = 0; let to = Address::from(output_asset_to(output_index).unwrap()); let asset_id = output_asset_id(output_index).unwrap(); let amount = output_amount(output_index).unwrap(); (to == receiver) && (amount == ask_amount) && (asset_id == AssetId::zero()) } ================================================ FILE: e2e/sway/scripts/arguments/Forc.toml ================================================ [project] authors = ["Fuel Labs "] entry = "main.sw" license = "Apache-2.0" name = "arguments" ================================================ FILE: e2e/sway/scripts/arguments/src/main.sw ================================================ script; struct Bimbam { val: u64, } struct SugarySnack { twix: u64, mars: u64, } fn main(bim: Bimbam, bam: SugarySnack) -> Bimbam { let val = bam.twix + bim.val + (bam.mars * 2); Bimbam { val: val } } ================================================ FILE: e2e/sway/scripts/basic_script/Forc.toml ================================================ [project] authors = ["Fuel Labs "] entry = "main.sw" license = "Apache-2.0" name = "basic_script" ================================================ FILE: e2e/sway/scripts/basic_script/src/main.sw ================================================ script; /// Compares a to b and returns a `str[5]` fn main(a: u64, b: u32) -> str[5] { if a < b.as_u64() { let my_string: str[5] = __to_str_array("hello"); my_string } else { let my_string: str[5] = __to_str_array("heyoo"); my_string } } ================================================ FILE: e2e/sway/scripts/empty/Forc.toml ================================================ [project] authors = ["Fuel Labs "] entry = "main.sw" license = "Apache-2.0" name = "empty" [dependencies] ================================================ FILE: e2e/sway/scripts/empty/src/main.sw ================================================ script; fn main() {} ================================================ FILE: e2e/sway/scripts/require_from_contract/Forc.toml ================================================ [project] authors = ["Fuel Labs "] entry = "main.sw" license = "Apache-2.0" name = "require_from_contract" [dependencies] library = { path = "../../contracts/lib_contract_abi", package = "lib_contract_abi" } ================================================ FILE: e2e/sway/scripts/require_from_contract/src/main.sw ================================================ script; use library::LibContract; fn main(contract_id: ContractId) { let contract_instance = abi(LibContract, contract_id.into()); contract_instance.require(); } ================================================ FILE: e2e/sway/scripts/reverting/Forc.toml ================================================ [project] authors = ["Fuel Labs "] entry = "main.sw" license = "Apache-2.0" name = "reverting" ================================================ FILE: e2e/sway/scripts/reverting/src/main.sw ================================================ script; fn main() { assert(false) } ================================================ FILE: e2e/sway/scripts/script_array/Forc.toml ================================================ [project] authors = ["Fuel Labs "] entry = "main.sw" license = "Apache-2.0" name = "script_array" ================================================ FILE: e2e/sway/scripts/script_array/src/main.sw ================================================ script; fn main(foo: [u64; 4]) -> u64 { foo[0] + foo[1] + foo[2] + foo[3] } ================================================ FILE: e2e/sway/scripts/script_asserts/Forc.toml ================================================ [project] authors = ["Fuel Labs "] entry = "main.sw" license = "Apache-2.0" name = "script_asserts" ================================================ FILE: e2e/sway/scripts/script_asserts/src/main.sw ================================================ script; struct TestStruct { field_1: bool, field_2: u64, } #[allow(dead_code)] enum TestEnum { VariantOne: (), VariantTwo: (), } impl PartialEq for TestStruct { fn eq(self, other: Self) -> bool { self.field_1 == other.field_1 && self.field_2 == other.field_2 } } impl Eq for TestStruct {} impl PartialEq for TestEnum { fn eq(self, other: Self) -> bool { match (self, other) { (TestEnum::VariantOne, TestEnum::VariantOne) => true, (TestEnum::VariantTwo, TestEnum::VariantTwo) => true, _ => false, } } } impl Eq for TestEnum {} #[allow(dead_code)] enum MatchEnum { AssertPrimitive: (u64, u64), AssertEqPrimitive: (u64, u64), AssertEqStruct: (TestStruct, TestStruct), AssertEqEnum: (TestEnum, TestEnum), AssertNePrimitive: (u64, u64), AssertNeStruct: (TestStruct, TestStruct), AssertNeEnum: (TestEnum, TestEnum), } fn main(match_enum: MatchEnum) { if let MatchEnum::AssertPrimitive((a, b)) = match_enum { assert(a == b); } else if let MatchEnum::AssertEqPrimitive((a, b)) = match_enum { assert_eq(a, b); } else if let MatchEnum::AssertEqStruct((test_struct, test_struct2)) = match_enum { assert_eq(test_struct, test_struct2); } else if let MatchEnum::AssertEqEnum((test_enum, test_enum2)) = match_enum { assert_eq(test_enum, test_enum2); } else if let MatchEnum::AssertNePrimitive((a, b)) = match_enum { assert_ne(a, b); } else if let MatchEnum::AssertNeStruct((test_struct, test_struct2)) = match_enum { assert_ne(test_struct, test_struct2); } else if let MatchEnum::AssertNeEnum((test_enum, test_enum2)) = match_enum { assert_ne(test_enum, test_enum2); } } ================================================ FILE: e2e/sway/scripts/script_blobs/Forc.toml ================================================ [project] authors = ["Fuel Labs "] entry = "main.sw" license = "Apache-2.0" name = "script_blobs" ================================================ FILE: e2e/sway/scripts/script_blobs/src/main.sw ================================================ script; configurable { SECRET_NUMBER: u64 = 9000, } enum MyEnum { A: u64, B: u8, C: (), } struct MyStruct { field_a: MyEnum, field_b: b256, } fn main(arg1: MyStruct) -> u64 { assert_eq(SECRET_NUMBER, 10001); match arg1.field_a { MyEnum::B(value) => { assert_eq(value, 99); } _ => { assert(false) } } assert_eq( arg1.field_b, 0x1111111111111111111111111111111111111111111111111111111111111111, ); return SECRET_NUMBER; } ================================================ FILE: e2e/sway/scripts/script_configurables/Forc.toml ================================================ [project] authors = ["Fuel Labs "] entry = "main.sw" license = "Apache-2.0" name = "script_configurables" ================================================ FILE: e2e/sway/scripts/script_configurables/src/main.sw ================================================ script; #[allow(dead_code)] enum EnumWithGeneric { VariantOne: D, VariantTwo: (), } struct StructWithGeneric { field_1: D, field_2: u64, } configurable { BOOL: bool = true, U8: u8 = 8, U16: u16 = 16, U32: u32 = 32, U64: u64 = 63, U256: u256 = 0x0000000000000000000000000000000000000000000000000000000000000008u256, B256: b256 = 0x0101010101010101010101010101010101010101010101010101010101010101, STR_4: str[4] = __to_str_array("fuel"), TUPLE: (u8, bool) = (8, true), ARRAY: [u32; 3] = [253, 254, 255], STRUCT: StructWithGeneric = StructWithGeneric { field_1: 8, field_2: 16, }, ENUM: EnumWithGeneric = EnumWithGeneric::VariantOne(true), } //U128: u128 = 128, //TODO: add once https://github.com/FuelLabs/sway/issues/5356 is done fn main() -> (bool, u8, u16, u32, u64, u256, b256, str[4], (u8, bool), [u32; 3], StructWithGeneric, EnumWithGeneric) { (BOOL, U8, U16, U32, U64, U256, B256, STR_4, TUPLE, ARRAY, STRUCT, ENUM) } ================================================ FILE: e2e/sway/scripts/script_enum/Forc.toml ================================================ [project] authors = ["Fuel Labs "] entry = "main.sw" license = "Apache-2.0" name = "script_enum" ================================================ FILE: e2e/sway/scripts/script_enum/src/main.sw ================================================ script; #[allow(dead_code)] enum MyEnum { One: (), Two: (), Three: (), } fn main(my_enum: MyEnum) -> u64 { match my_enum { MyEnum::One => 1, MyEnum::Two => 2, MyEnum::Three => 3, } } ================================================ FILE: e2e/sway/scripts/script_needs_custom_decoder/Forc.toml ================================================ [project] authors = ["Fuel Labs "] entry = "main.sw" license = "Apache-2.0" name = "script_needs_custom_decoder" [dependencies] ================================================ FILE: e2e/sway/scripts/script_needs_custom_decoder/src/main.sw ================================================ script; impl AbiEncode for [u8; 1000] { #[allow(dead_code)] fn abi_encode(self, buffer: Buffer) -> Buffer { let mut buffer = buffer; let mut i = 0; while i < 1000 { buffer = self[i].abi_encode(buffer); i += 1; }; buffer } } fn main(log_instead_of_return: bool) -> Option<[u8; 1000]> { let arr: [u8; 1000] = [0; 1000]; if log_instead_of_return { log(arr); return None; } Some(arr) } ================================================ FILE: e2e/sway/scripts/script_proxy/Forc.toml ================================================ [project] authors = ["Fuel Labs "] entry = "main.sw" license = "Apache-2.0" name = "script_proxy" ================================================ FILE: e2e/sway/scripts/script_proxy/src/main.sw ================================================ script; abi Proxy { #[storage(write)] fn set_target_contract(id: ContractId); // methods of the `huge_contract` in our e2e sway contracts #[storage(read)] fn something() -> u64; #[storage(read)] fn write_some_u64(some: u64); #[storage(read)] fn read_some_u64() -> u64; } fn main(proxy_contract_id: ContractId) -> bool { let proxy_instance = abi(Proxy, proxy_contract_id.into()); let _ = proxy_instance.something(); proxy_instance.write_some_u64(10001); let read_u_64 = proxy_instance.read_some_u64(); return read_u_64 == 10001; } ================================================ FILE: e2e/sway/scripts/script_struct/Forc.toml ================================================ [project] authors = ["Fuel Labs "] entry = "main.sw" license = "Apache-2.0" name = "script_struct" ================================================ FILE: e2e/sway/scripts/script_struct/src/main.sw ================================================ script; configurable { MY_STRUCT: MyStruct = MyStruct { number: 10, boolean: true, }, A_NUMBER: u64 = 11, } struct MyStruct { number: u64, boolean: bool, } fn main(arg: MyStruct) -> u64 { let _calc = MY_STRUCT.number + A_NUMBER; if arg.boolean { arg.number } else { 0 } } ================================================ FILE: e2e/sway/scripts/script_tx_input_output/Forc.toml ================================================ [project] authors = ["Fuel Labs "] entry = "main.sw" license = "Apache-2.0" name = "script_tx_input_output" ================================================ FILE: e2e/sway/scripts/script_tx_input_output/src/main.sw ================================================ script; use std::{inputs::*, outputs::*}; configurable { ASSET_ID: AssetId = AssetId::zero(), OWNER: Address = Address::zero(), } fn main(input_index: u64, output_index: u64) { // Checks if coin and maybe returns owner if let Some(owner) = input_coin_owner(input_index) { require(owner == OWNER, "wrong owner"); let asset_id = input_asset_id(input_index).unwrap(); require(asset_id == ASSET_ID, "wrong asset id"); } else { revert_with_log("input is not a coin"); } if let Some(Output::Change) = output_type(output_index) { let asset_to = output_asset_to(output_index).unwrap(); require(asset_to == OWNER, "wrong change address"); } else { revert_with_log("output is not change"); } } ================================================ FILE: e2e/sway/scripts/transfer_script/Forc.toml ================================================ [project] authors = ["Fuel Labs "] entry = "main.sw" license = "Apache-2.0" name = "transfer_script" ================================================ FILE: e2e/sway/scripts/transfer_script/src/main.sw ================================================ script; use std::asset::transfer; fn main(amount: u64, asset: AssetId, receiver: Identity) -> () { transfer(receiver, asset, amount); } ================================================ FILE: e2e/sway/types/contracts/b256/Forc.toml ================================================ [project] authors = ["Fuel Labs "] entry = "main.sw" license = "Apache-2.0" name = "b256" ================================================ FILE: e2e/sway/types/contracts/b256/src/main.sw ================================================ contract; abi MyContract { fn b256_as_output() -> b256; fn b256_as_input(foo: b256) -> bool; } impl MyContract for Contract { fn b256_as_output() -> b256 { 0x0202020202020202020202020202020202020202020202020202020202020202 } fn b256_as_input(foo: b256) -> bool { foo == 0x0101010101010101010101010101010101010101010101010101010101010101 } } ================================================ FILE: e2e/sway/types/contracts/b512/Forc.toml ================================================ [project] authors = ["Fuel Labs "] entry = "main.sw" license = "Apache-2.0" name = "b512" ================================================ FILE: e2e/sway/types/contracts/b512/src/main.sw ================================================ contract; use std::b512::B512; const HI_BITS = 0xbd0c9b8792876713afa8bff383eebf31c43437823ed761cc3600d0016de5110c; const LO_BITS = 0x44ac566bd156b4fc71a4a4cb2655d3dd360c695edb17dc3b64d611e122fea23d; const LO_BITS2 = 0x54ac566bd156b4fc71a4a4cb2655d3dd360c695edb17dc3b64d611e122fea23d; abi MyContract { fn b512_as_output() -> B512; fn b512_as_input(b512: B512) -> bool; } impl MyContract for Contract { fn b512_as_output() -> B512 { B512::from((HI_BITS, LO_BITS)) } fn b512_as_input(b512: B512) -> bool { let expected_b512 = B512::from((HI_BITS, LO_BITS2)); b512 == expected_b512 } } ================================================ FILE: e2e/sway/types/contracts/bytes/Forc.toml ================================================ [project] authors = ["Fuel Labs "] entry = "main.sw" license = "Apache-2.0" name = "bytes" ================================================ FILE: e2e/sway/types/contracts/bytes/src/main.sw ================================================ contract; use std::bytes::Bytes; #[allow(dead_code)] enum SomeEnum { First: bool, Second: T, } struct Wrapper { inner: T, inner_enum: SomeEnum, } fn expected_bytes() -> Bytes { let mut bytes = Bytes::new(); bytes.push(40u8); bytes.push(41u8); bytes.push(42u8); bytes } abi MyContract { fn accept_bytes(bytes: Bytes); fn accept_nested_bytes(wrapper: Wrapper>); fn return_bytes(len: u8) -> Bytes; } impl MyContract for Contract { fn accept_bytes(bytes: Bytes) { require( bytes == expected_bytes(), "given bytes didn't match the expected bytes", ); } fn accept_nested_bytes(wrapper: Wrapper>) { if let SomeEnum::Second(enum_bytes) = wrapper.inner_enum { require( enum_bytes == expected_bytes(), "wrapper.inner_enum didn't carry the expected bytes", ); } else { require(false, "enum was not of variant Second"); } let inner_vec = wrapper.inner; require( inner_vec .len() == 2, "Expected wrapper.inner vector to have 2 elements", ); require( inner_vec .get(0) .unwrap() == expected_bytes(), "wrapper.inner[0] didn't match expectation", ); require( inner_vec .get(1) .unwrap() == expected_bytes(), "wrapper.inner[1] didn't match expectation", ); } fn return_bytes(len: u8) -> Bytes { let mut bytes = Bytes::new(); let mut i: u8 = 0; while i < len { bytes.push(i); i += 1u8; } bytes } } ================================================ FILE: e2e/sway/types/contracts/call_empty_return/Forc.toml ================================================ [project] authors = ["Fuel Labs "] entry = "main.sw" license = "Apache-2.0" name = "call_empty_return" ================================================ FILE: e2e/sway/types/contracts/call_empty_return/src/main.sw ================================================ contract; use std::storage::storage_api::write; abi TestContract { #[storage(write)] fn store_value(val: u64); } const COUNTER_KEY = 0x0000000000000000000000000000000000000000000000000000000000000000; impl TestContract for Contract { #[storage(write)] fn store_value(val: u64) { write(COUNTER_KEY, 0, val); } } ================================================ FILE: e2e/sway/types/contracts/complex_types_contract/Forc.toml ================================================ [project] authors = ["Fuel Labs "] entry = "main.sw" license = "Apache-2.0" name = "complex_types_contract" ================================================ FILE: e2e/sway/types/contracts/complex_types_contract/src/main.sw ================================================ contract; use std::storage::storage_api::{read, write}; struct EmptyStruct {} #[allow(dead_code)] struct CounterConfig { dummy: bool, initial_value: u64, } abi TestContract { #[storage(write)] fn initialize_counter(config: CounterConfig) -> u64; #[storage(read, write)] fn increment_counter(amount: u64) -> u64; fn get_empty_struct() -> EmptyStruct; fn input_empty_struct(es: EmptyStruct) -> bool; } const COUNTER_KEY = 0x0000000000000000000000000000000000000000000000000000000000000000; impl TestContract for Contract { #[storage(write)] fn initialize_counter(config: CounterConfig) -> u64 { let value = config.initial_value; write(COUNTER_KEY, 0, value); value } #[storage(read, write)] fn increment_counter(amount: u64) -> u64 { let value = read::(COUNTER_KEY, 0).unwrap_or(0) + amount; write(COUNTER_KEY, 0, value); value } fn get_empty_struct() -> EmptyStruct { EmptyStruct {} } fn input_empty_struct(es: EmptyStruct) -> bool { let EmptyStruct {} = es; true } } ================================================ FILE: e2e/sway/types/contracts/contract_output_test/Forc.toml ================================================ [project] authors = ["Fuel Labs "] entry = "main.sw" license = "Apache-2.0" name = "contract_output_test" ================================================ FILE: e2e/sway/types/contracts/contract_output_test/src/main.sw ================================================ contract; struct MyStruct { foo: u8, bar: bool, } abi TestContract { fn is_even(value: u64) -> bool; fn return_my_string(value: str[4]) -> str[4]; fn return_my_struct(value: MyStruct) -> MyStruct; } impl TestContract for Contract { fn is_even(value: u64) -> bool { if (value / 2) * 2 == value { true } else { false } } fn return_my_string(value: str[4]) -> str[4] { value } fn return_my_struct(value: MyStruct) -> MyStruct { value } } ================================================ FILE: e2e/sway/types/contracts/empty_arguments/Forc.toml ================================================ [project] authors = ["Fuel Labs "] entry = "main.sw" license = "Apache-2.0" name = "empty_arguments" ================================================ FILE: e2e/sway/types/contracts/empty_arguments/src/main.sw ================================================ contract; abi TestContract { fn method_with_empty_argument() -> u64; } impl TestContract for Contract { fn method_with_empty_argument() -> u64 { 63 } } ================================================ FILE: e2e/sway/types/contracts/enum_as_input/Forc.toml ================================================ [project] authors = ["Fuel Labs "] entry = "main.sw" license = "Apache-2.0" name = "enum_as_input" ================================================ FILE: e2e/sway/types/contracts/enum_as_input/src/main.sw ================================================ contract; #[allow(dead_code)] enum StandardEnum { One: b256, Two: u32, Three: bool, } #[allow(dead_code)] enum UnitEnum { One: (), Two: (), Three: (), } #[allow(dead_code)] enum MaxedOutVariantsEnum { Variant0: u8, Variant1: u8, Variant2: u8, Variant3: u8, Variant4: u8, Variant5: u8, Variant6: u8, Variant7: u8, Variant8: u8, Variant9: u8, Variant10: u8, Variant11: u8, Variant12: u8, Variant13: u8, Variant14: u8, Variant15: u8, Variant16: u8, Variant17: u8, Variant18: u8, Variant19: u8, Variant20: u8, Variant21: u8, Variant22: u8, Variant23: u8, Variant24: u8, Variant25: u8, Variant26: u8, Variant27: u8, Variant28: u8, Variant29: u8, Variant30: u8, Variant31: u8, Variant32: u8, Variant33: u8, Variant34: u8, Variant35: u8, Variant36: u8, Variant37: u8, Variant38: u8, Variant39: u8, Variant40: u8, Variant41: u8, Variant42: u8, Variant43: u8, Variant44: u8, Variant45: u8, Variant46: u8, Variant47: u8, Variant48: u8, Variant49: u8, Variant50: u8, Variant51: u8, Variant52: u8, Variant53: u8, Variant54: u8, Variant55: u8, Variant56: u8, Variant57: u8, Variant58: u8, Variant59: u8, Variant60: u8, Variant61: u8, Variant62: u8, Variant63: u8, Variant64: u8, Variant65: u8, Variant66: u8, Variant67: u8, Variant68: u8, Variant69: u8, Variant70: u8, Variant71: u8, Variant72: u8, Variant73: u8, Variant74: u8, Variant75: u8, Variant76: u8, Variant77: u8, Variant78: u8, Variant79: u8, Variant80: u8, Variant81: u8, Variant82: u8, Variant83: u8, Variant84: u8, Variant85: u8, Variant86: u8, Variant87: u8, Variant88: u8, Variant89: u8, Variant90: u8, Variant91: u8, Variant92: u8, Variant93: u8, Variant94: u8, Variant95: u8, Variant96: u8, Variant97: u8, Variant98: u8, Variant99: u8, Variant100: u8, Variant101: u8, Variant102: u8, Variant103: u8, Variant104: u8, Variant105: u8, Variant106: u8, Variant107: u8, Variant108: u8, Variant109: u8, Variant110: u8, Variant111: u8, Variant112: u8, Variant113: u8, Variant114: u8, Variant115: u8, Variant116: u8, Variant117: u8, Variant118: u8, Variant119: u8, Variant120: u8, Variant121: u8, Variant122: u8, Variant123: u8, Variant124: u8, Variant125: u8, Variant126: u8, Variant127: u8, Variant128: u8, Variant129: u8, Variant130: u8, Variant131: u8, Variant132: u8, Variant133: u8, Variant134: u8, Variant135: u8, Variant136: u8, Variant137: u8, Variant138: u8, Variant139: u8, Variant140: u8, Variant141: u8, Variant142: u8, Variant143: u8, Variant144: u8, Variant145: u8, Variant146: u8, Variant147: u8, Variant148: u8, Variant149: u8, Variant150: u8, Variant151: u8, Variant152: u8, Variant153: u8, Variant154: u8, Variant155: u8, Variant156: u8, Variant157: u8, Variant158: u8, Variant159: u8, Variant160: u8, Variant161: u8, Variant162: u8, Variant163: u8, Variant164: u8, Variant165: u8, Variant166: u8, Variant167: u8, Variant168: u8, Variant169: u8, Variant170: u8, Variant171: u8, Variant172: u8, Variant173: u8, Variant174: u8, Variant175: u8, Variant176: u8, Variant177: u8, Variant178: u8, Variant179: u8, Variant180: u8, Variant181: u8, Variant182: u8, Variant183: u8, Variant184: u8, Variant185: u8, Variant186: u8, Variant187: u8, Variant188: u8, Variant189: u8, Variant190: u8, Variant191: u8, Variant192: u8, Variant193: u8, Variant194: u8, Variant195: u8, Variant196: u8, Variant197: u8, Variant198: u8, Variant199: u8, Variant200: u8, Variant201: u8, Variant202: u8, Variant203: u8, Variant204: u8, Variant205: u8, Variant206: u8, Variant207: u8, Variant208: u8, Variant209: u8, Variant210: u8, Variant211: u8, Variant212: u8, Variant213: u8, Variant214: u8, Variant215: u8, Variant216: u8, Variant217: u8, Variant218: u8, Variant219: u8, Variant220: u8, Variant221: u8, Variant222: u8, Variant223: u8, Variant224: u8, Variant225: u8, Variant226: u8, Variant227: u8, Variant228: u8, Variant229: u8, Variant230: u8, Variant231: u8, Variant232: u8, Variant233: u8, Variant234: u8, Variant235: u8, Variant236: u8, Variant237: u8, Variant238: u8, Variant239: u8, Variant240: u8, Variant241: u8, Variant242: u8, Variant243: u8, Variant244: u8, Variant245: u8, Variant246: u8, Variant247: u8, Variant248: u8, Variant249: u8, Variant250: u8, Variant251: u8, Variant252: u8, Variant253: u8, Variant254: u8, Variant255: u8, } abi EnumTesting { fn get_standard_enum() -> StandardEnum; fn check_standard_enum_integrity(arg: StandardEnum) -> bool; fn get_max_variant() -> MaxedOutVariantsEnum; fn get_unit_enum() -> UnitEnum; fn check_unit_enum_integrity(arg: UnitEnum) -> bool; } impl EnumTesting for Contract { fn get_standard_enum() -> StandardEnum { StandardEnum::Two(12345u32) } fn check_standard_enum_integrity(arg: StandardEnum) -> bool { match arg { StandardEnum::Two(value) => { value == 12345u32 }, _ => { false } } } fn get_max_variant() -> MaxedOutVariantsEnum { MaxedOutVariantsEnum::Variant255(11) } fn get_unit_enum() -> UnitEnum { UnitEnum::Two } fn check_unit_enum_integrity(arg: UnitEnum) -> bool { match arg { UnitEnum::Two => { true }, _ => { false } } } } ================================================ FILE: e2e/sway/types/contracts/enum_encoding/Forc.toml ================================================ [project] authors = ["Fuel Labs "] entry = "main.sw" license = "Apache-2.0" name = "enum_encoding" ================================================ FILE: e2e/sway/types/contracts/enum_encoding/src/main.sw ================================================ contract; #[allow(dead_code)] enum EnumThatHasABigAndSmallVariant { Big: b256, Small: u32, } struct BigBundle { arg_1: EnumThatHasABigAndSmallVariant, arg_2: u64, arg_3: u64, arg_4: u64, } #[allow(dead_code)] enum UnitEnum { var1: (), var2: (), } struct UnitBundle { arg_1: UnitEnum, arg_2: u64, } abi EnumTesting { fn get_big_bundle() -> BigBundle; fn check_big_bundle_integrity(arg: BigBundle) -> bool; fn get_unit_bundle() -> UnitBundle; fn check_unit_bundle_integrity(arg: UnitBundle) -> bool; } impl EnumTesting for Contract { fn get_big_bundle() -> BigBundle { let arg_1 = EnumThatHasABigAndSmallVariant::Small(12345u32); let arg_2 = 6666; let arg_3 = 7777; let arg_4 = 8888; BigBundle { arg_1, arg_2, arg_3, arg_4, } } fn check_big_bundle_integrity(arg: BigBundle) -> bool { let arg_1_is_correct = match arg.arg_1 { EnumThatHasABigAndSmallVariant::Small(value) => { value == 12345u32 }, _ => false, }; arg_1_is_correct && arg.arg_2 == 6666 && arg.arg_3 == 7777 && arg.arg_4 == 8888 } fn get_unit_bundle() -> UnitBundle { UnitBundle { arg_1: UnitEnum::var2, arg_2: 18_446_744_073_709_551_615u64, } } fn check_unit_bundle_integrity(arg: UnitBundle) -> bool { let arg_1_is_correct = match arg.arg_1 { UnitEnum::var2 => true, _ => false, }; arg_1_is_correct && arg.arg_2 == 18_446_744_073_709_551_615u64 } } ================================================ FILE: e2e/sway/types/contracts/enum_inside_struct/Forc.toml ================================================ [project] authors = ["Fuel Labs "] entry = "main.sw" license = "Apache-2.0" name = "enum_inside_struct" ================================================ FILE: e2e/sway/types/contracts/enum_inside_struct/src/main.sw ================================================ contract; #[allow(dead_code)] enum Shaker { Cosmopolitan: u64, Mojito: u64, } struct Cocktail { the_thing_you_mix_in: Shaker, glass: u64, } abi TestContract { fn return_enum_inside_struct(a: u64) -> Cocktail; fn take_enum_inside_struct(c: Cocktail) -> u64; } impl TestContract for Contract { fn return_enum_inside_struct(a: u64) -> Cocktail { Cocktail { the_thing_you_mix_in: Shaker::Mojito(a), glass: 333, } } fn take_enum_inside_struct(c: Cocktail) -> u64 { c.glass } } ================================================ FILE: e2e/sway/types/contracts/evm_address/Forc.toml ================================================ [project] authors = ["Fuel Labs "] entry = "main.sw" license = "Apache-2.0" name = "evm_address" ================================================ FILE: e2e/sway/types/contracts/evm_address/src/main.sw ================================================ contract; use std::vm::evm::evm_address::EvmAddress; abi EvmTest { fn evm_address_as_input(evm_addr: EvmAddress) -> bool; fn evm_address_from_literal() -> EvmAddress; fn evm_address_from_argument(raw_address: b256) -> EvmAddress; } impl EvmTest for Contract { fn evm_address_as_input(evm_addr: EvmAddress) -> bool { let evm_addr2 = EvmAddress::from(0x1616060606060606060606060606060606060606060606060606060606060606); evm_addr == evm_addr2 } fn evm_address_from_literal() -> EvmAddress { EvmAddress::from(0x0606060606060606060606060606060606060606060606060606060606060606) } fn evm_address_from_argument(raw_address: b256) -> EvmAddress { EvmAddress::from(raw_address) } } ================================================ FILE: e2e/sway/types/contracts/generics/Forc.toml ================================================ [project] authors = ["Fuel Labs "] entry = "main.sw" license = "Apache-2.0" name = "generics" ================================================ FILE: e2e/sway/types/contracts/generics/src/main.sw ================================================ contract; use std::hash::*; #[allow(dead_code)] struct StructUnusedGeneric { field: u64, } #[allow(dead_code)] enum EnumUnusedGeneric { One: u64, } struct StructTwoUnusedGenericParams {} #[allow(dead_code)] enum EnumTwoUnusedGenericParams { One: (), } struct StructUsedAndUnusedGenericParams { field: K, } #[allow(dead_code)] enum EnumUsedAndUnusedGenericParams { One: str[3], Two: K, } struct SimpleGeneric { single_generic_param: T, } struct PassTheGenericOn { one: SimpleGeneric, } struct StructWArrayGeneric { a: [L; 2], } struct StructWTupleGeneric { a: (M, M), } #[allow(dead_code)] enum EnumWGeneric { A: u64, B: N, } #[allow(dead_code)] struct StructWTwoGenerics { a: T, b: U, } struct StructWArrWGenericStruct { a: [StructWTwoGenerics; 3], } #[allow(dead_code)] struct MegaExample { a: ([U; 2], T), b: Vec<([EnumWGeneric>>>; 1], u32)>, } abi MyContract { fn unused_generic_args( arg_1: StructUnusedGeneric, arg_2: EnumUnusedGeneric, ); fn two_unused_generic_args( arg_1: StructTwoUnusedGenericParams, arg_2: EnumTwoUnusedGenericParams, ); fn used_and_unused_generic_args( arg_1: StructUsedAndUnusedGenericParams, arg_2: EnumUsedAndUnusedGenericParams, ) -> (StructUsedAndUnusedGenericParams, EnumUsedAndUnusedGenericParams); fn struct_w_generic(arg1: SimpleGeneric) -> SimpleGeneric; fn struct_delegating_generic(arg1: PassTheGenericOn) -> PassTheGenericOn; fn struct_w_generic_in_array(arg1: StructWArrayGeneric) -> StructWArrayGeneric; fn struct_w_generic_in_tuple(arg1: StructWTupleGeneric) -> StructWTupleGeneric; fn enum_w_generic(arg1: EnumWGeneric) -> EnumWGeneric; fn complex_test(arg1: MegaExample); fn array_with_generic_struct( arg: StructWArrWGenericStruct, ) -> StructWArrWGenericStruct; } impl MyContract for Contract { fn unused_generic_args( _arg_1: StructUnusedGeneric, _arg_2: EnumUnusedGeneric, ) {} fn two_unused_generic_args( _arg_1: StructTwoUnusedGenericParams, _arg_2: EnumTwoUnusedGenericParams, ) {} fn used_and_unused_generic_args( arg_1: StructUsedAndUnusedGenericParams, arg_2: EnumUsedAndUnusedGenericParams, ) -> (StructUsedAndUnusedGenericParams, EnumUsedAndUnusedGenericParams) { assert_eq(arg_1.field, 10u8); if let EnumUsedAndUnusedGenericParams::Two(val) = arg_2 { assert_eq(val, 11u8); } else { require( false, "Expected the variant EnumUsedAndUnusedGenericParams::Two", ); } ( StructUsedAndUnusedGenericParams { field: 12u8 }, EnumUsedAndUnusedGenericParams::Two(13u8), ) } fn struct_w_generic(arg1: SimpleGeneric) -> SimpleGeneric { let expected = SimpleGeneric { single_generic_param: 123u64, }; assert(arg1.single_generic_param == expected.single_generic_param); expected } fn struct_delegating_generic(arg1: PassTheGenericOn) -> PassTheGenericOn { let expected = PassTheGenericOn { one: SimpleGeneric { single_generic_param: __to_str_array("abc"), }, }; assert( sha256(from_str_array(expected.one.single_generic_param)) == sha256(from_str_array(arg1.one.single_generic_param)), ); expected } fn struct_w_generic_in_array(arg1: StructWArrayGeneric) -> StructWArrayGeneric { let expected = StructWArrayGeneric { a: [1u32, 2u32], }; assert(expected.a[0] == arg1.a[0]); assert(expected.a[1] == arg1.a[1]); expected } fn struct_w_generic_in_tuple(arg1: StructWTupleGeneric) -> StructWTupleGeneric { let expected = StructWTupleGeneric { a: (1u32, 2u32), }; assert(expected.a.0 == arg1.a.0); assert(expected.a.1 == arg1.a.1); expected } fn enum_w_generic(arg1: EnumWGeneric) -> EnumWGeneric { match arg1 { EnumWGeneric::B(value) => { assert(value == 10u64); } _ => { assert(false) } } EnumWGeneric::B(10) } fn complex_test(_arg: MegaExample) {} fn array_with_generic_struct( arg: StructWArrWGenericStruct, ) -> StructWArrWGenericStruct { arg } } ================================================ FILE: e2e/sway/types/contracts/heap_type_in_enums/Forc.toml ================================================ [project] authors = ["Fuel Labs "] entry = "main.sw" license = "Apache-2.0" name = "heap_type_in_enums" [dependencies] ================================================ FILE: e2e/sway/types/contracts/heap_type_in_enums/src/main.sw ================================================ contract; use std::bytes::Bytes; use std::string::String; pub enum TestError { Something: [u8; 5], Else: u64, } pub struct Bimbam { something: Bytes, } abi MyContract { fn returns_bytes_result(return_ok: bool) -> Result; fn returns_vec_result(return_ok: bool) -> Result, TestError>; fn returns_string_result(return_ok: bool) -> Result; fn returns_str_result(return_ok: bool) -> Result; fn returns_bytes_option(return_some: bool) -> Option; fn returns_vec_option(return_some: bool) -> Option>; fn returns_string_option(return_some: bool) -> Option; fn returns_str_option(return_some: bool) -> Option; fn would_raise_a_memory_overflow() -> Result; fn returns_a_heap_type_too_deep() -> Result; } impl MyContract for Contract { fn returns_bytes_result(return_ok: bool) -> Result { return if return_ok { let mut b = Bytes::new(); b.push(1u8); b.push(1u8); b.push(1u8); b.push(1u8); Result::Ok(b) } else { Result::Err(TestError::Something([255u8, 255u8, 255u8, 255u8, 255u8])) } } fn returns_vec_result(return_ok: bool) -> Result, TestError> { return if return_ok { let mut v = Vec::new(); v.push(2); v.push(2); v.push(2); v.push(2); v.push(2); Result::Ok(v) } else { Result::Err(TestError::Else(7777)) } } fn returns_string_result(return_ok: bool) -> Result { return if return_ok { let s = String::from_ascii_str("Hello World"); Result::Ok(s) } else { Result::Err(TestError::Else(3333)) } } fn returns_str_result(return_ok: bool) -> Result { return if return_ok { Result::Ok("Hello World") } else { Result::Err(TestError::Else(3333)) } } fn returns_bytes_option(return_some: bool) -> Option { return if return_some { let mut b = Bytes::new(); b.push(1u8); b.push(1u8); b.push(1u8); b.push(1u8); Option::Some(b) } else { Option::None } } fn returns_vec_option(return_some: bool) -> Option> { return if return_some { let mut v = Vec::new(); v.push(2); v.push(2); v.push(2); v.push(2); v.push(2); Option::Some(v) } else { None } } fn returns_string_option(return_some: bool) -> Option { return if return_some { let s = String::from_ascii_str("Hello World"); Option::Some(s) } else { None } } fn returns_str_option(return_some: bool) -> Option { return if return_some { Option::Some("Hello World") } else { None } } fn would_raise_a_memory_overflow() -> Result { Result::Err(0x1111111111111111111111111111111111111111111111111111111111111111) } fn returns_a_heap_type_too_deep() -> Result { let mut b = Bytes::new(); b.push(2u8); b.push(2u8); b.push(2u8); Result::Ok(Bimbam { something: b }) } } ================================================ FILE: e2e/sway/types/contracts/heap_types/Forc.toml ================================================ [project] authors = ["Fuel Labs "] entry = "main.sw" license = "Apache-2.0" name = "heap_types" ================================================ FILE: e2e/sway/types/contracts/heap_types/src/main.sw ================================================ contract; use std::bytes::Bytes; use std::string::String; #[allow(dead_code)] struct StructGenerics { one: T, two: K, three: U, } #[allow(dead_code)] enum EnumGeneric { One: H, Two: I, } abi HeapTypesContract { fn nested_heap_types() -> EnumGeneric>, String>; } impl HeapTypesContract for Contract { fn nested_heap_types() -> EnumGeneric>, String> { let mut some_vec = Vec::new(); some_vec.push(2u8); some_vec.push(4u8); some_vec.push(8u8); let struct_generics = StructGenerics { one: Bytes::from(some_vec), two: String::from_ascii_str("fuel"), three: some_vec.as_raw_slice(), }; let mut enum_vec = Vec::new(); enum_vec.push(struct_generics); enum_vec.push(struct_generics); EnumGeneric::One(enum_vec) } } ================================================ FILE: e2e/sway/types/contracts/identity/Forc.toml ================================================ [project] authors = ["Fuel Labs "] entry = "main.sw" license = "Apache-2.0" name = "identity" ================================================ FILE: e2e/sway/types/contracts/identity/src/main.sw ================================================ contract; struct TestStruct { identity: Identity, } #[allow(dead_code)] enum TestEnum { EnumIdentity: Identity, OtherValue: bool, } const ADDR = 0xd58573593432a30a800f97ad32f877425c223a9e427ab557aab5d5bb89156db0; abi MyContract { fn get_identity_address() -> Identity; fn get_identity_contract_id() -> Identity; fn get_struct_with_identity() -> TestStruct; fn get_enum_with_identity() -> TestEnum; fn get_identity_tuple() -> (TestStruct, TestEnum); fn input_identity(i: Identity) -> bool; fn input_struct_with_identity(s: TestStruct) -> bool; fn input_enum_with_identity(s: TestEnum) -> bool; } impl MyContract for Contract { fn get_identity_address() -> Identity { Identity::Address(Address::from(ADDR)) } fn get_identity_contract_id() -> Identity { Identity::ContractId(ContractId::from(ADDR)) } fn get_struct_with_identity() -> TestStruct { TestStruct { identity: Identity::Address(Address::from(ADDR)), } } fn get_enum_with_identity() -> TestEnum { TestEnum::EnumIdentity(Identity::ContractId(ContractId::from(ADDR))) } fn get_identity_tuple() -> (TestStruct, TestEnum) { let s = TestStruct { identity: Identity::Address(Address::from(ADDR)), }; let e = TestEnum::EnumIdentity(Identity::ContractId(ContractId::from(ADDR))); (s, e) } fn input_identity(input: Identity) -> bool { if let Identity::Address(a) = input { return a == Address::from(ADDR); } false } fn input_struct_with_identity(input: TestStruct) -> bool { if let Identity::Address(a) = input.identity { return a == Address::from(ADDR); } false } fn input_enum_with_identity(input: TestEnum) -> bool { if let TestEnum::EnumIdentity(identity) = input { if let Identity::ContractId(c) = identity { return c == ContractId::from(ADDR); } } false } } ================================================ FILE: e2e/sway/types/contracts/native_types/Forc.toml ================================================ [project] authors = ["Fuel Labs "] entry = "main.sw" license = "Apache-2.0" name = "native_types" ================================================ FILE: e2e/sway/types/contracts/native_types/src/main.sw ================================================ contract; struct User { address: Address, weight: u64, } abi MyContract { fn wrapped_address(user: User) -> User; fn unwrapped_address(addr: Address) -> Address; } impl MyContract for Contract { fn wrapped_address(user: User) -> User { user } fn unwrapped_address(addr: Address) -> Address { addr } } ================================================ FILE: e2e/sway/types/contracts/nested_structs/Forc.toml ================================================ [project] authors = ["Fuel Labs "] entry = "main.sw" license = "Apache-2.0" name = "nested_structs" ================================================ FILE: e2e/sway/types/contracts/nested_structs/src/main.sw ================================================ contract; pub struct SomeStruct { field: u32, field_2: bool, } pub struct AllStruct { some_struct: SomeStruct, } pub struct CallData { memory_address: MemoryAddress, num_coins_to_forward: u64, asset_id_of_coins_to_forward: ContractId, amount_of_gas_to_forward: u64, } #[allow(dead_code)] struct MemoryAddress { contract_id: ContractId, function_selector: u64, function_data: u64, } abi MyContract { fn get_struct() -> AllStruct; #[payable] fn check_struct_integrity(arg: AllStruct) -> bool; #[payable] fn i_am_called_differently(_arg1: AllStruct, _arg2: MemoryAddress); fn nested_struct_with_reserved_keyword_substring(call_data: CallData) -> CallData; } impl MyContract for Contract { fn get_struct() -> AllStruct { AllStruct { some_struct: SomeStruct { field: 12345u32, field_2: true, }, } } #[payable] fn check_struct_integrity(arg: AllStruct) -> bool { arg.some_struct.field == 12345u32 && arg.some_struct.field_2 == true } #[payable] fn i_am_called_differently(_arg1: AllStruct, _arg2: MemoryAddress) {} fn nested_struct_with_reserved_keyword_substring(call_data: CallData) -> CallData { call_data } } ================================================ FILE: e2e/sway/types/contracts/options/Forc.toml ================================================ [project] authors = ["Fuel Labs "] entry = "main.sw" license = "Apache-2.0" name = "options" ================================================ FILE: e2e/sway/types/contracts/options/src/main.sw ================================================ contract; struct TestStruct { option: Option
, } #[allow(dead_code)] enum TestEnum { EnumOption: Option
, OtherValue: bool, } const ADDR = 0xd58573593432a30a800f97ad32f877425c223a9e427ab557aab5d5bb89156db0; abi MyContract { fn get_some_u64() -> Option; fn get_some_address() -> Option
; fn get_some_struct() -> Option; fn get_some_enum() -> Option; fn get_some_tuple() -> Option<(TestStruct, TestEnum)>; fn get_none() -> Option
; fn input_primitive(s: Option) -> bool; fn input_struct(s: Option) -> bool; fn input_enum(e: Option) -> bool; fn input_none(none: Option
) -> bool; } impl MyContract for Contract { fn get_some_u64() -> Option { Option::Some(10) } fn get_some_address() -> Option
{ Option::Some(Address::from(ADDR)) } fn get_some_struct() -> Option { Option::Some(TestStruct { option: Option::Some(Address::from(ADDR)), }) } fn get_some_enum() -> Option { Option::Some(TestEnum::EnumOption(Option::Some(Address::from(ADDR)))) } fn get_some_tuple() -> Option<(TestStruct, TestEnum)> { let s = TestStruct { option: Option::Some(Address::from(ADDR)), }; let e = TestEnum::EnumOption(Option::Some(Address::from(ADDR))); Option::Some((s, e)) } fn get_none() -> Option
{ Option::None } fn input_primitive(input: Option) -> bool { if let Option::Some(u) = input { return u == 36; } false } fn input_struct(input: Option) -> bool { if let Option::Some(s) = input { if let Option::Some(a) = s.option { return a == Address::from(ADDR); } } false } fn input_enum(input: Option) -> bool { if let Option::Some(test_enum) = input { if let TestEnum::EnumOption(option) = test_enum { if let Option::Some(a) = option { return a == Address::from(ADDR); } } } false } fn input_none(none: Option
) -> bool { if let Option::None = none { return true; } false } } ================================================ FILE: e2e/sway/types/contracts/raw_slice/Forc.toml ================================================ [project] authors = ["Fuel Labs "] entry = "main.sw" license = "Apache-2.0" name = "raw_slice" ================================================ FILE: e2e/sway/types/contracts/raw_slice/src/main.sw ================================================ contract; #[allow(dead_code)] enum SomeEnum { First: bool, Second: T, } struct Wrapper { inner: T, inner_enum: SomeEnum, } abi RawSliceContract { fn return_raw_slice(length: u8) -> raw_slice; fn accept_raw_slice(slice: raw_slice); fn accept_nested_raw_slice(wrapper: Wrapper>); } fn validate_raw_slice(input: raw_slice) { let vec: Vec = Vec::from(input); require(vec.len() == 3, "raw slice len is not 3"); require( vec .get(2) .unwrap() == 42, "expected 3rd slice entry to be 42", ); require( vec .get(1) .unwrap() == 41, "expected 2nd slice entry to be 41", ); require( vec .get(0) .unwrap() == 40, "expected 1st slice entry to be 40", ); } fn validate_vec(vec: Vec) { require(vec.len() == 2, "vec should have two elements"); validate_raw_slice(vec.get(0).unwrap()); validate_raw_slice(vec.get(1).unwrap()); } impl RawSliceContract for Contract { fn return_raw_slice(length: u8) -> raw_slice { let mut vec = Vec::new(); let mut counter = 0u8; while counter < length { vec.push(counter); counter = counter + 1; } vec.as_raw_slice() } fn accept_raw_slice(slice: raw_slice) { validate_raw_slice(slice); } fn accept_nested_raw_slice(wrapper: Wrapper>) { if let SomeEnum::Second(enum_raw_slice) = wrapper.inner_enum { validate_raw_slice(enum_raw_slice); } else { require(false, "enum was not of variant Second"); } validate_vec(wrapper.inner); } } ================================================ FILE: e2e/sway/types/contracts/results/Forc.toml ================================================ [project] authors = ["Fuel Labs "] entry = "main.sw" license = "Apache-2.0" name = "results" ================================================ FILE: e2e/sway/types/contracts/results/src/main.sw ================================================ contract; struct TestStruct { option: Option
, } enum TestEnum { EnumOption: Option
, } pub enum TestError { NoAddress: str[5], OtherError: (), } const ADDR = 0xd58573593432a30a800f97ad32f877425c223a9e427ab557aab5d5bb89156db0; abi MyContract { fn get_ok_u64() -> Result; fn get_ok_address() -> Result; fn get_ok_struct() -> Result; fn get_ok_enum() -> Result; fn get_ok_tuple() -> Result<(TestStruct, TestEnum), TestError>; fn get_error() -> Result; fn input_ok(ok_address: Result) -> bool; fn input_error(test_error: Result) -> bool; } impl MyContract for Contract { fn get_ok_u64() -> Result { Result::Ok(10) } fn get_ok_address() -> Result { Result::Ok(Address::from(ADDR)) } fn get_ok_struct() -> Result { Result::Ok(TestStruct { option: Option::Some(Address::from(ADDR)), }) } fn get_ok_enum() -> Result { Result::Ok(TestEnum::EnumOption(Option::Some(Address::from(ADDR)))) } fn get_ok_tuple() -> Result<(TestStruct, TestEnum), TestError> { let s = TestStruct { option: Option::Some(Address::from(ADDR)), }; let e = TestEnum::EnumOption(Option::Some(Address::from(ADDR))); Result::Ok((s, e)) } fn get_error() -> Result { Result::Err(TestError::NoAddress(__to_str_array("error"))) } fn input_ok(ok_address: Result) -> bool { if let Result::Ok(a) = ok_address { return a == Address::from(ADDR); } false } fn input_error(test_result: Result) -> bool { if let Result::Err(test_error) = test_result { if let TestError::NoAddress(_err_msg) = test_error { return true; } } false } } ================================================ FILE: e2e/sway/types/contracts/std_lib_string/Forc.toml ================================================ [project] authors = ["Fuel Labs "] entry = "main.sw" license = "Apache-2.0" name = "std_lib_string" ================================================ FILE: e2e/sway/types/contracts/std_lib_string/src/main.sw ================================================ contract; use std::string::String; use std::assert::assert_eq; use std::bytes::Bytes; abi MyContract { fn return_dynamic_string() -> String; fn accepts_dynamic_string(s: String); fn echoes_dynamic_string(s: String) -> String; } impl MyContract for Contract { fn return_dynamic_string() -> String { String::from_ascii_str("Hello World") } fn accepts_dynamic_string(string: String) { assert_eq(string, String::from_ascii_str("Hello World")); } fn echoes_dynamic_string(string: String) -> String { string } } ================================================ FILE: e2e/sway/types/contracts/str_in_array/Forc.toml ================================================ [project] authors = ["Fuel Labs "] entry = "main.sw" license = "Apache-2.0" name = "str_in_array" ================================================ FILE: e2e/sway/types/contracts/str_in_array/src/main.sw ================================================ contract; abi TestContract { fn take_array_string_shuffle(a: [str[3]; 3]) -> [str[3]; 3]; fn take_array_string_return_single(a: [str[3]; 3]) -> [str[3]; 1]; fn take_array_string_return_single_element(a: [str[3]; 3]) -> str[3]; } impl TestContract for Contract { fn take_array_string_shuffle(a: [str[3]; 3]) -> [str[3]; 3] { [a[2], a[0], a[1]] } fn take_array_string_return_single(a: [str[3]; 3]) -> [str[3]; 1] { [a[0]] } fn take_array_string_return_single_element(a: [str[3]; 3]) -> str[3] { a[1] } } ================================================ FILE: e2e/sway/types/contracts/string_slice/Forc.toml ================================================ [project] authors = ["Fuel Labs "] entry = "main.sw" license = "Apache-2.0" name = "string_slice" ================================================ FILE: e2e/sway/types/contracts/string_slice/src/main.sw ================================================ contract; abi RawSliceContract { fn handles_str(input: str) -> str; } impl RawSliceContract for Contract { fn handles_str(input: str) -> str { assert_eq(input, "contract-input"); "contract-return" } } ================================================ FILE: e2e/sway/types/contracts/tuples/Forc.toml ================================================ [project] authors = ["Fuel Labs "] entry = "main.sw" license = "Apache-2.0" name = "tuples" ================================================ FILE: e2e/sway/types/contracts/tuples/src/main.sw ================================================ contract; use std::hash::*; struct Person { name: str[4], } #[allow(dead_code)] enum State { A: (), B: (), C: (), } abi MyContract { fn returns_tuple(input: (u64, u64)) -> (u64, u64); fn returns_struct_in_tuple(input: (u64, Person)) -> (u64, Person); fn returns_enum_in_tuple(input: (u64, State)) -> (u64, State); fn single_element_tuple(input: (u64, )) -> (u64, ); fn tuple_with_b256(p: (b256, u8)) -> (b256, u8); } impl MyContract for Contract { fn returns_tuple(input: (u64, u64)) -> (u64, u64) { let expected = (1u64, 2u64); assert(expected.0 == input.0); assert(expected.1 == input.1); expected } fn returns_struct_in_tuple(input: (u64, Person)) -> (u64, Person) { let expected = (42, Person { name: __to_str_array("Jane"), }); assert(input.0 == expected.0); assert(sha256(from_str_array(input.1.name)) == sha256(from_str_array(expected.1.name))); expected } fn returns_enum_in_tuple(input: (u64, State)) -> (u64, State) { let expected = (42, State::A); assert(input.0 == expected.0); match input.1 { State::A => {}, _ => { assert(false) } }; expected } fn single_element_tuple(input: (u64, )) -> (u64, ) { let expected = (123u64, ); assert(expected.0 == input.0); expected } fn tuple_with_b256(p: (b256, u8)) -> (b256, u8) { let expected = (b256::zero(), 10u8); assert(p.0 == expected.0); assert(p.1 == expected.1); expected } } ================================================ FILE: e2e/sway/types/contracts/two_structs/Forc.toml ================================================ [project] authors = ["Fuel Labs "] entry = "main.sw" license = "Apache-2.0" name = "two_structs" ================================================ FILE: e2e/sway/types/contracts/two_structs/src/main.sw ================================================ contract; pub struct StructOne { foo: u64, } pub struct StructTwo { bar: u64, } abi MyTest { fn something(input: StructOne) -> u64; fn something_else(input: StructTwo) -> u64; } impl MyTest for Contract { fn something(input: StructOne) -> u64 { let v = input.foo; v + 1 } fn something_else(input: StructTwo) -> u64 { let v = input.bar; v - 1 } } ================================================ FILE: e2e/sway/types/contracts/type_inside_enum/Forc.toml ================================================ [project] authors = ["Fuel Labs "] entry = "main.sw" license = "Apache-2.0" name = "type_inside_enum" ================================================ FILE: e2e/sway/types/contracts/type_inside_enum/src/main.sw ================================================ contract; #[allow(dead_code)] enum SomeEnum { SomeStr: str[4], SomeArr: [u64; 4], } #[allow(dead_code)] enum Shaker { Cosmopolitan: Recipe, Mojito: u32, } #[allow(dead_code)] struct Recipe { ice: u8, sugar: u16, } #[allow(dead_code)] enum EnumLevel1 { Num: u32, check: bool, } #[allow(dead_code)] enum EnumLevel2 { El1: EnumLevel1, Check: bool, } #[allow(dead_code)] enum EnumLevel3 { El2: EnumLevel2, Num: u8, } abi MyContract { fn str_inside_enum(my_enum: SomeEnum) -> SomeEnum; fn arr_inside_enum(my_enum: SomeEnum) -> SomeEnum; fn return_struct_inside_enum(c: u16) -> Shaker; fn take_struct_inside_enum(s: Shaker) -> u64; fn get_nested_enum() -> EnumLevel3; fn check_nested_enum_integrity(e: EnumLevel3) -> bool; } impl MyContract for Contract { fn str_inside_enum(my_enum: SomeEnum) -> SomeEnum { my_enum } fn arr_inside_enum(my_enum: SomeEnum) -> SomeEnum { my_enum } fn return_struct_inside_enum(c: u16) -> Shaker { Shaker::Cosmopolitan(Recipe { ice: 22u8, sugar: c, }) } fn take_struct_inside_enum(_s: Shaker) -> u64 { 8888 } fn get_nested_enum() -> EnumLevel3 { EnumLevel3::El2(EnumLevel2::El1(EnumLevel1::Num(42u32))) } fn check_nested_enum_integrity(e: EnumLevel3) -> bool { let arg_is_correct = match e { EnumLevel3::El2(EnumLevel2::El1(EnumLevel1::Num(value))) => { value == 42u32 }, _ => false, }; arg_is_correct } } ================================================ FILE: e2e/sway/types/contracts/u128/Forc.toml ================================================ [project] authors = ["Fuel Labs "] entry = "main.sw" license = "Apache-2.0" name = "u128" ================================================ FILE: e2e/sway/types/contracts/u128/src/main.sw ================================================ contract; use std::u128::U128; #[allow(dead_code)] enum SomeEnum { A: bool, B: T, } abi MyContract { fn u128_sum_and_ret(some_u128: U128) -> U128; fn u128_in_enum_input(some_enum: SomeEnum); fn u128_in_enum_output() -> SomeEnum; } impl MyContract for Contract { fn u128_sum_and_ret(arg: U128) -> U128 { arg + U128::from((3, 4)) } fn u128_in_enum_input(some_enum: SomeEnum) { if let SomeEnum::B(some_u128) = some_enum { let expected_u128 = U128::from((3, 3)); require( some_u128 == expected_u128, "given u128 didn't match the expected u128", ); } else { require(false, "enum was not of variant B: u128"); } } fn u128_in_enum_output() -> SomeEnum { SomeEnum::B(U128::from((4, 4))) } } ================================================ FILE: e2e/sway/types/contracts/u256/Forc.toml ================================================ [project] authors = ["Fuel Labs "] entry = "main.sw" license = "Apache-2.0" name = "u256" ================================================ FILE: e2e/sway/types/contracts/u256/src/main.sw ================================================ contract; #[allow(dead_code)] enum SomeEnum { A: bool, B: T, } abi MyContract { fn u256_sum_and_ret(some_u256: u256) -> u256; fn u256_in_enum_output() -> SomeEnum; fn u256_in_enum_input(some_enum: SomeEnum); } impl MyContract for Contract { fn u256_sum_and_ret(arg: u256) -> u256 { arg + 0x0000000000000003000000000000000400000000000000050000000000000006u256 } fn u256_in_enum_output() -> SomeEnum { SomeEnum::B(0x0000000000000001000000000000000200000000000000030000000000000004u256) } fn u256_in_enum_input(some_enum: SomeEnum) { if let SomeEnum::B(some_u256) = some_enum { require( some_u256 == 0x0000000000000002000000000000000300000000000000040000000000000005u256, "given u256 didn't match the expected u256", ); } else { require(false, "enum was not of variant B: u256"); } } } ================================================ FILE: e2e/sway/types/contracts/vector_output/Forc.toml ================================================ [project] authors = ["Fuel Labs "] entry = "main.sw" license = "Apache-2.0" name = "vector_output" ================================================ FILE: e2e/sway/types/contracts/vector_output/src/main.sw ================================================ contract; struct Bimbam { bim: u64, bam: u32, } enum Pasta { Rigatoni: u64, Spaghetti: bool, Tortelini: Bimbam, } struct ZimZam { vec_component: Vec, } abi VectorsOutputContract { fn vec_inside_type() -> ZimZam; fn array_in_vec() -> Vec<[u64; 4]>; fn bool_in_vec() -> Vec; fn enum_in_vec() -> Vec; fn str_in_vec() -> Vec; fn struct_in_vec() -> Vec; fn tuple_in_vec() -> Vec<(u64, u32)>; fn u16_in_vec(len: u16) -> Vec; fn u32_in_vec(len: u32) -> Vec; fn u64_in_vec(len: u64) -> Vec; fn u8_in_vec(len: u8) -> Vec; fn b256_in_vec(len: u8) -> Vec; } impl VectorsOutputContract for Contract { fn vec_inside_type() -> ZimZam { let mut b = Vec::new(); b.push(255); b.push(255); b.push(255); b.push(255); ZimZam { vec_component: b, } } fn array_in_vec() -> Vec<[u64; 4]> { let mut vec: Vec<[u64; 4]> = Vec::new(); vec.push([1, 1, 1, 1]); vec.push([2, 2, 2, 2]); vec.push([3, 3, 3, 3]); vec.push([4, 4, 4, 4]); vec } fn bool_in_vec() -> Vec { let mut vec: Vec = Vec::new(); vec.push(true); vec.push(false); vec.push(true); vec.push(false); vec } fn enum_in_vec() -> Vec { let mut vec: Vec = Vec::new(); vec.push(Pasta::Tortelini(Bimbam { bim: 1111, bam: 2222_u32, })); vec.push(Pasta::Rigatoni(1987)); vec.push(Pasta::Spaghetti(true)); vec } fn str_in_vec() -> Vec { let mut vec: Vec = Vec::new(); vec.push(__to_str_array("hell")); vec.push(__to_str_array("ello")); vec.push(__to_str_array("lloh")); vec } fn struct_in_vec() -> Vec { let mut vec: Vec = Vec::new(); let a = Bimbam { bim: 1111, bam: 2222_u32, }; vec.push(a); let b = Bimbam { bim: 3333, bam: 4444_u32, }; vec.push(b); let c = Bimbam { bim: 5555, bam: 6666_u32, }; vec.push(c); vec } fn tuple_in_vec() -> Vec<(u64, u32)> { let mut vec: Vec<(u64, u32)> = Vec::new(); vec.push((1111, 2222_u32)); vec.push((3333, 4444_u32)); vec.push((5555, 6666_u32)); vec } fn u8_in_vec(len: u8) -> Vec { let mut vec: Vec = Vec::new(); let mut i: u8 = 0; while i < len { vec.push(i); i += 1u8; } vec } fn u16_in_vec(len: u16) -> Vec { let mut vec: Vec = Vec::new(); let mut i: u16 = 0; while i < len { vec.push(i); i += 1_u16; } vec } fn u32_in_vec(len: u32) -> Vec { let mut vec: Vec = Vec::new(); let mut i: u32 = 0; while i < len { vec.push(i); i += 1_u32; } vec } fn u64_in_vec(len: u64) -> Vec { let mut vec: Vec = Vec::new(); let mut i: u64 = 0; while i < len { vec.push(i); i += 1_u64; } vec } fn b256_in_vec(len: u8) -> Vec { let mut vec: Vec = Vec::new(); let mut i = 0u8; while i < len { vec.push(0x0202020202020202020202020202020202020202020202020202020202020202); i += 1; } vec } } ================================================ FILE: e2e/sway/types/contracts/vectors/Forc.toml ================================================ [project] authors = ["Fuel Labs "] entry = "main.sw" license = "Apache-2.0" name = "vectors" ================================================ FILE: e2e/sway/types/contracts/vectors/src/data_structures.sw ================================================ library; pub struct SomeStruct { pub a: T, } pub enum SomeEnum { a: T, } ================================================ FILE: e2e/sway/types/contracts/vectors/src/eq_impls.sw ================================================ library; use ::data_structures::{SomeEnum, SomeStruct}; impl PartialEq for SomeEnum { fn eq(self, other: Self) -> bool { match self { SomeEnum::a(val) => { match other { SomeEnum::a(other_val) => { val == other_val } } } } } } impl PartialEq for SomeStruct { fn eq(self, other: Self) -> bool { self.a == other.a } } impl PartialEq for [Vec; 2] { fn eq(self, other: Self) -> bool { let mut i = 0; while i < 2 { if self[i] != other[i] { return false; } i += 1; } true } } impl PartialEq for [u64; 2] { fn eq(self, other: Self) -> bool { let mut i = 0; while i < 2 { if self[i] != other[i] { return false; } i += 1; } true } } impl PartialEq for SomeEnum> { fn eq(self, other: Self) -> bool { match self { SomeEnum::a(val) => { match other { SomeEnum::a(other_val) => { val == other_val } } } } } } impl PartialEq for SomeStruct> { fn eq(self, other: Self) -> bool { self.a == other.a } } impl PartialEq for SomeStruct>> { fn eq(self, other: Self) -> bool { self.a == other.a } } ================================================ FILE: e2e/sway/types/contracts/vectors/src/main.sw ================================================ contract; pub mod data_structures; mod eq_impls; mod utils; use eq_impls::*; use utils::*; use data_structures::*; abi MyContract { fn u32_vec(arg: Vec); fn struct_in_vec(arg: Vec>); fn vec_in_struct(arg: SomeStruct>); fn array_in_vec(arg: Vec<[u64; 2]>); fn vec_in_array(arg: [Vec; 2]); fn enum_in_vec(arg: Vec>); fn vec_in_enum(arg: SomeEnum>); fn tuple_in_vec(arg: Vec<(u32, u32)>); fn vec_in_tuple(arg: (Vec, Vec)); fn vec_in_vec(arg: Vec>); fn vec_in_a_vec_in_a_struct_in_a_vec(arg: Vec>>>); } impl MyContract for Contract { fn u32_vec(arg: Vec) { let expected = vec_from([0, 1, 2]); assert(arg == expected); } fn vec_in_vec(arg: Vec>) { let mut expected = Vec::new(); expected.push(vec_from([0, 1, 2])); expected.push(vec_from([0, 1, 2])); assert_eq(arg, expected); } fn struct_in_vec(arg: Vec>) { let mut expected = Vec::new(); expected.push(SomeStruct { a: 0u32 }); expected.push(SomeStruct { a: 1u32 }); assert_eq(arg, expected); } fn vec_in_struct(arg: SomeStruct>) { let expected = SomeStruct { a: vec_from([0, 1, 2]), }; assert_eq(arg, expected); } fn array_in_vec(arg: Vec<[u64; 2]>) { let mut expected = Vec::new(); expected.push([0, 1]); expected.push([0, 1]); assert_eq(arg, expected); } fn vec_in_array(arg: [Vec; 2]) { let expected = [vec_from([0, 1, 2]), vec_from([0, 1, 2])]; assert_eq(arg, expected); } fn vec_in_enum(arg: SomeEnum>) { let vec = vec_from([0, 1, 2]); let expected = SomeEnum::a(vec); assert_eq(arg, expected); } fn enum_in_vec(arg: Vec>) { let mut expected = Vec::new(); expected.push(SomeEnum::a(0u32)); expected.push(SomeEnum::a(1u32)); assert_eq(arg, expected); } fn tuple_in_vec(arg: Vec<(u32, u32)>) { let mut expected = Vec::new(); expected.push((0u32, 0u32)); expected.push((1u32, 1u32)); assert_eq(arg, expected); } fn vec_in_tuple(arg: (Vec, Vec)) { let expected = (vec_from([0, 1, 2]), vec_from([0, 1, 2])); assert_eq(arg, expected); } fn vec_in_a_vec_in_a_struct_in_a_vec(arg: Vec>>>) { let mut expected = Vec::new(); let mut inner_vec_1 = Vec::new(); let inner_inner_vec_1 = vec_from([0, 1, 2]); inner_vec_1.push(inner_inner_vec_1); let inner_inner_vec_2 = vec_from([3, 4, 5]); inner_vec_1.push(inner_inner_vec_2); expected.push(SomeStruct { a: inner_vec_1 }); let mut inner_vec_2 = Vec::new(); let inner_inner_vec_3 = vec_from([6, 7, 8]); inner_vec_2.push(inner_inner_vec_3); let inner_inner_vec_4 = vec_from([9, 10, 11]); inner_vec_2.push(inner_inner_vec_4); expected.push(SomeStruct { a: inner_vec_2 }); assert_eq(arg, expected); } } ================================================ FILE: e2e/sway/types/contracts/vectors/src/utils.sw ================================================ library; pub fn vec_from(vals: [u32; 3]) -> Vec { let mut vec = Vec::new(); vec.push(vals[0]); vec.push(vals[1]); vec.push(vals[2]); vec } ================================================ FILE: e2e/sway/types/predicates/address/Forc.toml ================================================ [project] authors = ["Fuel Labs "] entry = "main.sw" license = "Apache-2.0" name = "address" ================================================ FILE: e2e/sway/types/predicates/address/src/main.sw ================================================ predicate; fn main(input: Address) -> bool { let expected_addr = Address::from(0xef86afa9696cf0dc6385e2c407a6e159a1103cefb7e2ae0636fb33d3cb2a9e4a); input == expected_addr } ================================================ FILE: e2e/sway/types/predicates/enums/Forc.toml ================================================ [project] authors = ["Fuel Labs "] entry = "main.sw" license = "Apache-2.0" name = "enums" ================================================ FILE: e2e/sway/types/predicates/enums/src/main.sw ================================================ predicate; #[allow(dead_code)] enum TestEnum { A: u64, B: bool, } #[allow(dead_code)] enum AnotherTestEnum { A: u64, B: u64, } fn main(test_enum: TestEnum, test_enum2: AnotherTestEnum) -> bool { if let TestEnum::A(a) = test_enum { if let AnotherTestEnum::B(b) = test_enum2 { return a == b; } } false } ================================================ FILE: e2e/sway/types/predicates/predicate_b256/Forc.toml ================================================ [project] authors = ["Fuel Labs "] entry = "main.sw" license = "Apache-2.0" name = "predicate_b256" ================================================ FILE: e2e/sway/types/predicates/predicate_b256/src/main.sw ================================================ predicate; fn main(foo: b256) -> bool { foo == 0x0101010101010101010101010101010101010101010101010101010101010101 } ================================================ FILE: e2e/sway/types/predicates/predicate_bytes/Forc.toml ================================================ [project] authors = ["Fuel Labs "] entry = "main.sw" license = "Apache-2.0" name = "predicate_bytes" ================================================ FILE: e2e/sway/types/predicates/predicate_bytes/src/main.sw ================================================ predicate; use std::bytes::Bytes; #[allow(dead_code)] enum SomeEnum { First: bool, Second: T, } struct Wrapper { inner: T, inner_enum: SomeEnum, } fn expected_bytes() -> Bytes { let mut bytes = Bytes::new(); bytes.push(40u8); bytes.push(41u8); bytes.push(42u8); bytes } fn valid_bytes(bytes: Bytes) -> bool { bytes == expected_bytes() } fn valid_vec(arg: Vec) -> bool { if arg.len() != 2 { return false; } valid_bytes(arg.get(0).unwrap()) && valid_bytes(arg.get(1).unwrap()) } fn main(wrapper: Wrapper>) -> bool { if let SomeEnum::Second(enum_bytes) = wrapper.inner_enum { valid_bytes(enum_bytes) && valid_vec(wrapper.inner) } else { false } } ================================================ FILE: e2e/sway/types/predicates/predicate_bytes_hash/Forc.toml ================================================ [project] authors = ["Fuel Labs "] entry = "main.sw" license = "Apache-2.0" name = "predicate_bytes_hash" ================================================ FILE: e2e/sway/types/predicates/predicate_bytes_hash/src/main.sw ================================================ predicate; use std::bytes::Bytes; use std::hash::{Hash, sha256}; fn main(bytes: Bytes, hash: b256) -> bool { sha256(bytes) == hash } ================================================ FILE: e2e/sway/types/predicates/predicate_generics/Forc.toml ================================================ [project] authors = ["Fuel Labs "] entry = "main.sw" license = "Apache-2.0" name = "predicate_generics" ================================================ FILE: e2e/sway/types/predicates/predicate_generics/src/main.sw ================================================ predicate; struct GenericStruct { value: U, } #[allow(dead_code)] enum GenericEnum { Generic: GenericStruct, AnotherGeneric: V, } fn main( generic_struct: GenericStruct, generic_enum: GenericEnum, ) -> bool { if let GenericEnum::Generic(other_struct) = generic_enum { return other_struct.value == generic_struct.value.as_u16(); } false } ================================================ FILE: e2e/sway/types/predicates/predicate_raw_slice/Forc.toml ================================================ [project] authors = ["Fuel Labs "] entry = "main.sw" license = "Apache-2.0" name = "predicate_raw_slice" ================================================ FILE: e2e/sway/types/predicates/predicate_raw_slice/src/main.sw ================================================ predicate; #[allow(dead_code)] enum SomeEnum { First: bool, Second: T, } struct Wrapper { inner: T, inner_enum: SomeEnum, } fn valid_raw_slice(slice: raw_slice) -> bool { let vec: Vec = Vec::from(slice); vec.len() == 3 && vec.get(0).unwrap() == 40 && vec.get(1).unwrap() == 41 && vec.get(2).unwrap() == 42 } fn valid_vec(vec: Vec) -> bool { vec.len() == 2 && valid_raw_slice(vec.get(0).unwrap()) && valid_raw_slice(vec.get(1).unwrap()) } fn main(wrapper: Wrapper>) -> bool { if let SomeEnum::Second(enum_raw_slice) = wrapper.inner_enum { valid_raw_slice(enum_raw_slice) && valid_vec(wrapper.inner) } else { false } } ================================================ FILE: e2e/sway/types/predicates/predicate_std_lib_string/Forc.toml ================================================ [project] authors = ["Fuel Labs "] entry = "main.sw" license = "Apache-2.0" name = "predicate_std_lib_string" ================================================ FILE: e2e/sway/types/predicates/predicate_std_lib_string/src/main.sw ================================================ predicate; use std::string::String; fn main(_arg_0: u64, _arg_1: u64, string: String) -> bool { string == String::from_ascii_str("Hello World") } ================================================ FILE: e2e/sway/types/predicates/predicate_string_slice/Forc.toml ================================================ [project] authors = ["Fuel Labs "] entry = "main.sw" license = "Apache-2.0" name = "predicate_string_slice" ================================================ FILE: e2e/sway/types/predicates/predicate_string_slice/src/main.sw ================================================ predicate; fn main(input: str) -> bool { input == "predicate-input" } ================================================ FILE: e2e/sway/types/predicates/predicate_tuples/Forc.toml ================================================ [project] authors = ["Fuel Labs "] entry = "main.sw" license = "Apache-2.0" name = "predicate_tuples" ================================================ FILE: e2e/sway/types/predicates/predicate_tuples/src/main.sw ================================================ predicate; struct TestStruct { value: u32, } #[allow(dead_code)] enum TestEnum { Value: u64, OtherValue: u32, } fn main(input_tuple: (u64, TestStruct, TestEnum), number: u64) -> bool { let (u64_number, test_struct, test_enum) = input_tuple; if let TestEnum::Value(enum_value) = test_enum { return u64_number == 16 && test_struct.value == 32u32 && enum_value == 64 && number == 128; } false } ================================================ FILE: e2e/sway/types/predicates/predicate_u128/Forc.toml ================================================ [project] authors = ["Fuel Labs "] entry = "main.sw" license = "Apache-2.0" name = "predicate_u128" ================================================ FILE: e2e/sway/types/predicates/predicate_u128/src/main.sw ================================================ predicate; use std::u128::U128; fn main(arg: U128) -> bool { arg == U128::from((8, 2)) } ================================================ FILE: e2e/sway/types/predicates/predicate_u256/Forc.toml ================================================ [project] authors = ["Fuel Labs "] entry = "main.sw" license = "Apache-2.0" name = "predicate_u256" ================================================ FILE: e2e/sway/types/predicates/predicate_u256/src/main.sw ================================================ predicate; fn main(arg: u256) -> bool { arg == 0x000000000000000a000000000000000b000000000000000c000000000000000du256 } ================================================ FILE: e2e/sway/types/predicates/predicate_vector/Forc.toml ================================================ [project] authors = ["Fuel Labs "] entry = "main.sw" license = "Apache-2.0" name = "predicate_vector" ================================================ FILE: e2e/sway/types/predicates/predicate_vector/src/main.sw ================================================ predicate; fn main(a: u32, b: u64, c: Vec) -> bool { let number: u64 = c.get(2).unwrap(); number == (b + a.as_u64()) } ================================================ FILE: e2e/sway/types/predicates/predicate_vectors/Forc.toml ================================================ [project] authors = ["Fuel Labs "] entry = "main.sw" license = "Apache-2.0" name = "predicate_vectors" ================================================ FILE: e2e/sway/types/predicates/predicate_vectors/src/main.sw ================================================ predicate; pub struct SomeStruct { a: T, } pub enum SomeEnum { A: T, B: bool, } fn main( u32_vec: Vec, vec_in_vec: Vec>, struct_in_vec: Vec>, vec_in_struct: SomeStruct>, array_in_vec: Vec<[u64; 2]>, vec_in_array: [Vec; 2], vec_in_enum: SomeEnum>, enum_in_vec: Vec>, b256_in_vec: Vec, tuple_in_vec: Vec<(u32, u32)>, vec_in_tuple: (Vec, Vec), vec_in_a_vec_in_a_struct_in_a_vec: Vec>>>, ) -> bool { let mut result = true; result = result && (u32_vec.get(1).unwrap() == 4u32); result = result && (vec_in_vec.get(0).unwrap().get(1).unwrap() == 2u32); result = result && (struct_in_vec.get(0).unwrap().a == 8u32); result = result && (vec_in_struct.a.get(1).unwrap() == 16u32); let array: [u64; 2] = array_in_vec.get(1).unwrap(); result = result && (array[0] == 32u64); result = result && (vec_in_array[0].get(1).unwrap() == 64u32); if let SomeEnum::A(some_vec) = vec_in_enum { result = result && (some_vec.get(2).unwrap() == 128u32); } else { result = false; } let enum_a = enum_in_vec.get(1).unwrap(); if let SomeEnum::A(a) = enum_a { result = result && (a == 16u32) } else { result = false; } result = result && (b256_in_vec .get(1) .unwrap() == 0x0202020202020202020202020202020202020202020202020202020202020202); result = result && (tuple_in_vec.get(1).unwrap().0 == 128u32); let (tuple_a, _) = vec_in_tuple; result = result && (tuple_a.get(1).unwrap() == 64u32); result = result && (vec_in_a_vec_in_a_struct_in_a_vec .get(1) .unwrap() .a .get(1) .unwrap() .get(1) .unwrap() == 32u32); result } ================================================ FILE: e2e/sway/types/predicates/structs/Forc.toml ================================================ [project] authors = ["Fuel Labs "] entry = "main.sw" license = "Apache-2.0" name = "structs" ================================================ FILE: e2e/sway/types/predicates/structs/src/main.sw ================================================ predicate; struct TestStruct { value: u64, } struct AnotherTestStruct { value: u64, number: u64, } fn main(test_struct: TestStruct, test_struct2: AnotherTestStruct) -> bool { test_struct.value == (test_struct2.value + test_struct2.number) } ================================================ FILE: e2e/sway/types/predicates/u64/Forc.toml ================================================ [project] authors = ["Fuel Labs "] entry = "main.sw" license = "Apache-2.0" name = "u64" ================================================ FILE: e2e/sway/types/predicates/u64/src/main.sw ================================================ predicate; fn main(a: u64) -> bool { a == 32768 } ================================================ FILE: e2e/sway/types/scripts/options_results/Forc.toml ================================================ [project] authors = ["Fuel Labs "] entry = "main.sw" license = "Apache-2.0" name = "options_results" ================================================ FILE: e2e/sway/types/scripts/options_results/src/main.sw ================================================ script; pub enum TestError { ZimZam: str[5], } fn main(bim: Option, _bam: Option) -> Result, TestError> { if let Option::Some(42) = bim { Result::Ok(Option::Some(true)) } else if let Option::Some(_) = bim { Result::Ok(Option::None) } else { Result::Err(TestError::ZimZam(__to_str_array("error"))) } } ================================================ FILE: e2e/sway/types/scripts/script_b256/Forc.toml ================================================ [project] authors = ["Fuel Labs "] entry = "main.sw" license = "Apache-2.0" name = "script_b256" ================================================ FILE: e2e/sway/types/scripts/script_b256/src/main.sw ================================================ script; fn main(foo: b256) -> b256 { assert_eq( foo, 0x0101010101010101010101010101010101010101010101010101010101010101, ); 0x0202020202020202020202020202020202020202020202020202020202020202 } ================================================ FILE: e2e/sway/types/scripts/script_bytes/Forc.toml ================================================ [project] authors = ["Fuel Labs "] entry = "main.sw" license = "Apache-2.0" name = "script_bytes" ================================================ FILE: e2e/sway/types/scripts/script_bytes/src/main.sw ================================================ script; use std::bytes::Bytes; #[allow(dead_code)] enum SomeEnum { First: bool, Second: T, } struct Wrapper { inner: T, inner_enum: SomeEnum, } fn expected_bytes() -> Bytes { let mut bytes = Bytes::new(); bytes.push(40u8); bytes.push(41u8); bytes.push(42u8); bytes } fn main(_arg: u64, wrapper: Wrapper>) { if let SomeEnum::Second(enum_bytes) = wrapper.inner_enum { require( enum_bytes == expected_bytes(), "wrapper.inner_enum didn't carry the expected bytes", ) } else { require(false, "enum was not of variant Second"); } let inner_vec = wrapper.inner; require( inner_vec .len() == 2, "Expected wrapper.inner vector to have 2 elements", ); require( inner_vec .get(0) .unwrap() == expected_bytes(), "wrapper.inner[0] didn't match expectation", ); require( inner_vec .get(1) .unwrap() == expected_bytes(), "wrapper.inner[1] didn't match expectation", ); } ================================================ FILE: e2e/sway/types/scripts/script_generics/Forc.toml ================================================ [project] authors = ["Fuel Labs "] entry = "main.sw" license = "Apache-2.0" name = "script_generics" ================================================ FILE: e2e/sway/types/scripts/script_generics/src/main.sw ================================================ script; struct GenericBimbam { val: U, } #[allow(dead_code)] struct GenericSnack { twix: GenericBimbam, mars: V, } fn main( bim: GenericBimbam, bam: GenericSnack, ) -> (GenericSnack, GenericBimbam) { ( GenericSnack { twix: GenericBimbam { val: bam.mars.as_u64(), }, mars: 2u32 * bim.val.as_u32(), }, GenericBimbam { val: 255u8 }, ) } ================================================ FILE: e2e/sway/types/scripts/script_heap_types/Forc.toml ================================================ [project] authors = ["Fuel Labs "] entry = "main.sw" license = "Apache-2.0" name = "script_heap_types" ================================================ FILE: e2e/sway/types/scripts/script_heap_types/src/main.sw ================================================ script; use std::bytes::Bytes; use std::string::String; #[allow(dead_code)] struct StructGenerics { one: T, two: K, three: U, } #[allow(dead_code)] enum EnumGeneric { One: H, Two: I, } fn main() -> EnumGeneric>>, String> { let mut some_vec = Vec::new(); some_vec.push(2u8); some_vec.push(4u8); some_vec.push(8u8); let struct_generics = StructGenerics { one: Bytes::from(some_vec), two: String::from_ascii_str("fuel"), three: some_vec, }; let mut enum_vec = Vec::new(); enum_vec.push(struct_generics); enum_vec.push(struct_generics); EnumGeneric::One(enum_vec) } ================================================ FILE: e2e/sway/types/scripts/script_raw_slice/Forc.toml ================================================ [project] authors = ["Fuel Labs "] entry = "main.sw" license = "Apache-2.0" name = "script_raw_slice" ================================================ FILE: e2e/sway/types/scripts/script_raw_slice/src/main.sw ================================================ script; #[allow(dead_code)] enum SomeEnum { First: bool, Second: T, } struct Wrapper { inner: T, inner_enum: SomeEnum, } fn validate_raw_slice(input: raw_slice) { let vec: Vec = Vec::from(input); require(vec.len() == 3, "raw slice len is not 3"); require( vec .get(2) .unwrap() == 42, "expected 3rd slice entry to be 42", ); require( vec .get(1) .unwrap() == 41, "expected 2nd slice entry to be 41", ); require( vec .get(0) .unwrap() == 40, "expected 1st slice entry to be 40", ); } fn validate_vec(vec: Vec) { require(vec.len() == 2, "vec should have two elements"); validate_raw_slice(vec.get(0).unwrap()); validate_raw_slice(vec.get(1).unwrap()); } fn main(length: u8, wrapper: Wrapper>) -> raw_slice { if let SomeEnum::Second(enum_raw_slice) = wrapper.inner_enum { validate_raw_slice(enum_raw_slice); } else { require(false, "enum was not of variant Second"); } validate_vec(wrapper.inner); let mut vec = Vec::new(); let mut counter = 0u8; while counter < length { vec.push(counter); counter = counter + 1; } vec.as_raw_slice() } ================================================ FILE: e2e/sway/types/scripts/script_std_lib_string/Forc.toml ================================================ [project] authors = ["Fuel Labs "] entry = "main.sw" license = "Apache-2.0" name = "script_std_lib_string" ================================================ FILE: e2e/sway/types/scripts/script_std_lib_string/src/main.sw ================================================ script; use std::string::String; fn main(string: String) -> String { assert_eq(string, String::from_ascii_str("script-input")); String::from_ascii_str("script-return") } ================================================ FILE: e2e/sway/types/scripts/script_string_slice/Forc.toml ================================================ [project] authors = ["Fuel Labs "] entry = "main.sw" license = "Apache-2.0" name = "script_string_slice" ================================================ FILE: e2e/sway/types/scripts/script_string_slice/src/main.sw ================================================ script; fn main(input: str) -> str { assert_eq(input, "script-input"); "script-return" } ================================================ FILE: e2e/sway/types/scripts/script_tuples/Forc.toml ================================================ [project] authors = ["Fuel Labs "] entry = "main.sw" license = "Apache-2.0" name = "script_tuples" ================================================ FILE: e2e/sway/types/scripts/script_tuples/src/main.sw ================================================ script; #[allow(dead_code)] struct Bim { bim: u64, } #[allow(dead_code)] struct Bam { bam: str[5], } #[allow(dead_code)] struct Boum { boum: bool, } fn main(_my_tuple: (Bim, Bam, Boum), _zim: Bam) -> ((Boum, Bim, Bam), u64) { ( ( Boum { boum: true }, Bim { bim: 193817 }, Bam { bam: __to_str_array("hello"), }, ), 42242, ) } ================================================ FILE: e2e/sway/types/scripts/script_u128/Forc.toml ================================================ [project] authors = ["Fuel Labs "] entry = "main.sw" license = "Apache-2.0" name = "script_u128" ================================================ FILE: e2e/sway/types/scripts/script_u128/src/main.sw ================================================ script; use std::u128::U128; fn main(arg: U128) -> U128 { arg + U128::from((8, 2)) } ================================================ FILE: e2e/sway/types/scripts/script_u256/Forc.toml ================================================ [project] authors = ["Fuel Labs "] entry = "main.sw" license = "Apache-2.0" name = "script_u256" ================================================ FILE: e2e/sway/types/scripts/script_u256/src/main.sw ================================================ script; fn main(arg: u256) -> u256 { arg + 0x0000000000000006000000000000000700000000000000080000000000000009u256 } ================================================ FILE: e2e/sway/types/scripts/script_vectors/Forc.toml ================================================ [project] authors = ["Fuel Labs "] entry = "main.sw" license = "Apache-2.0" name = "script_vectors" ================================================ FILE: e2e/sway/types/scripts/script_vectors/src/data_structures.sw ================================================ library; pub struct SomeStruct { pub a: T, } pub enum SomeEnum { a: T, } ================================================ FILE: e2e/sway/types/scripts/script_vectors/src/eq_impls.sw ================================================ library; use ::data_structures::{SomeEnum, SomeStruct}; impl PartialEq for SomeEnum { fn eq(self, other: Self) -> bool { match self { SomeEnum::a(val) => { match other { SomeEnum::a(other_val) => { val == other_val } } } } } } impl PartialEq for SomeStruct { fn eq(self, other: Self) -> bool { self.a == other.a } } impl PartialEq for [Vec; 2] { fn eq(self, other: Self) -> bool { let mut i = 0; while i < 2 { if self[i] != other[i] { return false; } i += 1; } true } } impl PartialEq for [u64; 2] { fn eq(self, other: Self) -> bool { let mut i = 0; while i < 2 { if self[i] != other[i] { return false; } i += 1; } true } } impl PartialEq for SomeEnum> { fn eq(self, other: Self) -> bool { match self { SomeEnum::a(val) => { match other { SomeEnum::a(other_val) => { val == other_val } } } } } } impl PartialEq for SomeStruct> { fn eq(self, other: Self) -> bool { self.a == other.a } } impl PartialEq for SomeStruct>> { fn eq(self, other: Self) -> bool { self.a == other.a } } ================================================ FILE: e2e/sway/types/scripts/script_vectors/src/main.sw ================================================ script; pub mod data_structures; mod eq_impls; mod utils; use eq_impls::*; use utils::*; use data_structures::*; fn main( u32_vec: Vec, vec_in_vec: Vec>, struct_in_vec: Vec>, vec_in_struct: SomeStruct>, array_in_vec: Vec<[u64; 2]>, vec_in_array: [Vec; 2], vec_in_enum: SomeEnum>, enum_in_vec: Vec>, b256_in_vec: Vec, tuple_in_vec: Vec<(u32, u32)>, vec_in_tuple: (Vec, Vec), vec_in_a_vec_in_a_struct_in_a_vec: Vec>>>, ) -> bool { { let exp_u32_vec = vec_from([0u32, 1u32, 2u32]); require(u32_vec == exp_u32_vec, "u32_vec_error"); } { let mut exp_vec_in_vec = Vec::new(); exp_vec_in_vec.push(vec_from([0u32, 1u32, 2u32])); exp_vec_in_vec.push(vec_from([0u32, 1u32, 2u32])); require(vec_in_vec == exp_vec_in_vec, "vec_in_vec err"); } { let mut exp_struct_in_vec = Vec::new(); exp_struct_in_vec.push(SomeStruct { a: 0u32 }); exp_struct_in_vec.push(SomeStruct { a: 1u32 }); require(struct_in_vec == exp_struct_in_vec, "struct_in_vec err"); } { let exp_vec_in_struct = SomeStruct { a: vec_from([0u32, 1u32, 2u32]), }; require(vec_in_struct.a == exp_vec_in_struct.a, "vec_in_struct err"); } { let mut exp_array_in_vec = Vec::new(); exp_array_in_vec.push([0, 1]); exp_array_in_vec.push([0, 1]); require(array_in_vec == exp_array_in_vec, "array_in_vec err"); } { let exp_vec_in_array = [vec_from([0u32, 1u32, 2u32]), vec_from([0u32, 1u32, 2u32])]; require(vec_in_array == exp_vec_in_array, "vec_in_array err"); } { let exp_u32_vec = vec_from([0u32, 1u32, 2u32]); let exp_vec_in_enum = SomeEnum::a(exp_u32_vec); require(vec_in_enum == exp_vec_in_enum, "vec_in_enum err"); } { let mut exp_enum_in_vec = Vec::new(); exp_enum_in_vec.push(SomeEnum::a(0u32)); exp_enum_in_vec.push(SomeEnum::a(1u32)); require(enum_in_vec == exp_enum_in_vec, "enum_in_vec err"); } { let mut exp_b256_in_vec = Vec::new(); exp_b256_in_vec.push(0x0202020202020202020202020202020202020202020202020202020202020202); exp_b256_in_vec.push(0x0202020202020202020202020202020202020202020202020202020202020202); require(b256_in_vec == exp_b256_in_vec, "b256_in_vec err"); } { let mut exp_tuple_in_vec = Vec::new(); exp_tuple_in_vec.push((0u32, 0u32)); exp_tuple_in_vec.push((1u32, 1u32)); require(tuple_in_vec == exp_tuple_in_vec, "tuple_in_vec err"); } { let exp_vec_in_tuple = (vec_from([0u32, 1u32, 2u32]), vec_from([0u32, 1u32, 2u32])); require(vec_in_tuple == exp_vec_in_tuple, "vec_in_tuple err"); } { let mut exp_vec_in_a_vec_in_a_struct_in_a_vec = Vec::new(); let mut inner_vec_1 = Vec::new(); let inner_inner_vec_1 = vec_from([0u32, 1u32, 2u32]); inner_vec_1.push(inner_inner_vec_1); let inner_inner_vec_2 = vec_from([3u32, 4u32, 5u32]); inner_vec_1.push(inner_inner_vec_2); exp_vec_in_a_vec_in_a_struct_in_a_vec.push(SomeStruct { a: inner_vec_1 }); let mut inner_vec_2 = Vec::new(); let inner_inner_vec_3 = vec_from([6u32, 7u32, 8u32]); inner_vec_2.push(inner_inner_vec_3); let inner_inner_vec_4 = vec_from([9u32, 10u32, 11u32]); inner_vec_2.push(inner_inner_vec_4); exp_vec_in_a_vec_in_a_struct_in_a_vec.push(SomeStruct { a: inner_vec_2 }); require( vec_in_a_vec_in_a_struct_in_a_vec == exp_vec_in_a_vec_in_a_struct_in_a_vec, "vec_in_a_vec_in_a_struct_in_a_vec err", ); } true } ================================================ FILE: e2e/sway/types/scripts/script_vectors/src/utils.sw ================================================ library; pub fn vec_from(vals: [u32; 3]) -> Vec { let mut vec = Vec::new(); vec.push(vals[0]); vec.push(vals[1]); vec.push(vals[2]); vec } ================================================ FILE: e2e/tests/aws.rs ================================================ #[cfg(test)] mod tests { use anyhow::Result; use e2e::e2e_helpers::start_aws_kms; use fuels::{ accounts::{Account, ViewOnlyAccount, signers::kms::aws::AwsKmsSigner, wallet::Wallet}, core::traits::Signer, prelude::{ AssetId, Contract, LoadConfiguration, TxPolicies, launch_provider_and_get_wallet, }, types::errors::Context, }; #[tokio::test(flavor = "multi_thread")] async fn fund_aws_wallet() -> Result<()> { let kms = start_aws_kms(false).await?; let wallet = launch_provider_and_get_wallet().await?; let amount = 500000000; let key = kms.create_signer().await?; let address = key.address(); wallet .transfer(address, amount, AssetId::zeroed(), TxPolicies::default()) .await .context("Failed to transfer funds")?; let your_kms_key_id = key.key_id(); let provider = wallet.provider().clone(); let aws_client = kms.client(); // ANCHOR: use_kms_wallet let kms_signer = AwsKmsSigner::new(your_kms_key_id, aws_client).await?; let wallet = Wallet::new(kms_signer, provider); // ANCHOR_END: use_kms_wallet let total_base_balance = wallet.get_asset_balance(&AssetId::zeroed()).await?; assert_eq!(total_base_balance, amount as u128); Ok(()) } #[tokio::test(flavor = "multi_thread")] async fn deploy_contract() -> Result<()> { let kms = start_aws_kms(false).await?; let wallet = launch_provider_and_get_wallet().await?; let amount = 500000000; let key = kms.create_signer().await?; let address = key.address(); wallet .transfer(address, amount, AssetId::zeroed(), TxPolicies::default()) .await .context("Failed to transfer funds")?; let your_kms_key_id = key.key_id(); let provider = wallet.provider().clone(); let kms_signer = AwsKmsSigner::new(your_kms_key_id, kms.client()).await?; let aws_wallet = Wallet::new(kms_signer, provider); Contract::load_from( "../e2e/sway/contracts/contract_test/out/release/contract_test.bin", LoadConfiguration::default(), )? .deploy(&aws_wallet, TxPolicies::default()) .await?; Ok(()) } } ================================================ FILE: e2e/tests/binary_format.rs ================================================ #[cfg(test)] mod tests { use std::{convert::TryInto, ops::Range}; use fuels::programs::executable::{Executable, Regular}; const DATA_OFFSET_LOCATION: Range = 8..16; const CONFIGURABLES_OFFSET_LOCATION: Range = 16..24; const LEGACY_BINARY_PATH: &str = "../e2e/assets/precompiled_sway/legacy_format_simple_contract.bin"; const NEW_BINARY_PATH: &str = "../e2e/sway/bindings/simple_contract/out/release/simple_contract.bin"; #[test] fn no_configurables_offset_for_old_sway_binaries() { // given let (_, executable) = load(LEGACY_BINARY_PATH); // when let configurables_offset = executable.configurables_offset_in_code().unwrap(); // then assert_eq!(configurables_offset, None); } #[test] fn correct_data_offset_for_old_sway_binaries() { // given let (binary, executable) = load(LEGACY_BINARY_PATH); let expected_data_offset = read_offset(&binary, DATA_OFFSET_LOCATION); // when let data_offset = executable.data_offset_in_code().unwrap(); // then assert_eq!(data_offset, expected_data_offset); } #[test] fn correct_data_offset_for_new_sway_binaries() { // given let (binary, executable) = load(NEW_BINARY_PATH); let expected_data_offset = read_offset(&binary, DATA_OFFSET_LOCATION); // when let data_offset = executable.data_offset_in_code().unwrap(); // then assert_eq!(data_offset, expected_data_offset); } #[test] fn correct_configurables_offset_for_new_sway_binaries() { // given let (binary, executable) = load(NEW_BINARY_PATH); let expected_configurables_offset = read_offset(&binary, CONFIGURABLES_OFFSET_LOCATION); // when let configurables_offset = executable.configurables_offset_in_code(); // then let configurables_offset = configurables_offset .expect("to successfully detect a modern binary is used") .expect("to extract the configurables_offset"); assert_eq!(configurables_offset, expected_configurables_offset); } pub fn read_offset(binary: &[u8], range: Range) -> usize { assert_eq!(range.clone().count(), 8, "must be a range of 8 B"); let data: [u8; 8] = binary[range].try_into().unwrap(); u64::from_be_bytes(data) as usize } fn load(path: &str) -> (Vec, Executable) { let binary = std::fs::read(path).unwrap(); let executable = Executable::from_bytes(binary.clone()); (binary, executable) } } ================================================ FILE: e2e/tests/bindings.rs ================================================ use fuels::prelude::*; mod hygiene { #[tokio::test] async fn setup_program_test_is_hygienic() { fuels::prelude::setup_program_test!( Wallets("wallet"), Abigen(Contract( name = "SimpleContract", project = "e2e/sway/bindings/simple_contract" )), Deploy( name = "simple_contract_instance", contract = "SimpleContract", wallet = "wallet", random_salt = false, ), ); } } #[tokio::test] async fn compile_bindings_from_contract_file() { setup_program_test!( Wallets("wallet"), Abigen(Contract( name = "SimpleContract", project = "e2e/sway/bindings/simple_contract" )), Deploy( name = "simple_contract_instance", contract = "SimpleContract", wallet = "wallet", random_salt = false, ), ); let call_handler = simple_contract_instance .methods() .takes_int_returns_bool(42); let encoded_args = call_handler.call.encoded_args.unwrap(); assert_eq!(encoded_args, [0, 0, 0, 42]); } #[tokio::test] async fn compile_bindings_from_inline_contract() -> Result<()> { abigen!(Contract( name = "SimpleContract", // abi generated with: "e2e/sway/abi/simple_contract" abi = r#" { "programType": "contract", "specVersion": "1", "encodingVersion": "1", "concreteTypes": [ { "type": "bool", "concreteTypeId": "b760f44fa5965c2474a3b471467a22c43185152129295af588b022ae50b50903" }, { "type": "u32", "concreteTypeId": "d7649d428b9ff33d188ecbf38a7e4d8fd167fa01b2e10fe9a8f9308e52f1d7cc" } ], "metadataTypes": [], "functions": [ { "inputs": [ { "name": "_arg", "concreteTypeId": "d7649d428b9ff33d188ecbf38a7e4d8fd167fa01b2e10fe9a8f9308e52f1d7cc" } ], "name": "takes_u32_returns_bool", "output": "b760f44fa5965c2474a3b471467a22c43185152129295af588b022ae50b50903", "attributes": null } ], "loggedTypes": [], "messagesTypes": [], "configurables": [] } "#, )); let wallet = launch_provider_and_get_wallet().await?; let contract_instance = SimpleContract::new(ContractId::zeroed(), wallet); let call_handler = contract_instance.methods().takes_u32_returns_bool(42_u32); let encoded_args = call_handler.call.encoded_args.unwrap(); assert_eq!(encoded_args, [0, 0, 0, 42]); Ok(()) } #[tokio::test] async fn shared_types() -> Result<()> { setup_program_test!( Wallets("wallet"), Abigen( Contract( name = "ContractA", project = "e2e/sway/bindings/sharing_types/contract_a" ), Contract( name = "ContractB", project = "e2e/sway/bindings/sharing_types/contract_b" ), ), Deploy( name = "contract_a", contract = "ContractA", wallet = "wallet", random_salt = false, ), Deploy( name = "contract_b", contract = "ContractB", wallet = "wallet", random_salt = false, ), ); { let methods = contract_a.methods(); { let shared_struct_2 = SharedStruct2 { a: 11u32, b: SharedStruct1 { a: 12u32 }, }; let shared_enum = SharedEnum::a(10u64); let response = methods .uses_shared_type(shared_struct_2.clone(), shared_enum.clone()) .call() .await? .value; assert_eq!(response, (shared_struct_2, shared_enum)); } { let same_name_struct = abigen_bindings::contract_a_mod::StructSameNameButDifferentInternals { a: 13u32 }; let same_name_enum = abigen_bindings::contract_a_mod::EnumSameNameButDifferentInternals::a(14u32); let response = methods .uses_types_that_share_only_names(same_name_struct.clone(), same_name_enum.clone()) .call() .await? .value; assert_eq!(response, (same_name_struct, same_name_enum)); } { let arg = UniqueStructToContractA { a: SharedStruct2 { a: 15u32, b: SharedStruct1 { a: 5u8 }, }, }; let response = methods .uses_shared_type_inside_owned_one(arg.clone()) .call() .await? .value; assert_eq!(response, arg); } } { let methods = contract_b.methods(); { let shared_struct_2 = SharedStruct2 { a: 11u32, b: SharedStruct1 { a: 12u32 }, }; let shared_enum = SharedEnum::a(10u64); let response = methods .uses_shared_type(shared_struct_2.clone(), shared_enum.clone()) .call() .await? .value; assert_eq!(response, (shared_struct_2, shared_enum)); } { let same_name_struct = abigen_bindings::contract_b_mod::StructSameNameButDifferentInternals { a: [13u64] }; let same_name_enum = abigen_bindings::contract_b_mod::EnumSameNameButDifferentInternals::a([14u64]); let response = methods .uses_types_that_share_only_names(same_name_struct.clone(), same_name_enum.clone()) .call() .await? .value; assert_eq!(response, (same_name_struct, same_name_enum)); } { let arg = UniqueStructToContractB { a: SharedStruct2 { a: 15u32, b: SharedStruct1 { a: 5u8 }, }, }; let response = methods .uses_shared_type_inside_owned_one(arg.clone()) .call() .await? .value; assert_eq!(response, arg); } } Ok(()) } #[tokio::test] async fn type_paths_respected() -> Result<()> { setup_program_test!( Wallets("wallet"), Abigen(Contract( name = "ContractA", project = "e2e/sway/bindings/type_paths" )), Deploy( name = "contract_a_instance", contract = "ContractA", wallet = "wallet", random_salt = false, ), ); { let contract_a_type = abigen_bindings::contract_a_mod::contract_a_types::VeryCommonNameStruct { another_field: 10u32, }; let rtn = contract_a_instance .methods() .test_function(AWrapper { field: contract_a_type, }) .call() .await? .value; let rtn_using_the_other_type = abigen_bindings::contract_a_mod::another_lib::VeryCommonNameStruct { field_a: 10u32 }; assert_eq!(rtn, rtn_using_the_other_type); } Ok(()) } ================================================ FILE: e2e/tests/configurables.rs ================================================ use fuels::{ core::codec::EncoderConfig, prelude::*, types::{Bits256, SizedAsciiString, U256}, }; #[tokio::test] async fn contract_default_configurables() -> Result<()> { abigen!(Contract( name = "MyContract", abi = "e2e/sway/contracts/configurables/out/release/configurables-abi.json" )); let wallet = launch_provider_and_get_wallet().await?; let contract_id = Contract::load_from( "sway/contracts/configurables/out/release/configurables.bin", LoadConfiguration::default(), )? .deploy_if_not_exists(&wallet, TxPolicies::default()) .await? .contract_id; let contract_instance = MyContract::new(contract_id, wallet.clone()); let response = contract_instance .methods() .return_configurables() .call() .await?; let expected_value = ( true, 8, 16, 32, 63, U256::from(8), Bits256([1; 32]), "fuel".try_into()?, (8, true), [253, 254, 255], StructWithGeneric { field_1: 8u8, field_2: 16, }, EnumWithGeneric::VariantOne(true), ); assert_eq!(response.value, expected_value); Ok(()) } #[tokio::test] async fn script_default_configurables() -> Result<()> { setup_program_test!( Wallets("wallet"), Abigen(Script( name = "MyScript", project = "e2e/sway/scripts/script_configurables" )), LoadScript( name = "script_instance", script = "MyScript", wallet = "wallet" ) ); let mut script_instance = script_instance; script_instance.convert_into_loader().await?; let response = script_instance.main().call().await?; let expected_value = ( true, 8, 16, 32, 63, U256::from(8), Bits256([1; 32]), "fuel".try_into()?, (8, true), [253, 254, 255], StructWithGeneric { field_1: 8u8, field_2: 16, }, EnumWithGeneric::VariantOne(true), ); assert_eq!(response.value, expected_value); Ok(()) } #[tokio::test] async fn contract_configurables() -> Result<()> { // ANCHOR: contract_configurables abigen!(Contract( name = "MyContract", abi = "e2e/sway/contracts/configurables/out/release/configurables-abi.json" )); let wallet = launch_provider_and_get_wallet().await?; let str_4: SizedAsciiString<4> = "FUEL".try_into()?; let new_struct = StructWithGeneric { field_1: 16u8, field_2: 32, }; let new_enum = EnumWithGeneric::VariantTwo; let configurables = MyContractConfigurables::default() .with_BOOL(false)? .with_U8(7)? .with_U16(15)? .with_U32(31)? .with_U64(63)? .with_U256(U256::from(8))? .with_B256(Bits256([2; 32]))? .with_STR_4(str_4.clone())? .with_TUPLE((7, false))? .with_ARRAY([252, 253, 254])? .with_STRUCT(new_struct.clone())? .with_ENUM(new_enum.clone())?; let contract_id = Contract::load_from( "sway/contracts/configurables/out/release/configurables.bin", LoadConfiguration::default().with_configurables(configurables), )? .deploy_if_not_exists(&wallet, TxPolicies::default()) .await? .contract_id; let contract_instance = MyContract::new(contract_id, wallet.clone()); // ANCHOR_END: contract_configurables let response = contract_instance .methods() .return_configurables() .call() .await?; let expected_value = ( false, 7, 15, 31, 63, U256::from(8), Bits256([2; 32]), str_4, (7, false), [252, 253, 254], new_struct, new_enum, ); assert_eq!(response.value, expected_value); Ok(()) } #[tokio::test] async fn contract_manual_configurables() -> Result<()> { setup_program_test!( Abigen(Contract( name = "MyContract", project = "e2e/sway/contracts/configurables" )), Wallets("wallet") ); let str_4: SizedAsciiString<4> = "FUEL".try_into()?; let new_struct = StructWithGeneric { field_1: 16u8, field_2: 32, }; let new_enum = EnumWithGeneric::VariantTwo; let configurables = MyContractConfigurables::default() .with_BOOL(false)? .with_U8(7)? .with_U16(15)? .with_U32(31)? .with_U64(63)? .with_U256(U256::from(8))? .with_B256(Bits256([2; 32]))? .with_STR_4(str_4.clone())? .with_TUPLE((7, false))? .with_ARRAY([252, 253, 254])? .with_STRUCT(new_struct.clone())? .with_ENUM(new_enum.clone())?; let contract_id = Contract::load_from( "sway/contracts/configurables/out/release/configurables.bin", LoadConfiguration::default(), )? .with_configurables(configurables) .deploy_if_not_exists(&wallet, TxPolicies::default()) .await? .contract_id; let contract_instance = MyContract::new(contract_id, wallet.clone()); let response = contract_instance .methods() .return_configurables() .call() .await?; let expected_value = ( false, 7, 15, 31, 63, U256::from(8), Bits256([2; 32]), str_4, (7, false), [252, 253, 254], new_struct, new_enum, ); assert_eq!(response.value, expected_value); Ok(()) } #[tokio::test] async fn script_configurables() -> Result<()> { // ANCHOR: script_configurables abigen!(Script( name = "MyScript", abi = "e2e/sway/scripts/script_configurables/out/release/script_configurables-abi.json" )); let wallet = launch_provider_and_get_wallet().await?; let bin_path = "sway/scripts/script_configurables/out/release/script_configurables.bin"; let instance = MyScript::new(wallet, bin_path); let str_4: SizedAsciiString<4> = "FUEL".try_into()?; let new_struct = StructWithGeneric { field_1: 16u8, field_2: 32, }; let new_enum = EnumWithGeneric::VariantTwo; let configurables = MyScriptConfigurables::new(EncoderConfig { max_tokens: 5, ..Default::default() }) .with_BOOL(false)? .with_U8(7)? .with_U16(15)? .with_U32(31)? .with_U64(63)? .with_U256(U256::from(8))? .with_B256(Bits256([2; 32]))? .with_STR_4(str_4.clone())? .with_TUPLE((7, false))? .with_ARRAY([252, 253, 254])? .with_STRUCT(new_struct.clone())? .with_ENUM(new_enum.clone())?; let response = instance .with_configurables(configurables) .main() .call() .await?; // ANCHOR_END: script_configurables let expected_value = ( false, 7, 15, 31, 63, U256::from(8), Bits256([2; 32]), str_4, (7, false), [252, 253, 254], new_struct, new_enum, ); assert_eq!(response.value, expected_value); Ok(()) } #[tokio::test] async fn configurable_encoder_config_is_applied() { abigen!(Script( name = "MyScript", abi = "e2e/sway/scripts/script_configurables/out/release/script_configurables-abi.json" )); let new_struct = StructWithGeneric { field_1: 16u8, field_2: 32, }; { let _configurables = MyScriptConfigurables::default() .with_STRUCT(new_struct.clone()) .expect("no encoder config, it works"); } { let encoder_config = EncoderConfig { max_tokens: 1, ..Default::default() }; // Fails when a wrong encoder config is set let configurables_error = MyScriptConfigurables::new(encoder_config) .with_STRUCT(new_struct) .expect_err("should error"); assert!( configurables_error .to_string() .contains("token limit `1` reached while encoding. Try increasing it"), ) } } ================================================ FILE: e2e/tests/contracts.rs ================================================ use std::time::Duration; use fuel_tx::SubAssetId; use fuels::{ accounts::signers::private_key::PrivateKeySigner, core::codec::{DecoderConfig, EncoderConfig, calldata, encode_fn_selector}, prelude::*, programs::DEFAULT_MAX_FEE_ESTIMATION_TOLERANCE, tx::{ ConsensusParameters, ContractIdExt, ContractParameters, FeeParameters, consensus_parameters::{ConsensusParametersV1, FeeParametersV1}, }, types::{ Bits256, Identity, SizedAsciiString, errors::transaction::Reason, input::Input, output::Output, }, }; use rand::thread_rng; use tokio::time::Instant; #[tokio::test] async fn test_multiple_args() -> Result<()> { setup_program_test!( Wallets("wallet"), Abigen(Contract( name = "TestContract", project = "e2e/sway/contracts/contract_test" )), Deploy( name = "contract_instance", contract = "TestContract", wallet = "wallet", random_salt = false, ), ); // Make sure we can call the contract with multiple arguments let contract_methods = contract_instance.methods(); let response = contract_methods.get(5, 6).call().await?; assert_eq!(response.value, 11); let t = MyType { x: 5, y: 6 }; let response = contract_methods.get_alt(t.clone()).call().await?; assert_eq!(response.value, t); let response = contract_methods.get_single(5).call().await?; assert_eq!(response.value, 5); Ok(()) } #[tokio::test] async fn test_contract_calling_contract() -> Result<()> { // Tests a contract call that calls another contract (FooCaller calls FooContract underneath) setup_program_test!( Wallets("wallet"), Abigen( Contract( name = "LibContract", project = "e2e/sway/contracts/lib_contract" ), Contract( name = "LibContractCaller", project = "e2e/sway/contracts/lib_contract_caller" ), ), Deploy( name = "lib_contract_instance", contract = "LibContract", wallet = "wallet", random_salt = false, ), Deploy( name = "lib_contract_instance2", contract = "LibContract", wallet = "wallet", random_salt = false, ), Deploy( name = "contract_caller_instance", contract = "LibContractCaller", wallet = "wallet", random_salt = false, ), ); let lib_contract_id = lib_contract_instance.contract_id(); let lib_contract_id2 = lib_contract_instance2.contract_id(); // Call the contract directly. It increments the given value. let response = lib_contract_instance.methods().increment(42).call().await?; assert_eq!(43, response.value); let response = contract_caller_instance .methods() .increment_from_contracts(lib_contract_id, lib_contract_id2, 42) // Note that the two lib_contract_instances have different types .with_contracts(&[&lib_contract_instance, &lib_contract_instance2]) .call() .await?; assert_eq!(86, response.value); // ANCHOR: external_contract let response = contract_caller_instance .methods() .increment_from_contract(lib_contract_id, 42) .with_contracts(&[&lib_contract_instance]) .call() .await?; // ANCHOR_END: external_contract assert_eq!(43, response.value); // ANCHOR: external_contract_ids let response = contract_caller_instance .methods() .increment_from_contract(lib_contract_id, 42) .with_contract_ids(&[lib_contract_id]) .call() .await?; // ANCHOR_END: external_contract_ids assert_eq!(43, response.value); Ok(()) } #[tokio::test] async fn test_reverting_transaction() -> Result<()> { setup_program_test!( Wallets("wallet"), Abigen(Contract( name = "RevertContract", project = "e2e/sway/contracts/revert_transaction_error" )), Deploy( name = "contract_instance", contract = "RevertContract", wallet = "wallet", random_salt = false, ), ); let response = contract_instance .methods() .make_transaction_fail(true) .call() .await; assert!(matches!( response, Err(Error::Transaction(Reason::Failure { revert_id, .. })) if revert_id == Some(128) )); Ok(()) } #[tokio::test] async fn test_multiple_read_calls() -> Result<()> { setup_program_test!( Wallets("wallet"), Abigen(Contract( name = "MultiReadContract", project = "e2e/sway/contracts/multiple_read_calls" )), Deploy( name = "contract_instance", contract = "MultiReadContract", wallet = "wallet", random_salt = false, ), ); let contract_methods = contract_instance.methods(); contract_methods.store(42).call().await?; // Use "simulate" because the methods don't actually // run a transaction, but just a dry-run let stored = contract_methods .read() .simulate(Execution::state_read_only()) .await?; assert_eq!(stored.value, 42); let stored = contract_methods .read() .simulate(Execution::state_read_only()) .await?; assert_eq!(stored.value, 42); Ok(()) } #[tokio::test] async fn test_multi_call_beginner() -> Result<()> { setup_program_test!( Wallets("wallet"), Abigen(Contract( name = "TestContract", project = "e2e/sway/contracts/contract_test" )), Deploy( name = "contract_instance", contract = "TestContract", wallet = "wallet", random_salt = false, ), ); let contract_methods = contract_instance.methods(); let call_handler_1 = contract_methods.get_single(7); let call_handler_2 = contract_methods.get_single(42); let multi_call_handler = CallHandler::new_multi_call(wallet.clone()) .add_call(call_handler_1) .add_call(call_handler_2); let (val_1, val_2): (u64, u64) = multi_call_handler.call().await?.value; assert_eq!(val_1, 7); assert_eq!(val_2, 42); Ok(()) } #[tokio::test] async fn test_multi_call_pro() -> Result<()> { setup_program_test!( Wallets("wallet"), Abigen(Contract( name = "TestContract", project = "e2e/sway/contracts/contract_test" )), Deploy( name = "contract_instance", contract = "TestContract", wallet = "wallet", random_salt = false, ), ); let my_type_1 = MyType { x: 1, y: 2 }; let my_type_2 = MyType { x: 3, y: 4 }; let contract_methods = contract_instance.methods(); let call_handler_1 = contract_methods.get_single(5); let call_handler_2 = contract_methods.get_single(6); let call_handler_3 = contract_methods.get_alt(my_type_1.clone()); let call_handler_4 = contract_methods.get_alt(my_type_2.clone()); let call_handler_5 = contract_methods.get_array([7; 2]); let call_handler_6 = contract_methods.get_array([42; 2]); let multi_call_handler = CallHandler::new_multi_call(wallet.clone()) .add_call(call_handler_1) .add_call(call_handler_2) .add_call(call_handler_3) .add_call(call_handler_4) .add_call(call_handler_5) .add_call(call_handler_6); let (val_1, val_2, type_1, type_2, array_1, array_2): ( u64, u64, MyType, MyType, [u64; 2], [u64; 2], ) = multi_call_handler.call().await?.value; assert_eq!(val_1, 5); assert_eq!(val_2, 6); assert_eq!(type_1, my_type_1); assert_eq!(type_2, my_type_2); assert_eq!(array_1, [7; 2]); assert_eq!(array_2, [42; 2]); Ok(()) } #[tokio::test] async fn test_contract_call_fee_estimation() -> Result<()> { setup_program_test!( Wallets("wallet"), Abigen(Contract( name = "TestContract", project = "e2e/sway/contracts/contract_test" )), Deploy( name = "contract_instance", contract = "TestContract", wallet = "wallet", random_salt = false, ), ); let gas_limit = 800; let tolerance = Some(0.2); let block_horizon = Some(1); let expected_script_gas = 800; let expected_total_gas = 8463; let expected_metered_bytes_size = 824; let estimated_transaction_cost = contract_instance .methods() .initialize_counter(42) .with_tx_policies(TxPolicies::default().with_script_gas_limit(gas_limit)) .estimate_transaction_cost(tolerance, block_horizon) .await?; assert_eq!(estimated_transaction_cost.script_gas, expected_script_gas); assert_eq!(estimated_transaction_cost.total_gas, expected_total_gas); assert_eq!( estimated_transaction_cost.metered_bytes_size, expected_metered_bytes_size ); Ok(()) } #[tokio::test] async fn contract_call_has_same_estimated_and_used_gas() -> Result<()> { setup_program_test!( Wallets("wallet"), Abigen(Contract( name = "TestContract", project = "e2e/sway/contracts/contract_test" )), Deploy( name = "contract_instance", contract = "TestContract", wallet = "wallet", random_salt = false, ), ); let contract_methods = contract_instance.methods(); let tolerance = Some(0.0); let block_horizon = Some(1); let estimated_total_gas = contract_methods .initialize_counter(42) .estimate_transaction_cost(tolerance, block_horizon) .await? .total_gas; let gas_used = contract_methods .initialize_counter(42) .call() .await? .tx_status .total_gas; assert_eq!(estimated_total_gas, gas_used); Ok(()) } #[tokio::test] async fn mult_call_has_same_estimated_and_used_gas() -> Result<()> { setup_program_test!( Wallets("wallet"), Abigen(Contract( name = "TestContract", project = "e2e/sway/contracts/contract_test" )), Deploy( name = "contract_instance", contract = "TestContract", wallet = "wallet", random_salt = false, ), ); let contract_methods = contract_instance.methods(); let call_handler_1 = contract_methods.initialize_counter(42); let call_handler_2 = contract_methods.get_array([42; 2]); let multi_call_handler = CallHandler::new_multi_call(wallet.clone()) .add_call(call_handler_1) .add_call(call_handler_2); let tolerance = Some(0.0); let block_horizon = Some(1); let estimated_total_gas = multi_call_handler .estimate_transaction_cost(tolerance, block_horizon) .await? .total_gas; let total_gas = multi_call_handler .call::<(u64, [u64; 2])>() .await? .tx_status .total_gas; assert_eq!(estimated_total_gas, total_gas); Ok(()) } #[tokio::test] async fn contract_method_call_respects_maturity_and_expiration() -> Result<()> { setup_program_test!( Wallets("wallet"), Abigen(Contract( name = "BlockHeightContract", project = "e2e/sway/contracts/transaction_block_height" )), Deploy( name = "contract_instance", contract = "BlockHeightContract", wallet = "wallet", random_salt = false, ), ); let provider = wallet.provider(); let maturity = 10; let expiration = 20; let call_handler = contract_instance .methods() .calling_this_will_produce_a_block() .with_tx_policies( TxPolicies::default() .with_maturity(maturity) .with_expiration(expiration), ); { let err = call_handler .clone() .call() .await .expect_err("maturity not reached"); assert!(err.to_string().contains("TransactionMaturity")); } { provider.produce_blocks(15, None).await?; call_handler .clone() .call() .await .expect("should succeed. Block height between `maturity` and `expiration`"); } { provider.produce_blocks(15, None).await?; let err = call_handler.call().await.expect_err("expiration reached"); assert!(err.to_string().contains("TransactionExpiration")); } Ok(()) } #[tokio::test] async fn test_auth_msg_sender_from_sdk() -> Result<()> { setup_program_test!( Wallets("wallet"), Abigen(Contract( name = "AuthContract", project = "e2e/sway/contracts/auth_testing_contract" )), Deploy( name = "contract_instance", contract = "AuthContract", wallet = "wallet", random_salt = false, ), ); // Contract returns true if `msg_sender()` matches `wallet.address()`. let response = contract_instance .methods() .check_msg_sender(wallet.address()) .call() .await?; assert!(response.value); Ok(()) } #[tokio::test] async fn test_large_return_data() -> Result<()> { setup_program_test!( Wallets("wallet"), Abigen(Contract( name = "TestContract", project = "e2e/sway/contracts/large_return_data" )), Deploy( name = "contract_instance", contract = "TestContract", wallet = "wallet", random_salt = false, ), ); let contract_methods = contract_instance.methods(); let res = contract_methods.get_id().call().await?; assert_eq!( res.value.0, [ 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 ] ); // One word-sized string let res = contract_methods.get_small_string().call().await?; assert_eq!(res.value, "gggggggg"); // Two word-sized string let res = contract_methods.get_large_string().call().await?; assert_eq!(res.value, "ggggggggg"); // Large struct will be bigger than a `WORD`. let res = contract_methods.get_large_struct().call().await?; assert_eq!(res.value.foo, 12); assert_eq!(res.value.bar, 42); // Array will be returned in `ReturnData`. let res = contract_methods.get_large_array().call().await?; assert_eq!(res.value, [1, 2]); let res = contract_methods.get_contract_id().call().await?; // First `value` is from `CallResponse`. // Second `value` is from the `ContractId` type. assert_eq!( res.value, ContractId::from([ 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 ]) ); Ok(()) } #[tokio::test] async fn can_handle_function_called_new() -> Result<()> { setup_program_test!( Wallets("wallet"), Abigen(Contract( name = "TestContract", project = "e2e/sway/contracts/contract_test" )), Deploy( name = "contract_instance", contract = "TestContract", wallet = "wallet", random_salt = false, ), ); let response = contract_instance.methods().new().call().await?.value; assert_eq!(response, 12345); Ok(()) } #[tokio::test] async fn test_contract_setup_macro_deploy_with_salt() -> Result<()> { // ANCHOR: contract_setup_macro_multi setup_program_test!( Wallets("wallet"), Abigen( Contract( name = "LibContract", project = "e2e/sway/contracts/lib_contract" ), Contract( name = "LibContractCaller", project = "e2e/sway/contracts/lib_contract_caller" ), ), Deploy( name = "lib_contract_instance", contract = "LibContract", wallet = "wallet", random_salt = false, ), Deploy( name = "contract_caller_instance", contract = "LibContractCaller", wallet = "wallet", ), Deploy( name = "contract_caller_instance2", contract = "LibContractCaller", wallet = "wallet", ), ); let lib_contract_id = lib_contract_instance.contract_id(); let contract_caller_id = contract_caller_instance.contract_id(); let contract_caller_id2 = contract_caller_instance2.contract_id(); // Because we deploy with salt, we can deploy the same contract multiple times assert_ne!(contract_caller_id, contract_caller_id2); // The first contract can be called because they were deployed on the same provider let response = contract_caller_instance .methods() .increment_from_contract(lib_contract_id, 42) .with_contracts(&[&lib_contract_instance]) .call() .await?; assert_eq!(43, response.value); let response = contract_caller_instance2 .methods() .increment_from_contract(lib_contract_id, 42) .with_contracts(&[&lib_contract_instance]) .call() .await?; assert_eq!(43, response.value); // ANCHOR_END: contract_setup_macro_multi Ok(()) } #[tokio::test] async fn test_wallet_getter() -> Result<()> { setup_program_test!( Wallets("wallet"), Abigen(Contract( name = "TestContract", project = "e2e/sway/contracts/contract_test" )), Deploy( name = "contract_instance", contract = "TestContract", wallet = "wallet", random_salt = false, ), ); assert_eq!(contract_instance.account().address(), wallet.address()); //`contract_id()` is tested in // async fn test_contract_calling_contract() -> Result<()> { Ok(()) } #[tokio::test] async fn test_connect_wallet() -> Result<()> { // ANCHOR: contract_setup_macro_manual_wallet let config = WalletsConfig::new(Some(2), Some(1), Some(DEFAULT_COIN_AMOUNT)); let mut wallets = launch_custom_provider_and_get_wallets(config, None, None).await?; let wallet = wallets.pop().unwrap(); let wallet_2 = wallets.pop().unwrap(); setup_program_test!( Abigen(Contract( name = "TestContract", project = "e2e/sway/contracts/contract_test" )), Deploy( name = "contract_instance", contract = "TestContract", wallet = "wallet", random_salt = false, ), ); // ANCHOR_END: contract_setup_macro_manual_wallet // pay for call with wallet let tx_policies = TxPolicies::default() .with_tip(100) .with_script_gas_limit(1_000_000); contract_instance .methods() .initialize_counter(42) .with_tx_policies(tx_policies) .call() .await?; // confirm that funds have been deducted let wallet_balance = wallet.get_asset_balance(&Default::default()).await?; assert!(DEFAULT_COIN_AMOUNT as u128 > wallet_balance); // pay for call with wallet_2 contract_instance .with_account(wallet_2.clone()) .methods() .initialize_counter(42) .with_tx_policies(tx_policies) .call() .await?; // confirm there are no changes to wallet, wallet_2 has been charged let wallet_balance_second_call = wallet.get_asset_balance(&Default::default()).await?; let wallet_2_balance = wallet_2.get_asset_balance(&Default::default()).await?; assert_eq!(wallet_balance_second_call, wallet_balance); assert!(DEFAULT_COIN_AMOUNT as u128 > wallet_2_balance); Ok(()) } async fn setup_output_variable_estimation_test() -> Result<(Vec, [Identity; 3], AssetId, ContractId)> { let wallet_config = WalletsConfig::new(Some(3), None, None); let wallets = launch_custom_provider_and_get_wallets(wallet_config, None, None).await?; let contract_id = Contract::load_from( "sway/contracts/token_ops/out/release/token_ops.bin", LoadConfiguration::default(), )? .deploy_if_not_exists(&wallets[0], TxPolicies::default()) .await? .contract_id; let mint_asset_id = contract_id.asset_id(&SubAssetId::zeroed()); let addresses = wallets .iter() .map(|wallet| wallet.address().into()) .collect::>() .try_into() .unwrap(); Ok((wallets, addresses, mint_asset_id, contract_id)) } #[tokio::test] async fn test_output_variable_estimation() -> Result<()> { abigen!(Contract( name = "MyContract", abi = "e2e/sway/contracts/token_ops/out/release/token_ops-abi.json" )); let (wallets, addresses, mint_asset_id, contract_id) = setup_output_variable_estimation_test().await?; let contract_instance = MyContract::new(contract_id, wallets[0].clone()); let contract_methods = contract_instance.methods(); let amount = 1000; { // Should fail due to lack of output variables let response = contract_methods .mint_to_addresses(amount, addresses) .call() .await; assert!(matches!( response, Err(Error::Transaction(Reason::Failure { .. })) )); } { // Should add 3 output variables automatically let _ = contract_methods .mint_to_addresses(amount, addresses) .with_variable_output_policy(VariableOutputPolicy::EstimateMinimum) .call() .await?; for wallet in wallets.iter() { let balance = wallet.get_asset_balance(&mint_asset_id).await?; assert_eq!(balance, amount as u128); } } Ok(()) } #[tokio::test] async fn test_output_variable_estimation_multicall() -> Result<()> { abigen!(Contract( name = "MyContract", abi = "e2e/sway/contracts/token_ops/out/release/token_ops-abi.json" )); let (wallets, addresses, mint_asset_id, contract_id) = setup_output_variable_estimation_test().await?; let contract_instance = MyContract::new(contract_id, wallets[0].clone()); let contract_methods = contract_instance.methods(); const NUM_OF_CALLS: u64 = 3; let amount = 1000; let total_amount = amount * NUM_OF_CALLS; let mut multi_call_handler = CallHandler::new_multi_call(wallets[0].clone()); for _ in 0..NUM_OF_CALLS { let call_handler = contract_methods.mint_to_addresses(amount, addresses); multi_call_handler = multi_call_handler.add_call(call_handler); } wallets[0] .force_transfer_to_contract( contract_id, total_amount, AssetId::zeroed(), TxPolicies::default(), ) .await .unwrap(); let base_layer_address = Bits256([1u8; 32]); let call_handler = contract_methods.send_message(base_layer_address, amount); multi_call_handler = multi_call_handler.add_call(call_handler); let _ = multi_call_handler .with_variable_output_policy(VariableOutputPolicy::EstimateMinimum) .call::<((), (), ())>() .await?; for wallet in wallets.iter() { let balance = wallet.get_asset_balance(&mint_asset_id).await?; assert_eq!(balance, 3 * amount as u128); } Ok(()) } #[tokio::test] async fn test_contract_instance_get_balances() -> Result<()> { let mut rng = thread_rng(); let signer = PrivateKeySigner::random(&mut rng); let (coins, asset_ids) = setup_multiple_assets_coins(signer.address(), 2, 4, 8); let random_asset_id = asset_ids[1]; let provider = setup_test_provider(coins.clone(), vec![], None, None).await?; let wallet = Wallet::new(signer, provider.clone()); setup_program_test!( Abigen(Contract( name = "TestContract", project = "e2e/sway/contracts/contract_test" )), Deploy( name = "contract_instance", contract = "TestContract", wallet = "wallet", random_salt = false, ), ); let contract_id = contract_instance.contract_id(); // Check the current balance of the contract with id 'contract_id' let contract_balances = contract_instance.get_balances().await?; assert!(contract_balances.is_empty()); // Transfer an amount to the contract let amount = 8; wallet .force_transfer_to_contract(contract_id, amount, random_asset_id, TxPolicies::default()) .await?; // Check that the contract now has 1 coin let contract_balances = contract_instance.get_balances().await?; assert_eq!(contract_balances.len(), 1); let random_asset_balance = contract_balances.get(&random_asset_id).unwrap(); assert_eq!(*random_asset_balance, amount); Ok(()) } #[tokio::test] async fn contract_call_futures_implement_send() -> Result<()> { use std::future::Future; fn tokio_spawn_imitation(_: T) where T: Future + Send + 'static, { } setup_program_test!( Wallets("wallet"), Abigen(Contract( name = "TestContract", project = "e2e/sway/contracts/contract_test" )), Deploy( name = "contract_instance", contract = "TestContract", wallet = "wallet", random_salt = false, ), ); tokio_spawn_imitation(async move { contract_instance .methods() .initialize_counter(42) .call() .await .unwrap(); }); Ok(()) } #[tokio::test] async fn test_contract_set_estimation() -> Result<()> { setup_program_test!( Wallets("wallet"), Abigen( Contract( name = "LibContract", project = "e2e/sway/contracts/lib_contract" ), Contract( name = "LibContractCaller", project = "e2e/sway/contracts/lib_contract_caller" ), ), Deploy( name = "lib_contract_instance", contract = "LibContract", wallet = "wallet", random_salt = false, ), Deploy( name = "contract_caller_instance", contract = "LibContractCaller", wallet = "wallet", random_salt = false, ), ); let lib_contract_id = lib_contract_instance.contract_id(); let res = lib_contract_instance.methods().increment(42).call().await?; assert_eq!(43, res.value); { // Should fail due to missing external contracts let res = contract_caller_instance .methods() .increment_from_contract(lib_contract_id, 42) .call() .await; assert!(matches!( res, Err(Error::Transaction(Reason::Failure { .. })) )); } let res = contract_caller_instance .methods() .increment_from_contract(lib_contract_id, 42) .determine_missing_contracts() .await? .call() .await?; assert_eq!(43, res.value); Ok(()) } #[tokio::test] async fn test_output_variable_contract_id_estimation_multicall() -> Result<()> { setup_program_test!( Wallets("wallet"), Abigen( Contract( name = "LibContract", project = "e2e/sway/contracts/lib_contract" ), Contract( name = "LibContractCaller", project = "e2e/sway/contracts/lib_contract_caller" ), Contract( name = "TestContract", project = "e2e/sway/contracts/contract_test" ), ), Deploy( name = "lib_contract_instance", contract = "LibContract", wallet = "wallet", random_salt = false, ), Deploy( name = "contract_caller_instance", contract = "LibContractCaller", wallet = "wallet", random_salt = false, ), Deploy( name = "contract_test_instance", contract = "TestContract", wallet = "wallet", random_salt = false, ), ); let lib_contract_id = lib_contract_instance.contract_id(); let contract_methods = contract_caller_instance.methods(); let mut multi_call_handler = CallHandler::new_multi_call(wallet.clone()).with_tx_policies(Default::default()); for _ in 0..3 { let call_handler = contract_methods.increment_from_contract(lib_contract_id, 42); multi_call_handler = multi_call_handler.add_call(call_handler); } // add call that does not need ContractId let contract_methods = contract_test_instance.methods(); let call_handler = contract_methods.get(5, 6); multi_call_handler = multi_call_handler.add_call(call_handler); let call_response = multi_call_handler .determine_missing_contracts() .await? .call::<(u64, u64, u64, u64)>() .await?; assert_eq!(call_response.value, (43, 43, 43, 11)); Ok(()) } #[tokio::test] async fn test_contract_call_with_non_default_max_input() -> Result<()> { use fuels::{ tx::{ConsensusParameters, TxParameters}, types::coin::Coin, }; let mut consensus_parameters = ConsensusParameters::default(); let tx_params = TxParameters::default() .with_max_inputs(123) .with_max_size(1_000_000); consensus_parameters.set_tx_params(tx_params); let contract_params = ContractParameters::default().with_contract_max_size(1_000_000); consensus_parameters.set_contract_params(contract_params); let mut rng = thread_rng(); let signer = PrivateKeySigner::random(&mut rng); let coins: Vec = setup_single_asset_coins( signer.address(), Default::default(), DEFAULT_NUM_COINS, DEFAULT_COIN_AMOUNT, ); let chain_config = ChainConfig { consensus_parameters: consensus_parameters.clone(), ..ChainConfig::default() }; let provider = setup_test_provider(coins, vec![], None, Some(chain_config)).await?; let wallet = Wallet::new(signer, provider.clone()); assert_eq!(consensus_parameters, provider.consensus_parameters().await?); setup_program_test!( Abigen(Contract( name = "TestContract", project = "e2e/sway/contracts/contract_test" )), Deploy( name = "contract_instance", contract = "TestContract", wallet = "wallet", random_salt = false, ), ); let response = contract_instance.methods().get(5, 6).call().await?; assert_eq!(response.value, 11); Ok(()) } #[tokio::test] async fn test_add_custom_assets() -> Result<()> { let initial_amount = 100_000; let asset_base = AssetConfig { id: AssetId::zeroed(), num_coins: 1, coin_amount: initial_amount, }; let asset_id_1 = AssetId::from([3u8; 32]); let asset_1 = AssetConfig { id: asset_id_1, num_coins: 1, coin_amount: initial_amount, }; let asset_id_2 = AssetId::from([1u8; 32]); let asset_2 = AssetConfig { id: asset_id_2, num_coins: 1, coin_amount: initial_amount, }; let assets = vec![asset_base, asset_1, asset_2]; let num_wallets = 2; let wallet_config = WalletsConfig::new_multiple_assets(num_wallets, assets); let mut wallets = launch_custom_provider_and_get_wallets(wallet_config, None, None).await?; let wallet_1 = wallets.pop().unwrap(); let wallet_2 = wallets.pop().unwrap(); setup_program_test!( Abigen(Contract( name = "MyContract", project = "e2e/sway/contracts/contract_test" )), Deploy( name = "contract_instance", contract = "MyContract", wallet = "wallet_1", random_salt = false, ), ); let amount_1 = 5000; let amount_2 = 3000; let response = contract_instance .methods() .get(5, 6) .add_custom_asset(asset_id_1, amount_1, Some(wallet_2.address())) .add_custom_asset(asset_id_2, amount_2, Some(wallet_2.address())) .call() .await?; assert_eq!(response.value, 11); let balance_asset_1 = wallet_1.get_asset_balance(&asset_id_1).await?; let balance_asset_2 = wallet_1.get_asset_balance(&asset_id_2).await?; assert_eq!(balance_asset_1, (initial_amount - amount_1) as u128); assert_eq!(balance_asset_2, (initial_amount - amount_2) as u128); let balance_asset_1 = wallet_2.get_asset_balance(&asset_id_1).await?; let balance_asset_2 = wallet_2.get_asset_balance(&asset_id_2).await?; assert_eq!(balance_asset_1, (initial_amount + amount_1) as u128); assert_eq!(balance_asset_2, (initial_amount + amount_2) as u128); Ok(()) } #[tokio::test] async fn contract_load_error_messages() { { let binary_path = "sway/contracts/contract_test/out/release/no_file_on_path.bin"; let expected_error = format!("io: file \"{binary_path}\" does not exist"); let error = Contract::load_from(binary_path, LoadConfiguration::default()) .expect_err("should have failed"); assert_eq!(error.to_string(), expected_error); } { let binary_path = "sway/contracts/contract_test/out/release/contract_test-abi.json"; let expected_error = format!("expected \"{binary_path}\" to have '.bin' extension"); let error = Contract::load_from(binary_path, LoadConfiguration::default()) .expect_err("should have failed"); assert_eq!(error.to_string(), expected_error); } } #[tokio::test] async fn test_payable_annotation() -> Result<()> { setup_program_test!( Wallets("wallet"), Abigen(Contract( name = "TestContract", project = "e2e/sway/contracts/payable_annotation" )), Deploy( name = "contract_instance", contract = "TestContract", wallet = "wallet", random_salt = false, ), ); let contract_methods = contract_instance.methods(); let response = contract_methods .payable() .call_params( CallParameters::default() .with_amount(100) .with_gas_forwarded(20_000), )? .call() .await?; assert_eq!(response.value, 42); // ANCHOR: non_payable_params let err = contract_methods .non_payable() .call_params(CallParameters::default().with_amount(100)) .expect_err("should return error"); assert!(matches!(err, Error::Other(s) if s.contains("assets forwarded to non-payable method"))); // ANCHOR_END: non_payable_params let response = contract_methods .non_payable() .call_params(CallParameters::default().with_gas_forwarded(20_000))? .call() .await?; assert_eq!(response.value, 42); Ok(()) } #[tokio::test] async fn multi_call_from_calls_with_different_account_types() -> Result<()> { use fuels::prelude::*; abigen!(Contract( name = "MyContract", abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json" )); let wallet = launch_provider_and_get_wallet().await?; let predicate = Predicate::from_code(vec![]); let contract_methods_wallet = MyContract::new(ContractId::default(), wallet.clone()).methods(); let contract_methods_predicate = MyContract::new(ContractId::default(), predicate).methods(); let call_handler_1 = contract_methods_wallet.initialize_counter(42); let call_handler_2 = contract_methods_predicate.get_array([42; 2]); let _multi_call_handler = CallHandler::new_multi_call(wallet) .add_call(call_handler_1) .add_call(call_handler_2); Ok(()) } #[tokio::test] async fn low_level_call() -> Result<()> { setup_program_test!( Wallets("wallet"), Abigen( Contract( name = "MyCallerContract", project = "e2e/sway/contracts/low_level_caller" ), Contract( name = "MyTargetContract", project = "e2e/sway/contracts/contract_test" ), ), Deploy( name = "caller_contract_instance", contract = "MyCallerContract", wallet = "wallet", random_salt = false, ), Deploy( name = "target_contract_instance", contract = "MyTargetContract", wallet = "wallet", random_salt = false, ), ); { let function_selector = encode_fn_selector("initialize_counter"); let call_data = calldata!(42u64)?; caller_contract_instance .methods() .call_low_level_call( target_contract_instance.id(), Bytes(function_selector), Bytes(call_data), ) .determine_missing_contracts() .await? .call() .await?; let response = target_contract_instance .methods() .read_counter() .call() .await?; assert_eq!(response.value, 42); } { let function_selector = encode_fn_selector("set_value_multiple_complex"); let call_data = calldata!( MyStruct { a: true, b: [1, 2, 3], }, SizedAsciiString::<4>::try_from("fuel")? )?; caller_contract_instance .methods() .call_low_level_call( target_contract_instance.id(), Bytes(function_selector), Bytes(call_data), ) .determine_missing_contracts() .await? .call() .await?; let result_uint = target_contract_instance .methods() .read_counter() .call() .await? .value; let result_bool = target_contract_instance .methods() .get_bool_value() .call() .await? .value; let result_str = target_contract_instance .methods() .get_str_value() .call() .await? .value; assert_eq!(result_uint, 2); assert!(result_bool); assert_eq!(result_str, "fuel"); } Ok(()) } #[cfg(any(not(feature = "fuel-core-lib"), feature = "rocksdb"))] #[test] fn db_rocksdb() { use std::fs; use fuels::{ accounts::wallet::Wallet, client::{PageDirection, PaginationRequest}, prelude::{DEFAULT_COIN_AMOUNT, DbType, Error, ViewOnlyAccount, setup_test_provider}, }; let temp_dir = tempfile::tempdir().expect("failed to make tempdir"); let temp_database_path = temp_dir.path().join("db"); tokio::runtime::Runtime::new() .expect("tokio runtime failed") .block_on(async { let _ = temp_dir; let signer = PrivateKeySigner::new( "0x4433d156e8c53bf5b50af07aa95a29436f29a94e0ccc5d58df8e57bdc8583c32" .parse() .unwrap(), ); const NUMBER_OF_ASSETS: u64 = 2; let node_config = NodeConfig { database_type: DbType::RocksDb(Some(temp_database_path.clone())), ..NodeConfig::default() }; let chain_config = ChainConfig { consensus_parameters: Default::default(), ..ChainConfig::local_testnet() }; let (coins, _) = setup_multiple_assets_coins( signer.address(), NUMBER_OF_ASSETS, DEFAULT_NUM_COINS, DEFAULT_COIN_AMOUNT, ); let provider = setup_test_provider(coins.clone(), vec![], Some(node_config), Some(chain_config)) .await?; provider.produce_blocks(2, None).await?; Ok::<(), Error>(()) }) .unwrap(); // The runtime needs to be terminated because the node can currently only be killed when the runtime itself shuts down. tokio::runtime::Runtime::new() .expect("tokio runtime failed") .block_on(async { let node_config = NodeConfig { database_type: DbType::RocksDb(Some(temp_database_path.clone())), ..NodeConfig::default() }; let provider = setup_test_provider(vec![], vec![], Some(node_config), None).await?; // the same wallet that was used when rocksdb was built. When we connect it to the provider, we expect it to have the same amount of assets let signer = PrivateKeySigner::new( "0x4433d156e8c53bf5b50af07aa95a29436f29a94e0ccc5d58df8e57bdc8583c32" .parse() .unwrap(), ); let wallet = Wallet::new(signer, provider.clone()); let blocks = provider .get_blocks(PaginationRequest { cursor: None, results: 10, direction: PageDirection::Forward, }) .await? .results; assert_eq!(blocks.len(), 3); assert_eq!( *wallet.get_balances().await?.iter().next().unwrap().1, DEFAULT_COIN_AMOUNT as u128 ); assert_eq!( *wallet.get_balances().await?.iter().next().unwrap().1, DEFAULT_COIN_AMOUNT as u128 ); assert_eq!(wallet.get_balances().await?.len(), 2); fs::remove_dir_all( temp_database_path .parent() .expect("db parent folder does not exist"), )?; Ok::<(), Error>(()) }) .unwrap(); } #[tokio::test] async fn can_configure_decoding_of_contract_return() -> Result<()> { setup_program_test!( Wallets("wallet"), Abigen(Contract( name = "MyContract", project = "e2e/sway/contracts/needs_custom_decoder" ),), Deploy( contract = "MyContract", name = "contract_instance", wallet = "wallet", random_salt = false, ) ); let methods = contract_instance.methods(); { // Single call: Will not work if max_tokens not big enough methods.i_return_a_1k_el_array().with_decoder_config(DecoderConfig{max_tokens: 100, ..Default::default()}).call().await.expect_err( "should have failed because there are more tokens than what is supported by default", ); } { // Single call: Works when limit is bumped let result = methods .i_return_a_1k_el_array() .with_decoder_config(DecoderConfig { max_tokens: 1001, ..Default::default() }) .call() .await? .value; assert_eq!(result, [0; 1000]); } { // Multi call: Will not work if max_tokens not big enough CallHandler::new_multi_call(wallet.clone()) .add_call(methods.i_return_a_1k_el_array()) .with_decoder_config(DecoderConfig { max_tokens: 100, ..Default::default() }) .call::<([u8; 1000],)>().await.expect_err( "should have failed because there are more tokens than what is supported by default", ); } { // Multi call: Works when configured CallHandler::new_multi_call(wallet.clone()) .add_call(methods.i_return_a_1k_el_array()) .with_decoder_config(DecoderConfig { max_tokens: 1001, ..Default::default() }) .call::<([u8; 1000],)>() .await .unwrap(); } Ok(()) } #[tokio::test] async fn test_contract_submit_and_response() -> Result<()> { setup_program_test!( Wallets("wallet"), Abigen(Contract( name = "TestContract", project = "e2e/sway/contracts/contract_test" )), Deploy( name = "contract_instance", contract = "TestContract", wallet = "wallet", random_salt = false, ), ); let contract_methods = contract_instance.methods(); let submitted_tx = contract_methods.get(1, 2).submit().await?; tokio::time::sleep(Duration::from_millis(500)).await; let value = submitted_tx.response().await?.value; assert_eq!(value, 3); let contract_methods = contract_instance.methods(); let call_handler_1 = contract_methods.get_single(7); let call_handler_2 = contract_methods.get_single(42); let multi_call_handler = CallHandler::new_multi_call(wallet.clone()) .add_call(call_handler_1) .add_call(call_handler_2); let handle = multi_call_handler.submit().await?; tokio::time::sleep(Duration::from_millis(500)).await; let (val_1, val_2): (u64, u64) = handle.response().await?.value; assert_eq!(val_1, 7); assert_eq!(val_2, 42); Ok(()) } #[tokio::test] async fn test_heap_type_multicall() -> Result<()> { setup_program_test!( Wallets("wallet"), Abigen( Contract( name = "TestContract", project = "e2e/sway/contracts/contract_test" ), Contract( name = "VectorOutputContract", project = "e2e/sway/types/contracts/vector_output" ) ), Deploy( name = "contract_instance", contract = "VectorOutputContract", wallet = "wallet", random_salt = false, ), Deploy( name = "contract_instance_2", contract = "TestContract", wallet = "wallet", random_salt = false, ), ); { let call_handler_1 = contract_instance.methods().u8_in_vec(5); let call_handler_2 = contract_instance_2.methods().get_single(7); let call_handler_3 = contract_instance.methods().u8_in_vec(3); let multi_call_handler = CallHandler::new_multi_call(wallet.clone()) .add_call(call_handler_1) .add_call(call_handler_2) .add_call(call_handler_3); let (val_1, val_2, val_3): (Vec, u64, Vec) = multi_call_handler.call().await?.value; assert_eq!(val_1, vec![0, 1, 2, 3, 4]); assert_eq!(val_2, 7); assert_eq!(val_3, vec![0, 1, 2]); } Ok(()) } #[tokio::test] async fn heap_types_correctly_offset_in_create_transactions_w_storage_slots() -> Result<()> { setup_program_test!( Wallets("wallet"), Abigen(Predicate( name = "MyPredicate", project = "e2e/sway/types/predicates/predicate_vector" ),), ); let provider = wallet.provider().clone(); let data = MyPredicateEncoder::default().encode_data(18, 24, vec![2, 4, 42])?; let predicate = Predicate::load_from( "sway/types/predicates/predicate_vector/out/release/predicate_vector.bin", )? .with_data(data) .with_provider(provider); wallet .transfer( predicate.address(), 10_000, AssetId::zeroed(), TxPolicies::default(), ) .await?; // if the contract is successfully deployed then the predicate was unlocked. This further means // the offsets were setup correctly since the predicate uses heap types in its arguments. // Storage slots were loaded automatically by default Contract::load_from( "sway/contracts/storage/out/release/storage.bin", LoadConfiguration::default(), )? .deploy_if_not_exists(&predicate, TxPolicies::default()) .await?; Ok(()) } #[tokio::test] async fn test_arguments_with_gas_forwarded() -> Result<()> { setup_program_test!( Wallets("wallet"), Abigen( Contract( name = "TestContract", project = "e2e/sway/contracts/contract_test" ), Contract( name = "VectorOutputContract", project = "e2e/sway/types/contracts/vectors" ) ), Deploy( name = "contract_instance", contract = "TestContract", wallet = "wallet", random_salt = false, ), Deploy( name = "contract_instance_2", contract = "VectorOutputContract", wallet = "wallet", random_salt = false, ), ); let x = 128; let vec_input = vec![0, 1, 2]; { let response = contract_instance .methods() .get_single(x) .call_params(CallParameters::default().with_gas_forwarded(4096))? .call() .await?; assert_eq!(response.value, x); } { contract_instance_2 .methods() .u32_vec(vec_input.clone()) .call_params(CallParameters::default().with_gas_forwarded(4096))? .call() .await?; } { let call_handler_1 = contract_instance.methods().get_single(x); let call_handler_2 = contract_instance_2.methods().u32_vec(vec_input); let multi_call_handler = CallHandler::new_multi_call(wallet.clone()) .add_call(call_handler_1) .add_call(call_handler_2); let (value, _): (u64, ()) = multi_call_handler.call().await?.value; assert_eq!(value, x); } Ok(()) } #[tokio::test] async fn contract_custom_call_no_signatures_strategy() -> Result<()> { setup_program_test!( Wallets("wallet"), Abigen(Contract( name = "TestContract", project = "e2e/sway/contracts/contract_test" )), Deploy( name = "contract_instance", contract = "TestContract", wallet = "wallet", random_salt = false, ), ); let provider = wallet.provider(); let counter = 42; let call_handler = contract_instance.methods().initialize_counter(counter); let mut tb = call_handler.transaction_builder().await?; let base_asset_id = *provider.consensus_parameters().await?.base_asset_id(); let amount = 10; let consensus_parameters = provider.consensus_parameters().await?; let new_base_inputs = wallet .get_asset_inputs_for_amount(base_asset_id, amount, None) .await?; tb.inputs_mut().extend(new_base_inputs); tb.outputs_mut() .push(Output::change(wallet.address(), 0, base_asset_id)); // ANCHOR: tb_no_signatures_strategy let mut tx = tb .with_build_strategy(ScriptBuildStrategy::NoSignatures) .build(provider) .await?; // ANCHOR: tx_sign_with tx.sign_with(wallet.signer(), consensus_parameters.chain_id()) .await?; // ANCHOR_END: tx_sign_with // ANCHOR_END: tb_no_signatures_strategy let tx_id = provider.send_transaction(tx).await?; tokio::time::sleep(Duration::from_millis(500)).await; let tx_status = provider.tx_status(&tx_id).await?; let response = call_handler.get_response(tx_status)?; assert_eq!(counter, response.value); Ok(()) } #[tokio::test] async fn contract_encoder_config_is_applied() -> Result<()> { setup_program_test!( Abigen(Contract( name = "TestContract", project = "e2e/sway/contracts/contract_test" )), Wallets("wallet") ); let contract_id = Contract::load_from( "sway/contracts/contract_test/out/release/contract_test.bin", LoadConfiguration::default(), )? .deploy_if_not_exists(&wallet, TxPolicies::default()) .await? .contract_id; let instance = TestContract::new(contract_id, wallet.clone()); { let _encoding_ok = instance .methods() .get(0, 1) .call() .await .expect("should not fail as it uses the default encoder config"); } { let encoder_config = EncoderConfig { max_tokens: 1, ..Default::default() }; let instance_with_encoder_config = instance.with_encoder_config(encoder_config); // uses 2 tokens when 1 is the limit let encoding_error = instance_with_encoder_config .methods() .get(0, 1) .call() .await .expect_err("should error"); assert!(encoding_error.to_string().contains( "cannot encode contract call arguments: codec: token limit `1` reached while encoding." )); let encoding_error = instance_with_encoder_config .methods() .get(0, 1) .simulate(Execution::realistic()) .await .expect_err("should error"); assert!(encoding_error.to_string().contains( "cannot encode contract call arguments: codec: token limit `1` reached while encoding." )); } Ok(()) } #[tokio::test] async fn test_reentrant_calls() -> Result<()> { setup_program_test!( Wallets("wallet"), Abigen(Contract( name = "LibContractCaller", project = "e2e/sway/contracts/lib_contract_caller" ),), Deploy( name = "contract_caller_instance", contract = "LibContractCaller", wallet = "wallet", random_salt = false, ), ); let contract_id = contract_caller_instance.contract_id(); let response = contract_caller_instance .methods() .re_entrant(contract_id, true) .call() .await?; assert_eq!(42, response.value); Ok(()) } #[tokio::test] async fn msg_sender_gas_estimation_issue() { // Gas estimation requires an input of the base asset. If absent, a fake input is // added. However, if a non-base coin is present and the fake input introduces a // second owner, it causes the `msg_sender` sway fn to fail. This leads // to a premature failure in gas estimation, risking transaction failure due to // a low gas limit. let mut rng = thread_rng(); let signer = PrivateKeySigner::random(&mut rng); let (coins, ids) = setup_multiple_assets_coins(signer.address(), 2, DEFAULT_NUM_COINS, DEFAULT_COIN_AMOUNT); let provider = setup_test_provider(coins, vec![], None, None) .await .unwrap(); let wallet = Wallet::new(signer, provider.clone()); setup_program_test!( Abigen(Contract( name = "MyContract", project = "e2e/sway/contracts/msg_methods" )), Deploy( contract = "MyContract", name = "contract_instance", wallet = "wallet", random_salt = false, ) ); let asset_id = ids[0]; // The fake coin won't be added if we add a base asset, so let's not do that assert!( asset_id != *provider .consensus_parameters() .await .unwrap() .base_asset_id() ); let call_params = CallParameters::default() .with_amount(100) .with_asset_id(asset_id); contract_instance .methods() .message_sender() .call_params(call_params) .unwrap() .call() .await .unwrap(); } #[tokio::test] async fn variable_output_estimation_is_optimized() -> Result<()> { setup_program_test!( Wallets("wallet"), Abigen(Contract( name = "MyContract", project = "e2e/sway/contracts/var_outputs" )), Deploy( contract = "MyContract", name = "contract_instance", wallet = "wallet", random_salt = false, ) ); let contract_methods = contract_instance.methods(); let coins = 252; let recipient = Identity::Address(wallet.address()); let start = Instant::now(); let _ = contract_methods .mint(coins, recipient) .with_variable_output_policy(VariableOutputPolicy::EstimateMinimum) .call() .await?; // debug builds are slower (20x for `fuel-core-lib`, 4x for a release-fuel-core-binary) // we won't validate in that case so we don't have to maintain two expectations if !cfg!(debug_assertions) { let elapsed = start.elapsed().as_secs(); let limit = 2; if elapsed > limit { panic!("Estimation took too long ({elapsed}). Limit is {limit}"); } } Ok(()) } fn config_for_high_price_node() -> (WalletsConfig, NodeConfig, ChainConfig) { let wallet_config = WalletsConfig::new(None, None, None); let fee_parameters = FeeParameters::V1(FeeParametersV1 { gas_price_factor: 92000, gas_per_byte: 63, }); let consensus_parameters = ConsensusParameters::V1(ConsensusParametersV1 { fee_params: fee_parameters, ..Default::default() }); let node_config = NodeConfig { starting_gas_price: 1100, ..NodeConfig::default() }; let chain_config = ChainConfig { consensus_parameters, ..ChainConfig::default() }; (wallet_config, node_config, chain_config) } async fn setup_node_with_high_price() -> Result> { let (wallet_config, node_config, chain_config) = config_for_high_price_node(); let wallets = launch_custom_provider_and_get_wallets( wallet_config, Some(node_config), Some(chain_config), ) .await?; Ok(wallets) } #[cfg(any(not(feature = "fuel-core-lib"), feature = "rocksdb"))] async fn setup_node_with_high_price_historical_execution() -> Result<(Vec, tempfile::TempDir)> { let (wallet_config, mut node_config, chain_config) = config_for_high_price_node(); let temp_dir = tempfile::tempdir().expect("failed to make tempdir"); let temp_database_path = temp_dir.path().join("db"); node_config.database_type = DbType::RocksDb(Some(temp_database_path)); node_config.historical_execution = true; let wallets = launch_custom_provider_and_get_wallets( wallet_config, Some(node_config), Some(chain_config), ) .await?; Ok((wallets, temp_dir)) } #[tokio::test] async fn simulations_can_be_made_without_coins() -> Result<()> { abigen!(Contract( name = "MyContract", abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json" )); let wallet = setup_node_with_high_price().await?.pop().unwrap(); let contract_id = Contract::load_from( "sway/contracts/contract_test/out/release/contract_test.bin", LoadConfiguration::default(), )? .deploy_if_not_exists(&wallet, TxPolicies::default()) .await? .contract_id; let provider = wallet.provider().clone(); let no_funds_wallet = Wallet::random(&mut thread_rng(), provider); let response = MyContract::new(contract_id, no_funds_wallet) .methods() .get(5, 6) .simulate(Execution::state_read_only()) .await?; assert_eq!(response.value, 11); Ok(()) } #[tokio::test] #[cfg(any(not(feature = "fuel-core-lib"), feature = "rocksdb"))] async fn simulations_can_be_made_at_specific_block_height() -> Result<()> { let (mut wallets, _temp_dir) = setup_node_with_high_price_historical_execution().await?; let wallet = wallets.pop().unwrap(); setup_program_test!( Abigen(Contract( name = "MyContract", project = "e2e/sway/contracts/contract_test" )), Deploy( name = "contract_instance", contract = "MyContract", wallet = "wallet", random_salt = false, ), ); let contract_id = contract_instance.contract_id(); let provider = wallet.provider(); let contract_methods = contract_instance.methods(); contract_methods.initialize_counter(42).call().await?; provider.produce_blocks(5, None).await?; let block_height = provider.latest_block_height().await?; provider.produce_blocks(5, None).await?; contract_methods.increment_counter(24).call().await?; let no_funds_wallet = Wallet::random(&mut thread_rng(), provider.clone()); let no_funds_methods = MyContract::new(contract_id, no_funds_wallet).methods(); { let response = no_funds_methods .read_counter() .simulate(Execution::state_read_only()) .await?; assert_eq!(response.value, 66); } { let response = no_funds_methods .read_counter() .simulate(Execution::state_read_only().at_height(block_height)) .await?; assert_eq!(response.value, 42); } Ok(()) } #[tokio::test] async fn simulations_can_be_made_without_coins_multicall() -> Result<()> { abigen!(Contract( name = "MyContract", abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json" )); let wallet = setup_node_with_high_price().await?.pop().unwrap(); let contract_id = Contract::load_from( "sway/contracts/contract_test/out/release/contract_test.bin", LoadConfiguration::default(), )? .deploy_if_not_exists(&wallet, TxPolicies::default()) .await? .contract_id; let provider = wallet.provider().clone(); let no_funds_wallet = Wallet::random(&mut thread_rng(), provider); let contract_instance = MyContract::new(contract_id, no_funds_wallet.clone()); let contract_methods = contract_instance.methods(); let call_handler_1 = contract_methods.get(1, 2); let call_handler_2 = contract_methods.get(3, 4); let mut multi_call_handler = CallHandler::new_multi_call(no_funds_wallet) .add_call(call_handler_1) .add_call(call_handler_2); let value: (u64, u64) = multi_call_handler .simulate(Execution::state_read_only()) .await? .value; assert_eq!(value, (3, 7)); Ok(()) } #[tokio::test] async fn contract_call_with_non_zero_base_asset_id_and_tip() -> Result<()> { use fuels::{prelude::*, tx::ConsensusParameters}; abigen!(Contract( name = "MyContract", abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json" )); let asset_id = AssetId::new([1; 32]); let mut consensus_parameters = ConsensusParameters::default(); consensus_parameters.set_base_asset_id(asset_id); let config = ChainConfig { consensus_parameters, ..Default::default() }; let asset_base = AssetConfig { id: asset_id, num_coins: 1, coin_amount: 10_000, }; let wallet_config = WalletsConfig::new_multiple_assets(1, vec![asset_base]); let wallets = launch_custom_provider_and_get_wallets(wallet_config, None, Some(config)).await?; let wallet = wallets.first().expect("has wallet"); let contract_id = Contract::load_from( "sway/contracts/contract_test/out/release/contract_test.bin", LoadConfiguration::default(), )? .deploy_if_not_exists(wallet, TxPolicies::default()) .await? .contract_id; let contract_instance = MyContract::new(contract_id, wallet.clone()); let response = contract_instance .methods() .initialize_counter(42) .with_tx_policies(TxPolicies::default().with_tip(10)) .call() .await?; assert_eq!(42, response.value); Ok(()) } #[tokio::test] async fn max_fee_estimation_respects_tolerance() -> Result<()> { use fuels::prelude::*; let mut rng = rand::thread_rng(); let call_signer = PrivateKeySigner::random(&mut rng); let call_coins = setup_single_asset_coins(call_signer.address(), AssetId::BASE, 1000, 1); let deploy_signer = PrivateKeySigner::random(&mut rng); let deploy_coins = setup_single_asset_coins(deploy_signer.address(), AssetId::BASE, 1, 1_000_000); let provider = setup_test_provider([call_coins, deploy_coins].concat(), vec![], None, None).await?; let call_wallet = Wallet::new(call_signer, provider.clone()); let deploy_wallet = Wallet::new(deploy_signer, provider.clone()); setup_program_test!( Abigen(Contract( name = "MyContract", project = "e2e/sway/contracts/contract_test" )), Deploy( name = "contract_instance", wallet = "deploy_wallet", contract = "MyContract", random_salt = false, ) ); let contract_instance = contract_instance.with_account(call_wallet.clone()); let max_fee_from_tx = |tolerance: f32| { let contract_instance = contract_instance.clone(); let provider = provider.clone(); async move { let builder = contract_instance .methods() .initialize_counter(42) .transaction_builder() .await .unwrap(); assert_eq!( builder.max_fee_estimation_tolerance, DEFAULT_MAX_FEE_ESTIMATION_TOLERANCE, "Expected pre-set tolerance" ); builder .with_max_fee_estimation_tolerance(tolerance) .build(&provider) .await .unwrap() .max_fee() .unwrap() } }; let max_fee_from_builder = |tolerance: f32| { let contract_instance = contract_instance.clone(); let provider = provider.clone(); async move { contract_instance .methods() .initialize_counter(42) .transaction_builder() .await .unwrap() .with_max_fee_estimation_tolerance(tolerance) .estimate_max_fee(&provider) .await .unwrap() } }; let base_amount_in_inputs = |tolerance: f32| { let contract_instance = contract_instance.clone(); let call_wallet = &call_wallet; async move { let mut tb = contract_instance .methods() .initialize_counter(42) .transaction_builder() .await .unwrap() .with_max_fee_estimation_tolerance(tolerance); call_wallet.adjust_for_fee(&mut tb, 0).await.unwrap(); tb.inputs .iter() .filter_map(|input: &Input| match input { Input::ResourceSigned { resource } if resource.coin_asset_id().unwrap() == AssetId::BASE => { Some(resource.amount()) } _ => None, }) .sum::() } }; let no_increase_max_fee = max_fee_from_tx(0.0).await; let increased_max_fee = max_fee_from_tx(2.00).await; assert_eq!( increased_max_fee as f64 / no_increase_max_fee as f64, 1.00 + 2.00 ); let no_increase_max_fee = max_fee_from_builder(0.0).await; let increased_max_fee = max_fee_from_builder(2.00).await; assert_eq!( increased_max_fee as f64 / no_increase_max_fee as f64, 1.00 + 2.00 ); let normal_base_asset = base_amount_in_inputs(0.0).await; let more_base_asset_due_to_bigger_tolerance = base_amount_in_inputs(5.00).await; assert!(more_base_asset_due_to_bigger_tolerance > normal_base_asset); Ok(()) } #[tokio::test] async fn blob_contract_deployment() -> Result<()> { abigen!(Contract( name = "MyContract", abi = "e2e/sway/contracts/huge_contract/out/release/huge_contract-abi.json" )); let contract_binary = "sway/contracts/huge_contract/out/release/huge_contract.bin"; let contract_size = std::fs::metadata(contract_binary) .expect("contract file not found") .len(); assert!( contract_size > 150_000, "the testnet size limit was around 100kB, we want a contract bigger than that to reflect prod (current: {contract_size}B)" ); let wallets = launch_custom_provider_and_get_wallets(WalletsConfig::new(Some(2), None, None), None, None) .await?; let provider = wallets[0].provider().clone(); let consensus_parameters = provider.consensus_parameters().await?; let contract_max_size = consensus_parameters.contract_params().contract_max_size(); assert!( contract_size > contract_max_size, "this test should ideally be run with a contract bigger than the max contract size ({contract_max_size}B) so that we know deployment couldn't have happened without blobs" ); let contract = Contract::load_from(contract_binary, LoadConfiguration::default())?; let contract_id = contract .convert_to_loader(100_000)? .deploy_if_not_exists(&wallets[0], TxPolicies::default()) .await? .contract_id; let contract_instance = MyContract::new(contract_id, wallets[0].clone()); let response = contract_instance.methods().something().call().await?.value; assert_eq!(response, 1001); Ok(()) } #[tokio::test] async fn regular_contract_can_be_deployed() -> Result<()> { // given setup_program_test!( Wallets("wallet"), Abigen(Contract( name = "MyContract", project = "e2e/sway/contracts/contract_test" )), ); let contract_binary = "sway/contracts/contract_test/out/release/contract_test.bin"; // when let contract_id = Contract::load_from(contract_binary, LoadConfiguration::default())? .deploy_if_not_exists(&wallet, TxPolicies::default()) .await? .contract_id; // then let contract_instance = MyContract::new(contract_id, wallet); let response = contract_instance .methods() .read_counter() .call() .await? .value; assert_eq!(response, 0); Ok(()) } #[tokio::test] async fn unuploaded_loader_can_be_deployed_directly() -> Result<()> { setup_program_test!( Wallets("wallet"), Abigen(Contract( name = "MyContract", project = "e2e/sway/contracts/huge_contract" )), ); let contract_binary = "sway/contracts/huge_contract/out/release/huge_contract.bin"; let contract_id = Contract::load_from(contract_binary, LoadConfiguration::default())? .convert_to_loader(1024)? .deploy_if_not_exists(&wallet, TxPolicies::default()) .await? .contract_id; let contract_instance = MyContract::new(contract_id, wallet); let response = contract_instance.methods().something().call().await?.value; assert_eq!(response, 1001); Ok(()) } #[tokio::test] async fn unuploaded_loader_can_upload_blobs_separately_then_deploy() -> Result<()> { setup_program_test!( Wallets("wallet"), Abigen(Contract( name = "MyContract", project = "e2e/sway/contracts/huge_contract" )), ); let contract_binary = "sway/contracts/huge_contract/out/release/huge_contract.bin"; let contract = Contract::load_from(contract_binary, LoadConfiguration::default())? .convert_to_loader(1024)? .upload_blobs(&wallet, TxPolicies::default()) .await?; let blob_ids = contract.blob_ids(); // if this were an example for the user we'd just call `deploy` on the contract above // this way we are testing that the blobs were really deployed above, otherwise the following // would fail let contract_id = Contract::loader_from_blob_ids( blob_ids.to_vec(), contract.salt(), contract.storage_slots().to_vec(), )? .deploy_if_not_exists(&wallet, TxPolicies::default()) .await? .contract_id; let contract_instance = MyContract::new(contract_id, wallet); let response = contract_instance.methods().something().call().await?.value; assert_eq!(response, 1001); Ok(()) } #[tokio::test] async fn loader_blob_already_uploaded_not_an_issue() -> Result<()> { setup_program_test!( Wallets("wallet"), Abigen(Contract( name = "MyContract", project = "e2e/sway/contracts/huge_contract" )), ); let contract_binary = "sway/contracts/huge_contract/out/release/huge_contract.bin"; let contract = Contract::load_from(contract_binary, LoadConfiguration::default())? .convert_to_loader(1024)?; // this will upload blobs contract .clone() .upload_blobs(&wallet, TxPolicies::default()) .await?; // this will try to upload the blobs but skip upon encountering an error let contract_id = contract .deploy_if_not_exists(&wallet, TxPolicies::default()) .await? .contract_id; let contract_instance = MyContract::new(contract_id, wallet); let response = contract_instance.methods().something().call().await?.value; assert_eq!(response, 1001); Ok(()) } #[tokio::test] async fn loader_works_via_proxy() -> Result<()> { let wallet = launch_provider_and_get_wallet().await?; abigen!( Contract( name = "MyContract", abi = "e2e/sway/contracts/huge_contract/out/release/huge_contract-abi.json" ), Contract( name = "MyProxy", abi = "e2e/sway/contracts/proxy/out/release/proxy-abi.json" ) ); let contract_binary = "sway/contracts/huge_contract/out/release/huge_contract.bin"; let contract = Contract::load_from(contract_binary, LoadConfiguration::default())?; let contract_id = contract .convert_to_loader(100)? .deploy_if_not_exists(&wallet, TxPolicies::default()) .await? .contract_id; let contract_binary = "sway/contracts/proxy/out/release/proxy.bin"; let proxy_id = Contract::load_from(contract_binary, LoadConfiguration::default())? .deploy_if_not_exists(&wallet, TxPolicies::default()) .await? .contract_id; let proxy = MyProxy::new(proxy_id, wallet.clone()); proxy .methods() .set_target_contract(contract_id) .call() .await?; let response = proxy .methods() .something() .with_contract_ids(&[contract_id]) .call() .await? .value; assert_eq!(response, 1001); Ok(()) } #[tokio::test] async fn loader_storage_works_via_proxy() -> Result<()> { let wallet = launch_provider_and_get_wallet().await?; abigen!( Contract( name = "MyContract", abi = "e2e/sway/contracts/huge_contract/out/release/huge_contract-abi.json" ), Contract( name = "MyProxy", abi = "e2e/sway/contracts/proxy/out/release/proxy-abi.json" ) ); let contract_binary = "sway/contracts/huge_contract/out/release/huge_contract.bin"; let contract = Contract::load_from(contract_binary, LoadConfiguration::default())?; let contract_storage_slots = contract.storage_slots().to_vec(); let contract_id = contract .convert_to_loader(100)? .deploy_if_not_exists(&wallet, TxPolicies::default()) .await? .contract_id; let contract_binary = "sway/contracts/proxy/out/release/proxy.bin"; let proxy_contract = Contract::load_from(contract_binary, LoadConfiguration::default())?; let combined_storage_slots = [&contract_storage_slots, proxy_contract.storage_slots()].concat(); let proxy_id = proxy_contract .with_storage_slots(combined_storage_slots) .deploy_if_not_exists(&wallet, TxPolicies::default()) .await? .contract_id; let proxy = MyProxy::new(proxy_id, wallet.clone()); proxy .methods() .set_target_contract(contract_id) .call() .await?; let response = proxy .methods() .read_some_u64() .with_contract_ids(&[contract_id]) .call() .await? .value; assert_eq!(response, 42); let _res = proxy .methods() .write_some_u64(36) .with_contract_ids(&[contract_id]) .call() .await?; let response = proxy .methods() .read_some_u64() .with_contract_ids(&[contract_id]) .call() .await? .value; assert_eq!(response, 36); Ok(()) } #[tokio::test] async fn adjust_for_fee_errors() -> Result<()> { setup_program_test!( Wallets("wallet"), Abigen(Contract( name = "MyContract", project = "e2e/sway/contracts/contract_test" )), ); let contract_binary = "sway/contracts/contract_test/out/release/contract_test.bin"; let err = Contract::load_from(contract_binary, LoadConfiguration::default())? .deploy(&wallet, TxPolicies::default().with_tip(10_000_000_000_000)) .await .expect_err("should return error"); assert!( matches!(err, Error::Provider(s) if s.contains("failed to adjust inputs to cover for missing \ base asset: failed to get base asset \ (0000000000000000000000000000000000000000000000000000000000000000) inputs with amount:")) ); Ok(()) } #[tokio::test] async fn tx_input_output() -> Result<()> { let [wallet_1, wallet_2] = launch_custom_provider_and_get_wallets( WalletsConfig::new(Some(2), Some(10), Some(1000)), None, None, ) .await? .try_into() .unwrap(); abigen!(Contract( name = "TxContract", abi = "e2e/sway/contracts/tx_input_output/out/release/tx_input_output-abi.json" )); let contract_binary = "sway/contracts/tx_input_output/out/release/tx_input_output.bin"; // Set `wallet_1` as the custom input owner let configurables = TxContractConfigurables::default().with_OWNER(wallet_1.address())?; let contract = Contract::load_from( contract_binary, LoadConfiguration::default().with_configurables(configurables), )?; let contract_id = contract .deploy_if_not_exists(&wallet_2, TxPolicies::default()) .await? .contract_id; let contract_instance = TxContract::new(contract_id, wallet_2.clone()); let asset_id = AssetId::zeroed(); { let custom_input = wallet_1 .get_asset_inputs_for_amount(asset_id, 10, None) .await? .pop() .unwrap(); // Input at first position is a coin owned by wallet_1 let _ = contract_instance .methods() .check_input(0) .with_inputs(vec![custom_input]) .add_signer(wallet_1.signer().clone()) .call() .await?; let custom_output = Output::change(wallet_1.address(), 0, asset_id); // Output at first position is change to wallet_1 let _ = contract_instance .methods() .check_output_is_change(0) .with_outputs(vec![custom_output]) .call() .await?; } { // Input at first position is not a coin owned by wallet_1 let err = contract_instance .methods() .check_input(0) .call() .await .unwrap_err(); assert!(err.to_string().contains("input is not a coin")); // Output at first position is not change let err = contract_instance .methods() .check_output_is_change(0) .call() .await .unwrap_err(); assert!(err.to_string().contains("output is not change")); } Ok(()) } #[tokio::test] async fn multicall_tx_input_output() -> Result<()> { let [wallet_1, wallet_2, wallet_3] = launch_custom_provider_and_get_wallets( WalletsConfig::new(Some(3), Some(10), Some(1000)), None, None, ) .await? .try_into() .unwrap(); abigen!(Contract( name = "TxContract", abi = "e2e/sway/contracts/tx_input_output/out/release/tx_input_output-abi.json" )); let contract_binary = "sway/contracts/tx_input_output/out/release/tx_input_output.bin"; let get_contract_instance = |owner: Address| { let wallet_for_fees = wallet_3.clone(); async move { let configurables = TxContractConfigurables::default().with_OWNER(owner)?; let contract = Contract::load_from( contract_binary, LoadConfiguration::default().with_configurables(configurables), )?; let contract_id = contract .deploy_if_not_exists(&wallet_for_fees, TxPolicies::default()) .await? .contract_id; fuels::types::errors::Result::<_>::Ok(TxContract::new(contract_id, wallet_for_fees)) } }; // Set `wallet_1` as owner let contract_instance_1 = get_contract_instance(wallet_1.address()).await?; // Set `wallet_2` as owner let contract_instance_2 = get_contract_instance(wallet_2.address()).await?; let asset_id = AssetId::zeroed(); { let custom_input = wallet_1 .get_asset_inputs_for_amount(asset_id, 10, None) .await? .pop() .unwrap(); // Input at first position is a coin owned by wallet_1 let ch1 = contract_instance_1 .methods() .check_input(0) .with_inputs(vec![custom_input]) .add_signer(wallet_1.signer().clone()); let custom_input = wallet_2 .get_asset_inputs_for_amount(asset_id, 10, None) .await? .pop() .unwrap(); // As inputs follow the order off calls added to the multicall, // we need to check the second input in this call let ch2 = contract_instance_2 .methods() .check_input(1) .with_inputs(vec![custom_input]) .add_signer(wallet_2.signer().clone()); let multi_call_handler = CallHandler::new_multi_call(wallet_3.clone()) .add_call(ch1) .add_call(ch2); let _: ((), ()) = multi_call_handler.call().await?.value; } { let custom_input = wallet_1 .get_asset_inputs_for_amount(asset_id, 10, None) .await? .pop() .unwrap(); // Input at first position is a coin owned by wallet_1 let ch1 = contract_instance_1 .methods() .check_input(0) .with_inputs(vec![custom_input]) .add_signer(wallet_1.signer().clone()); // This call will read the wrong input and return an error let ch2 = contract_instance_2.methods().check_input(0); let multi_call_handler = CallHandler::new_multi_call(wallet_3.clone()) .add_call(ch1) .add_call(ch2); let err = multi_call_handler.call::<((), ())>().await.unwrap_err(); assert!(err.to_string().contains("wrong owner")); } { let custom_output = Output::change(wallet_1.address(), 0, asset_id); // Output at first position is change to wallet_1 let ch1 = contract_instance_1 .methods() .check_output_is_change(0) .with_outputs(vec![custom_output]); // This call will read the wrong output and return an error let ch2 = contract_instance_2.methods().check_output_is_change(0); let multi_call_handler = CallHandler::new_multi_call(wallet_3.clone()) .add_call(ch1) .add_call(ch2); let err = multi_call_handler.call::<((), ())>().await.unwrap_err(); assert!(err.to_string().contains("wrong change address")); } Ok(()) } #[tokio::test] async fn test_returned_method_descriptors_are_valid() -> Result<()> { abigen!(Contract( name = "MyContract", abi = "e2e/sway/contracts/token_ops/out/release/token_ops-abi.json" )); assert_eq!( MyContract::METHODS.burn_coins().fn_selector(), encode_fn_selector("burn_coins") ); assert_eq!( MyContract::METHODS.get_balance().fn_selector(), encode_fn_selector("get_balance") ); assert_eq!( MyContract::METHODS.get_msg_amount().fn_selector(), encode_fn_selector("get_msg_amount") ); assert_eq!( MyContract::METHODS.mint_coins().fn_selector(), encode_fn_selector("mint_coins") ); assert_eq!( MyContract::METHODS.mint_to_addresses().fn_selector(), encode_fn_selector("mint_to_addresses") ); assert_eq!( MyContract::METHODS.send_message().fn_selector(), encode_fn_selector("send_message") ); assert_eq!( MyContract::METHODS.transfer().fn_selector(), encode_fn_selector("transfer") ); assert_eq!(MyContract::METHODS.iter().len(), 7); Ok(()) } ================================================ FILE: e2e/tests/debug_utils.rs ================================================ use fuels::{ core::{ codec::{ABIEncoder, ABIFormatter}, traits::Tokenizable, }, prelude::*, programs::{debug::ScriptType, executable::Executable}, }; #[tokio::test] async fn can_debug_single_call_tx() -> Result<()> { setup_program_test!( Wallets("wallet"), Abigen(Contract( name = "MyContract", project = "e2e/sway/types/contracts/nested_structs" )) ); let contract_id = Contract::load_from( "sway/types/contracts/nested_structs/out/release/nested_structs.bin", Default::default(), )? .contract_id(); let call_handler = MyContract::new(contract_id, wallet) .methods() .check_struct_integrity(AllStruct { some_struct: SomeStruct { field: 2, field_2: true, }, }); let abi = std::fs::read_to_string( "./sway/types/contracts/nested_structs/out/release/nested_structs-abi.json", ) .unwrap(); let decoder = ABIFormatter::from_json_abi(&abi)?; // without gas forwarding { let tb = call_handler .clone() .call_params(CallParameters::default().with_amount(10)) .unwrap() .transaction_builder() .await .unwrap(); let script = tb.script; let script_data = tb.script_data; let ScriptType::ContractCall(call_descriptions) = ScriptType::detect(&script, &script_data)? else { panic!("expected a contract call") }; assert_eq!(call_descriptions.len(), 1); let call_description = &call_descriptions[0]; assert_eq!(call_description.contract_id, contract_id); assert_eq!(call_description.amount, 10); assert_eq!(call_description.asset_id, AssetId::default()); assert_eq!( call_description.decode_fn_selector().unwrap(), "check_struct_integrity" ); assert!(call_description.gas_forwarded.is_none()); assert_eq!( decoder.decode_fn_args( &call_description.decode_fn_selector().unwrap(), call_description.encoded_args.as_slice() )?, vec!["AllStruct { some_struct: SomeStruct { field: 2, field_2: true } }"] ); } // with gas forwarding { let tb = call_handler .clone() .call_params( CallParameters::default() .with_amount(10) .with_gas_forwarded(20), ) .unwrap() .transaction_builder() .await .unwrap(); let script = tb.script; let script_data = tb.script_data; let ScriptType::ContractCall(call_descriptions) = ScriptType::detect(&script, &script_data)? else { panic!("expected a contract call") }; assert_eq!(call_descriptions.len(), 1); let call_description = &call_descriptions[0]; assert_eq!(call_description.contract_id, contract_id); assert_eq!(call_description.amount, 10); assert_eq!(call_description.asset_id, AssetId::default()); assert_eq!( call_description.decode_fn_selector().unwrap(), "check_struct_integrity" ); assert_eq!(call_description.gas_forwarded, Some(20)); assert_eq!( decoder.decode_fn_args( &call_description.decode_fn_selector().unwrap(), call_description.encoded_args.as_slice() )?, vec!["AllStruct { some_struct: SomeStruct { field: 2, field_2: true } }"] ); } Ok(()) } #[tokio::test] async fn can_debug_multi_call_tx() -> Result<()> { setup_program_test!( Wallets("wallet"), Abigen(Contract( name = "MyContract", project = "e2e/sway/types/contracts/nested_structs" )) ); let contract_id = Contract::load_from( "sway/types/contracts/nested_structs/out/release/nested_structs.bin", Default::default(), )? .contract_id(); let call1 = MyContract::new(contract_id, wallet.clone()) .methods() .check_struct_integrity(AllStruct { some_struct: SomeStruct { field: 2, field_2: true, }, }); let call2 = MyContract::new(contract_id, wallet.clone()) .methods() .i_am_called_differently( AllStruct { some_struct: SomeStruct { field: 2, field_2: true, }, }, MemoryAddress { contract_id, function_selector: 123, function_data: 456, }, ); let abi = std::fs::read_to_string( "./sway/types/contracts/nested_structs/out/release/nested_structs-abi.json", ) .unwrap(); let decoder = ABIFormatter::from_json_abi(&abi)?; // without gas forwarding { let first_call = call1 .clone() .call_params(CallParameters::default().with_amount(10)) .unwrap(); let second_call = call2 .clone() .call_params(CallParameters::default().with_amount(20)) .unwrap(); let tb = CallHandler::new_multi_call(wallet.clone()) .add_call(first_call) .add_call(second_call) .transaction_builder() .await .unwrap(); let script = tb.script; let script_data = tb.script_data; let ScriptType::ContractCall(call_descriptions) = ScriptType::detect(&script, &script_data)? else { panic!("expected a contract call") }; assert_eq!(call_descriptions.len(), 2); let call_description = &call_descriptions[0]; assert_eq!(call_description.contract_id, contract_id); assert_eq!(call_description.amount, 10); assert_eq!(call_description.asset_id, AssetId::default()); assert_eq!( call_description.decode_fn_selector().unwrap(), "check_struct_integrity" ); assert!(call_description.gas_forwarded.is_none()); assert_eq!( decoder.decode_fn_args( &call_description.decode_fn_selector().unwrap(), call_description.encoded_args.as_slice() )?, vec!["AllStruct { some_struct: SomeStruct { field: 2, field_2: true } }"] ); let call_description = &call_descriptions[1]; let fn_selector = call_description.decode_fn_selector().unwrap(); assert_eq!(call_description.contract_id, contract_id); assert_eq!(call_description.amount, 20); assert_eq!(call_description.asset_id, AssetId::default()); assert_eq!(fn_selector, "i_am_called_differently"); assert!(call_description.gas_forwarded.is_none()); assert_eq!( decoder.decode_fn_args(&fn_selector, call_description.encoded_args.as_slice())?, vec![ "AllStruct { some_struct: SomeStruct { field: 2, field_2: true } }".to_string(), format!( "MemoryAddress {{ contract_id: std::contract_id::ContractId {{ bits: Bits256({:?}) }}, function_selector: 123, function_data: 456 }}", contract_id.as_slice() ) ] ); } // with gas forwarding { let first_call = call1 .clone() .call_params( CallParameters::default() .with_amount(10) .with_gas_forwarded(15), ) .unwrap(); let second_call = call2 .clone() .call_params( CallParameters::default() .with_amount(20) .with_gas_forwarded(25), ) .unwrap(); let tb = CallHandler::new_multi_call(wallet.clone()) .add_call(first_call) .add_call(second_call) .transaction_builder() .await .unwrap(); let script = tb.script; let script_data = tb.script_data; let ScriptType::ContractCall(call_descriptions) = ScriptType::detect(&script, &script_data)? else { panic!("expected a contract call") }; assert_eq!(call_descriptions.len(), 2); let call_description = &call_descriptions[0]; assert_eq!(call_description.contract_id, contract_id); assert_eq!(call_description.amount, 10); assert_eq!(call_description.asset_id, AssetId::default()); assert_eq!( call_description.decode_fn_selector().unwrap(), "check_struct_integrity" ); assert_eq!(call_description.gas_forwarded, Some(15)); assert_eq!( decoder.decode_fn_args( &call_description.decode_fn_selector().unwrap(), call_description.encoded_args.as_slice() )?, vec!["AllStruct { some_struct: SomeStruct { field: 2, field_2: true } }"] ); let call_description = &call_descriptions[1]; assert_eq!(call_description.contract_id, contract_id); assert_eq!(call_description.amount, 20); assert_eq!(call_description.asset_id, AssetId::default()); assert_eq!( call_description.decode_fn_selector().unwrap(), "i_am_called_differently" ); assert_eq!(call_description.gas_forwarded, Some(25)); assert_eq!( decoder.decode_fn_args( &call_description.decode_fn_selector().unwrap(), call_description.encoded_args.as_slice() )?, vec![ "AllStruct { some_struct: SomeStruct { field: 2, field_2: true } }".to_string(), format!( "MemoryAddress {{ contract_id: std::contract_id::ContractId {{ bits: Bits256({:?}) }}, function_selector: 123, function_data: 456 }}", contract_id.as_slice() ) ] ); } Ok(()) } #[tokio::test] async fn can_debug_sway_script() -> Result<()> { setup_program_test!( Wallets("wallet"), Abigen(Script( name = "MyScript", project = "e2e/sway/scripts/script_struct" )), LoadScript( name = "script_instance", script = "MyScript", wallet = "wallet" ) ); let tb = script_instance .main(MyStruct { number: 10, boolean: false, }) .transaction_builder() .await .unwrap(); let abi = std::fs::read_to_string("./sway/scripts/script_struct/out/release/script_struct-abi.json")?; let decoder = ABIFormatter::from_json_abi(abi)?; let ScriptType::Other(desc) = ScriptType::detect(&tb.script, &tb.script_data).unwrap() else { panic!("expected a script") }; assert_eq!( decoder.decode_fn_args("main", desc.data.as_slice())?, vec!["MyStruct { number: 10, boolean: false }"] ); assert_eq!( decoder .decode_configurables(desc.data_section().unwrap()) .unwrap(), vec![ ("A_NUMBER".to_owned(), "11".to_owned()), ( "MY_STRUCT".to_owned(), "MyStruct { number: 10, boolean: true }".to_owned() ), ] ); Ok(()) } #[tokio::test] async fn debugs_sway_script_with_no_configurables() -> Result<()> { setup_program_test!( Wallets("wallet"), Abigen(Script( name = "MyScript", project = "e2e/sway/scripts/basic_script" )), LoadScript( name = "script_instance", script = "MyScript", wallet = "wallet" ) ); let tb = script_instance .main(10, 11) .transaction_builder() .await .unwrap(); let ScriptType::Other(desc) = ScriptType::detect(&tb.script, &tb.script_data).unwrap() else { panic!("expected a script") }; assert!(desc.data_section().is_none()); Ok(()) } fn generate_modern_sway_binary(len: usize) -> Vec { assert!( len > 24, "needs at least 24B to fit in the indicator_of_modern_binary, data & configurables offsets" ); let mut custom_script = vec![0; len]; let indicator_of_modern_binary = fuel_asm::op::jmpf(0x00, 0x04); custom_script[4..8].copy_from_slice(&indicator_of_modern_binary.to_bytes()); custom_script } #[tokio::test] async fn data_section_offset_not_set_if_out_of_bounds() -> Result<()> { let mut custom_script = generate_modern_sway_binary(100); custom_script[16..24].copy_from_slice(&u64::MAX.to_be_bytes()); let ScriptType::Other(desc) = ScriptType::detect(&custom_script, &[]).unwrap() else { panic!("expected a script") }; assert!(desc.data_section_offset.is_none()); Ok(()) } #[tokio::test] async fn can_detect_a_loader_script_w_data_section() -> Result<()> { setup_program_test!(Abigen(Script( name = "MyScript", project = "e2e/sway/scripts/script_struct" ))); let script_data = ABIEncoder::default() .encode(&[MyStruct { number: 10, boolean: false, } .into_token()]) .unwrap(); let executable = Executable::load_from("sway/scripts/script_struct/out/release/script_struct.bin") .unwrap() .convert_to_loader() .unwrap(); let expected_blob_id = executable.blob().id(); let script = executable.code(); let ScriptType::Loader { script, blob_id } = ScriptType::detect(&script, &script_data).unwrap() else { panic!("expected a loader script") }; assert_eq!(blob_id, expected_blob_id); let decoder = ABIFormatter::from_json_abi(std::fs::read_to_string( "./sway/scripts/script_struct/out/release/script_struct-abi.json", )?)?; assert_eq!( decoder.decode_fn_args("main", script.data.as_slice())?, vec!["MyStruct { number: 10, boolean: false }"] ); assert_eq!( decoder .decode_configurables(script.data_section().unwrap()) .unwrap(), vec![ ("A_NUMBER".to_owned(), "11".to_owned()), ( "MY_STRUCT".to_owned(), "MyStruct { number: 10, boolean: true }".to_owned() ), ] ); Ok(()) } #[tokio::test] async fn can_detect_a_loader_script_wo_data_section() -> Result<()> { setup_program_test!(Abigen(Script( name = "MyScript", project = "e2e/sway/scripts/empty" ))); let executable = Executable::load_from("sway/scripts/empty/out/release/empty.bin") .unwrap() .convert_to_loader() .unwrap(); let expected_blob_id = executable.blob().id(); let script = executable.code(); let ScriptType::Loader { blob_id, .. } = ScriptType::detect(&script, &[]).unwrap() else { panic!("expected a loader script") }; assert_eq!(blob_id, expected_blob_id); Ok(()) } ================================================ FILE: e2e/tests/from_token.rs ================================================ use fuels::{core::traits::Tokenizable, prelude::*, types::Token}; #[tokio::test] async fn create_struct_from_decoded_tokens() -> Result<()> { abigen!(Contract( name = "SimpleContract", abi = "e2e/sway/types/contracts/nested_structs/out/release/nested_structs-abi.json" )); let u32_token = Token::U32(10); let bool_token = Token::Bool(true); let struct_from_tokens = SomeStruct::from_token(Token::Struct(vec![u32_token, bool_token]))?; assert_eq!(10, struct_from_tokens.field); assert!(struct_from_tokens.field_2); Ok(()) } #[tokio::test] async fn create_nested_struct_from_decoded_tokens() -> Result<()> { abigen!(Contract( name = "SimpleContract", abi = "e2e/sway/types/contracts/nested_structs/out/release/nested_structs-abi.json" )); let u32_token = Token::U32(10); let bool_token = Token::Bool(true); let inner_struct_token = Token::Struct(vec![u32_token, bool_token]); let nested_struct_from_tokens = AllStruct::from_token(Token::Struct(vec![inner_struct_token]))?; assert_eq!(10, nested_struct_from_tokens.some_struct.field); assert!(nested_struct_from_tokens.some_struct.field_2); Ok(()) } ================================================ FILE: e2e/tests/imports.rs ================================================ #[cfg(test)] mod tests { #[test] fn provides_output_type() { // test exists because we've excluded fuel_tx::Output twice #[allow(unused_imports)] use fuels::types::output::Output; } } ================================================ FILE: e2e/tests/logs.rs ================================================ use fuel_tx::SubAssetId; use fuels::{ core::codec::DecoderConfig, prelude::*, tx::ContractIdExt, types::{AsciiString, Bits256, SizedAsciiString, errors::transaction::Reason}, }; #[tokio::test] async fn parse_logged_variables() -> Result<()> { setup_program_test!( Wallets("wallet"), Abigen(Contract( name = "LogContract", project = "e2e/sway/logs/contract_logs" )), Deploy( name = "contract_instance", contract = "LogContract", wallet = "wallet", random_salt = false, ), ); // ANCHOR: produce_logs let contract_methods = contract_instance.methods(); let response = contract_methods.produce_logs_variables().call().await?; let log_u64 = response.decode_logs_with_type::()?; let log_bits256 = response.decode_logs_with_type::()?; let log_string = response.decode_logs_with_type::>()?; let log_array = response.decode_logs_with_type::<[u8; 3]>()?; let expected_bits256 = Bits256([ 239, 134, 175, 169, 105, 108, 240, 220, 99, 133, 226, 196, 7, 166, 225, 89, 161, 16, 60, 239, 183, 226, 174, 6, 54, 251, 51, 211, 203, 42, 158, 74, ]); assert_eq!(log_u64, vec![64]); assert_eq!(log_bits256, vec![expected_bits256]); assert_eq!(log_string, vec!["Fuel"]); assert_eq!(log_array, vec![[1, 2, 3]]); // ANCHOR_END: produce_logs Ok(()) } #[tokio::test] async fn parse_logs_values() -> Result<()> { setup_program_test!( Wallets("wallet"), Abigen(Contract( name = "LogContract", project = "e2e/sway/logs/contract_logs" )), Deploy( name = "contract_instance", contract = "LogContract", wallet = "wallet", random_salt = false, ), ); let contract_methods = contract_instance.methods(); let response = contract_methods.produce_logs_values().call().await?; let log_u64 = response.decode_logs_with_type::()?; let log_u32 = response.decode_logs_with_type::()?; let log_u16 = response.decode_logs_with_type::()?; let log_u8 = response.decode_logs_with_type::()?; // try to retrieve non existent log let log_nonexistent = response.decode_logs_with_type::()?; assert_eq!(log_u64, vec![64]); assert_eq!(log_u32, vec![32]); assert_eq!(log_u16, vec![16]); assert_eq!(log_u8, vec![8]); assert!(log_nonexistent.is_empty()); Ok(()) } #[tokio::test] async fn parse_logs_custom_types() -> Result<()> { setup_program_test!( Wallets("wallet"), Abigen(Contract( name = "LogContract", project = "e2e/sway/logs/contract_logs" )), Deploy( name = "contract_instance", contract = "LogContract", wallet = "wallet", random_salt = false, ), ); let contract_methods = contract_instance.methods(); let response = contract_methods.produce_logs_custom_types().call().await?; let log_test_struct = response.decode_logs_with_type::()?; let log_test_enum = response.decode_logs_with_type::()?; let log_tuple = response.decode_logs_with_type::<(TestStruct, TestEnum)>()?; let expected_bits256 = Bits256([ 239, 134, 175, 169, 105, 108, 240, 220, 99, 133, 226, 196, 7, 166, 225, 89, 161, 16, 60, 239, 183, 226, 174, 6, 54, 251, 51, 211, 203, 42, 158, 74, ]); let expected_struct = TestStruct { field_1: true, field_2: expected_bits256, field_3: 64, }; let expected_enum = TestEnum::VariantTwo; assert_eq!(log_test_struct, vec![expected_struct.clone()]); assert_eq!(log_test_enum, vec![expected_enum.clone()]); assert_eq!(log_tuple, vec![(expected_struct, expected_enum)]); Ok(()) } #[tokio::test] async fn parse_logs_generic_types() -> Result<()> { setup_program_test!( Wallets("wallet"), Abigen(Contract( name = "LogContract", project = "e2e/sway/logs/contract_logs" )), Deploy( name = "contract_instance", contract = "LogContract", wallet = "wallet", random_salt = false, ), ); let contract_methods = contract_instance.methods(); let response = contract_methods.produce_logs_generic_types().call().await?; let log_struct = response.decode_logs_with_type::>()?; let log_enum = response.decode_logs_with_type::>()?; let log_struct_nested = response.decode_logs_with_type::>>()?; let log_struct_deeply_nested = response.decode_logs_with_type::>, >>()?; let l = [1u8, 2u8, 3u8]; let expected_struct = StructWithGeneric { field_1: l, field_2: 64, }; let expected_enum = EnumWithGeneric::VariantOne(l); let expected_nested_struct = StructWithNestedGeneric { field_1: expected_struct.clone(), field_2: 64, }; let expected_deeply_nested_struct = StructDeeplyNestedGeneric { field_1: expected_nested_struct.clone(), field_2: 64, }; assert_eq!(log_struct, vec![expected_struct]); assert_eq!(log_enum, vec![expected_enum]); assert_eq!(log_struct_nested, vec![expected_nested_struct]); assert_eq!( log_struct_deeply_nested, vec![expected_deeply_nested_struct] ); Ok(()) } #[tokio::test] async fn decode_logs() -> Result<()> { setup_program_test!( Wallets("wallet"), Abigen(Contract( name = "LogContract", project = "e2e/sway/logs/contract_logs" )), Deploy( name = "contract_instance", contract = "LogContract", wallet = "wallet", random_salt = false, ), ); // ANCHOR: decode_logs let contract_methods = contract_instance.methods(); let response = contract_methods.produce_multiple_logs().call().await?; let logs = response.decode_logs(); // ANCHOR_END: decode_logs let expected_bits256 = Bits256([ 239, 134, 175, 169, 105, 108, 240, 220, 99, 133, 226, 196, 7, 166, 225, 89, 161, 16, 60, 239, 183, 226, 174, 6, 54, 251, 51, 211, 203, 42, 158, 74, ]); let expected_struct = TestStruct { field_1: true, field_2: expected_bits256, field_3: 64, }; let expected_enum = TestEnum::VariantTwo; let expected_generic_struct = StructWithGeneric { field_1: expected_struct.clone(), field_2: 64, }; let expected_logs: Vec = vec![ format!("{:?}", 64u64), format!("{:?}", 32u32), format!("{:?}", 16u16), format!("{:?}", 8u8), format!("{:?}", 64u64), format!("{expected_bits256:?}"), format!("{:?}", SizedAsciiString::<4>::new("Fuel".to_string())?), format!("{:?}", [1, 2, 3]), format!("{expected_struct:?}"), format!("{expected_enum:?}"), format!("{expected_generic_struct:?}"), ]; assert_eq!(expected_logs, logs.filter_succeeded()); Ok(()) } #[tokio::test] async fn decode_logs_with_no_logs() -> Result<()> { setup_program_test!( Wallets("wallet"), Abigen(Contract( name = "TestContract", project = "e2e/sway/contracts/contract_test" )), Deploy( name = "contract_instance", contract = "TestContract", wallet = "wallet", random_salt = false, ), ); let contract_methods = contract_instance.methods(); let logs = contract_methods .initialize_counter(42) .call() .await? .decode_logs(); assert!(logs.filter_succeeded().is_empty()); Ok(()) } #[tokio::test] async fn multi_call_log_single_contract() -> Result<()> { setup_program_test!( Wallets("wallet"), Abigen(Contract( name = "LogContract", project = "e2e/sway/logs/contract_logs" )), Deploy( name = "contract_instance", contract = "LogContract", wallet = "wallet", random_salt = false, ), ); let contract_methods = contract_instance.methods(); let call_handler_1 = contract_methods.produce_logs_values(); let call_handler_2 = contract_methods.produce_logs_variables(); let multi_call_handler = CallHandler::new_multi_call(wallet.clone()) .add_call(call_handler_1) .add_call(call_handler_2); let expected_logs: Vec = vec![ format!("{:?}", 64u64), format!("{:?}", 32u32), format!("{:?}", 16u16), format!("{:?}", 8u8), format!("{:?}", 64u64), format!( "{:?}", Bits256([ 239, 134, 175, 169, 105, 108, 240, 220, 99, 133, 226, 196, 7, 166, 225, 89, 161, 16, 60, 239, 183, 226, 174, 6, 54, 251, 51, 211, 203, 42, 158, 74, ]) ), format!("{:?}", SizedAsciiString::<4>::new("Fuel".to_string())?), format!("{:?}", [1, 2, 3]), ]; let logs = multi_call_handler.call::<((), ())>().await?.decode_logs(); assert_eq!(logs.filter_succeeded(), expected_logs); Ok(()) } #[tokio::test] async fn multi_call_log_multiple_contracts() -> Result<()> { setup_program_test!( Wallets("wallet"), Abigen(Contract( name = "LogContract", project = "e2e/sway/logs/contract_logs" )), Deploy( name = "contract_instance", contract = "LogContract", wallet = "wallet", random_salt = false, ), Deploy( name = "contract_instance2", contract = "LogContract", wallet = "wallet", random_salt = false, ), ); let call_handler_1 = contract_instance.methods().produce_logs_values(); let call_handler_2 = contract_instance2.methods().produce_logs_variables(); let multi_call_handler = CallHandler::new_multi_call(wallet.clone()) .add_call(call_handler_1) .add_call(call_handler_2); let expected_logs: Vec = vec![ format!("{:?}", 64u64), format!("{:?}", 32u32), format!("{:?}", 16u16), format!("{:?}", 8u8), format!("{:?}", 64u64), format!( "{:?}", Bits256([ 239, 134, 175, 169, 105, 108, 240, 220, 99, 133, 226, 196, 7, 166, 225, 89, 161, 16, 60, 239, 183, 226, 174, 6, 54, 251, 51, 211, 203, 42, 158, 74, ]) ), format!("{:?}", SizedAsciiString::<4>::new("Fuel".to_string())?), format!("{:?}", [1, 2, 3]), ]; let logs = multi_call_handler.call::<((), ())>().await?.decode_logs(); assert_eq!(logs.filter_succeeded(), expected_logs); Ok(()) } #[tokio::test] async fn multi_call_contract_with_contract_logs() -> Result<()> { setup_program_test!( Wallets("wallet"), Abigen( Contract(name = "MyContract", project = "e2e/sway/logs/contract_logs"), Contract( name = "ContractCaller", project = "e2e/sway/logs/contract_with_contract_logs" ) ), Deploy( name = "contract_instance", contract = "MyContract", wallet = "wallet", random_salt = false, ), Deploy( name = "contract_caller_instance", contract = "ContractCaller", wallet = "wallet", random_salt = false, ), Deploy( name = "contract_caller_instance2", contract = "ContractCaller", wallet = "wallet", random_salt = false, ), ); let call_handler_1 = contract_caller_instance .methods() .logs_from_external_contract(contract_instance.id()) .with_contracts(&[&contract_instance]); let call_handler_2 = contract_caller_instance2 .methods() .logs_from_external_contract(contract_instance.id()) .with_contracts(&[&contract_instance]); let multi_call_handler = CallHandler::new_multi_call(wallet.clone()) .add_call(call_handler_1) .add_call(call_handler_2); let expected_logs: Vec = vec![ format!("{:?}", 64), format!("{:?}", 32), format!("{:?}", 16), format!("{:?}", 8), format!("{:?}", 64), format!("{:?}", 32), format!("{:?}", 16), format!("{:?}", 8), ]; let logs = multi_call_handler.call::<((), ())>().await?.decode_logs(); assert_eq!(logs.filter_succeeded(), expected_logs); Ok(()) } fn assert_revert_containing_msg(msg: &str, error: Error) { let Error::Transaction(Reason::Failure { reason, .. }) = error else { panic!("error does not have the transaction failure variant"); }; assert!( reason.contains(msg), "message: \"{msg}\" not contained in reason: \"{reason}\"" ); } #[tokio::test] async fn revert_logs() -> Result<()> { setup_program_test!( Wallets("wallet"), Abigen(Contract( name = "RevertLogsContract", project = "e2e/sway/logs/contract_revert_logs" )), Deploy( name = "contract_instance", contract = "RevertLogsContract", wallet = "wallet", random_salt = false, ), ); macro_rules! reverts_with_msg { ($method:ident, call, $msg:expr) => { let error = contract_instance .methods() .$method() .call() .await .expect_err("should return a revert error"); assert_revert_containing_msg($msg, error); }; ($method:ident, simulate, $msg:expr) => { let error = contract_instance .methods() .$method() .simulate(Execution::realistic()) .await .expect_err("should return a revert error"); assert_revert_containing_msg($msg, error); }; } { reverts_with_msg!(require_primitive, call, "42"); reverts_with_msg!(require_primitive, simulate, "42"); reverts_with_msg!(require_string, call, "fuel"); reverts_with_msg!(require_string, simulate, "fuel"); reverts_with_msg!(require_custom_generic, call, "StructDeeplyNestedGeneric"); reverts_with_msg!( require_custom_generic, simulate, "StructDeeplyNestedGeneric" ); reverts_with_msg!(require_with_additional_logs, call, "64"); reverts_with_msg!(require_with_additional_logs, simulate, "64"); } { reverts_with_msg!(rev_w_log_primitive, call, "42"); reverts_with_msg!(rev_w_log_primitive, simulate, "42"); reverts_with_msg!(rev_w_log_string, call, "fuel"); reverts_with_msg!(rev_w_log_string, simulate, "fuel"); reverts_with_msg!(rev_w_log_custom_generic, call, "StructDeeplyNestedGeneric"); reverts_with_msg!( rev_w_log_custom_generic, simulate, "StructDeeplyNestedGeneric" ); } Ok(()) } #[tokio::test] async fn multi_call_revert_logs_single_contract() -> Result<()> { setup_program_test!( Wallets("wallet"), Abigen(Contract( name = "RevertLogsContract", project = "e2e/sway/logs/contract_revert_logs" )), Deploy( name = "contract_instance", contract = "RevertLogsContract", wallet = "wallet", random_salt = false, ), ); let contract_methods = contract_instance.methods(); // The output of the error depends on the order of the contract // handlers as the script returns the first revert it finds. { let call_handler_1 = contract_methods.require_string(); let call_handler_2 = contract_methods.rev_w_log_custom_generic(); let mut multi_call_handler = CallHandler::new_multi_call(wallet.clone()) .add_call(call_handler_1) .add_call(call_handler_2); let error = multi_call_handler .simulate::<((), ())>(Execution::realistic()) .await .expect_err("should return a revert error"); assert_revert_containing_msg("fuel", error); let error = multi_call_handler .call::<((), ())>() .await .expect_err("should return a revert error"); assert_revert_containing_msg("fuel", error); } { let call_handler_1 = contract_methods.require_custom_generic(); let call_handler_2 = contract_methods.rev_w_log_string(); let mut multi_call_handler = CallHandler::new_multi_call(wallet.clone()) .add_call(call_handler_1) .add_call(call_handler_2); let error = multi_call_handler .simulate::<((), ())>(Execution::realistic()) .await .expect_err("should return a revert error"); assert_revert_containing_msg("StructDeeplyNestedGeneric", error); let error = multi_call_handler .call::<((), ())>() .await .expect_err("should return a revert error"); assert_revert_containing_msg("StructDeeplyNestedGeneric", error); } Ok(()) } #[tokio::test] async fn multi_call_revert_logs_multi_contract() -> Result<()> { setup_program_test!( Wallets("wallet"), Abigen(Contract( name = "RevertLogsContract", project = "e2e/sway/logs/contract_revert_logs" )), Deploy( name = "contract_instance", contract = "RevertLogsContract", wallet = "wallet", random_salt = false, ), Deploy( name = "contract_instance2", contract = "RevertLogsContract", wallet = "wallet", random_salt = false, ), ); let contract_methods = contract_instance.methods(); let contract_methods2 = contract_instance2.methods(); // The output of the error depends on the order of the contract // handlers as the script returns the first revert it finds. { let call_handler_1 = contract_methods.require_string(); let call_handler_2 = contract_methods2.rev_w_log_custom_generic(); let mut multi_call_handler = CallHandler::new_multi_call(wallet.clone()) .add_call(call_handler_1) .add_call(call_handler_2); let error = multi_call_handler .simulate::<((), ())>(Execution::realistic()) .await .expect_err("should return a revert error"); assert_revert_containing_msg("fuel", error); let error = multi_call_handler .call::<((), ())>() .await .expect_err("should return a revert error"); assert_revert_containing_msg("fuel", error); } { let call_handler_1 = contract_methods2.require_custom_generic(); let call_handler_2 = contract_methods.rev_w_log_string(); let mut multi_call_handler = CallHandler::new_multi_call(wallet.clone()) .add_call(call_handler_1) .add_call(call_handler_2); let error = multi_call_handler .simulate::<((), ())>(Execution::realistic()) .await .expect_err("should return a revert error"); assert_revert_containing_msg("StructDeeplyNestedGeneric", error); let error = multi_call_handler .call::<((), ())>() .await .expect_err("should return a revert error"); assert_revert_containing_msg("StructDeeplyNestedGeneric", error); } Ok(()) } #[tokio::test] #[allow(unused_variables)] async fn script_decode_logs() -> Result<()> { // ANCHOR: script_logs abigen!(Script( name = "LogScript", abi = "e2e/sway/logs/script_logs/out/release/script_logs-abi.json" )); let wallet = launch_provider_and_get_wallet().await?; let bin_path = "sway/logs/script_logs/out/release/script_logs.bin"; let instance = LogScript::new(wallet.clone(), bin_path); let response = instance.main().call().await?; let logs = response.decode_logs(); let log_u64 = response.decode_logs_with_type::()?; // ANCHOR_END: script_logs let l = [1u8, 2u8, 3u8]; let expected_bits256 = Bits256([ 239, 134, 175, 169, 105, 108, 240, 220, 99, 133, 226, 196, 7, 166, 225, 89, 161, 16, 60, 239, 183, 226, 174, 6, 54, 251, 51, 211, 203, 42, 158, 74, ]); let expected_struct = TestStruct { field_1: true, field_2: expected_bits256, field_3: 64, }; let expected_enum = TestEnum::VariantTwo; let expected_tuple = (expected_struct.clone(), expected_enum.clone()); let expected_generic_struct = StructWithGeneric { field_1: expected_struct.clone(), field_2: 64, }; let expected_generic_enum = EnumWithGeneric::VariantOne(l); let expected_nested_struct = StructWithNestedGeneric { field_1: expected_generic_struct.clone(), field_2: 64, }; let expected_deeply_nested_struct = StructDeeplyNestedGeneric { field_1: expected_nested_struct.clone(), field_2: 64, }; let expected_logs: Vec = vec![ format!("{:?}", 128u64), format!("{:?}", 32u32), format!("{:?}", 16u16), format!("{:?}", 8u8), format!("{:?}", 64u64), format!("{expected_bits256:?}"), format!("{:?}", SizedAsciiString::<4>::new("Fuel".to_string())?), format!("{:?}", [1, 2, 3]), format!("{expected_struct:?}"), format!("{expected_enum:?}"), format!("{expected_tuple:?}"), format!("{expected_generic_struct:?}"), format!("{expected_generic_enum:?}"), format!("{expected_nested_struct:?}"), format!("{expected_deeply_nested_struct:?}"), ]; assert_eq!(logs.filter_succeeded(), expected_logs); Ok(()) } #[tokio::test] async fn contract_with_contract_logs() -> Result<()> { setup_program_test!( Wallets("wallet"), Abigen( Contract(name = "MyContract", project = "e2e/sway/logs/contract_logs",), Contract( name = "ContractCaller", project = "e2e/sway/logs/contract_with_contract_logs", ) ), Deploy( name = "contract_instance", contract = "MyContract", wallet = "wallet", random_salt = false, ), Deploy( name = "contract_caller_instance", contract = "ContractCaller", wallet = "wallet", random_salt = false, ) ); let expected_logs: Vec = vec![ format!("{:?}", 64), format!("{:?}", 32), format!("{:?}", 16), format!("{:?}", 8), ]; let logs = contract_caller_instance .methods() .logs_from_external_contract(contract_instance.id()) .with_contracts(&[&contract_instance]) .call() .await? .decode_logs(); assert_eq!(expected_logs, logs.filter_succeeded()); Ok(()) } #[tokio::test] #[allow(unused_variables)] async fn script_logs_with_contract_logs() -> Result<()> { setup_program_test!( Wallets("wallet"), Abigen( Contract(name = "MyContract", project = "e2e/sway/logs/contract_logs",), Script( name = "LogScript", project = "e2e/sway/logs/script_with_contract_logs" ) ), Deploy( name = "contract_instance", contract = "MyContract", wallet = "wallet", random_salt = false, ), LoadScript( name = "script_instance", script = "LogScript", wallet = "wallet" ) ); let expected_num_contract_logs = 4; let expected_script_logs: Vec = vec![ // Contract logs format!("{:?}", 64), format!("{:?}", 32), format!("{:?}", 16), format!("{:?}", 8), // Script logs format!("{:?}", true), format!("{:?}", 42), format!("{:?}", SizedAsciiString::<4>::new("Fuel".to_string())?), format!("{:?}", [1, 2, 3]), ]; // ANCHOR: instance_to_contract_id let contract_id: ContractId = contract_instance.contract_id(); // ANCHOR_END: instance_to_contract_id // ANCHOR: external_contract_ids let response = script_instance .main(contract_id, MatchEnum::Logs) .with_contract_ids(&[contract_id]) .call() .await?; // ANCHOR_END: external_contract_ids // ANCHOR: external_contract let response = script_instance .main(contract_id, MatchEnum::Logs) .with_contracts(&[&contract_instance]) .call() .await?; // ANCHOR_END: external_contract { let num_contract_logs = response .tx_status .receipts .iter() .filter(|receipt| matches!(receipt, Receipt::LogData { id, .. } | Receipt::Log { id, .. } if *id == contract_id)) .count(); assert_eq!(num_contract_logs, expected_num_contract_logs); } { let logs = response.decode_logs(); assert_eq!(logs.filter_succeeded(), expected_script_logs); } Ok(()) } #[tokio::test] async fn script_decode_logs_with_type() -> Result<()> { setup_program_test!( Wallets("wallet"), Abigen(Script( name = "LogScript", project = "e2e/sway/logs/script_logs" )), LoadScript( name = "script_instance", script = "LogScript", wallet = "wallet" ) ); let response = script_instance.main().call().await?; let l = [1u8, 2u8, 3u8]; let expected_bits256 = Bits256([ 239, 134, 175, 169, 105, 108, 240, 220, 99, 133, 226, 196, 7, 166, 225, 89, 161, 16, 60, 239, 183, 226, 174, 6, 54, 251, 51, 211, 203, 42, 158, 74, ]); let expected_struct = TestStruct { field_1: true, field_2: expected_bits256, field_3: 64, }; let expected_enum = TestEnum::VariantTwo; let expected_generic_struct = StructWithGeneric { field_1: expected_struct.clone(), field_2: 64, }; let expected_generic_enum = EnumWithGeneric::VariantOne(l); let expected_nested_struct = StructWithNestedGeneric { field_1: expected_generic_struct.clone(), field_2: 64, }; let expected_deeply_nested_struct = StructDeeplyNestedGeneric { field_1: expected_nested_struct.clone(), field_2: 64, }; let log_u64 = response.decode_logs_with_type::()?; let log_u32 = response.decode_logs_with_type::()?; let log_u16 = response.decode_logs_with_type::()?; let log_u8 = response.decode_logs_with_type::()?; let log_struct = response.decode_logs_with_type::()?; let log_enum = response.decode_logs_with_type::()?; let log_generic_struct = response.decode_logs_with_type::>()?; let log_generic_enum = response.decode_logs_with_type::>()?; let log_nested_struct = response .decode_logs_with_type::>>()?; let log_deeply_nested_struct = response.decode_logs_with_type::>, >>()?; // try to retrieve non existent log let log_nonexistent = response.decode_logs_with_type::()?; assert_eq!(log_u64, vec![128, 64]); assert_eq!(log_u32, vec![32]); assert_eq!(log_u16, vec![16]); assert_eq!(log_u8, vec![8]); assert_eq!(log_struct, vec![expected_struct]); assert_eq!(log_enum, vec![expected_enum]); assert_eq!(log_generic_struct, vec![expected_generic_struct]); assert_eq!(log_generic_enum, vec![expected_generic_enum]); assert_eq!(log_nested_struct, vec![expected_nested_struct]); assert_eq!( log_deeply_nested_struct, vec![expected_deeply_nested_struct] ); assert!(log_nonexistent.is_empty()); Ok(()) } #[tokio::test] async fn script_require_log() -> Result<()> { setup_program_test!( Wallets("wallet"), Abigen(Script( name = "LogScript", project = "e2e/sway/logs/script_revert_logs" )), LoadScript( name = "script_instance", script = "LogScript", wallet = "wallet" ) ); macro_rules! reverts_with_msg { ($arg:expr, call, $msg:expr) => { let error = script_instance .main($arg) .call() .await .expect_err("should return a revert error"); assert_revert_containing_msg($msg, error); }; ($arg:expr, simulate, $msg:expr) => { let error = script_instance .main($arg) .simulate(Execution::realistic()) .await .expect_err("should return a revert error"); assert_revert_containing_msg($msg, error); }; } { reverts_with_msg!(MatchEnum::RequirePrimitive, call, "42"); reverts_with_msg!(MatchEnum::RequirePrimitive, simulate, "42"); reverts_with_msg!(MatchEnum::RequireString, call, "fuel"); reverts_with_msg!(MatchEnum::RequireString, simulate, "fuel"); reverts_with_msg!( MatchEnum::RequireCustomGeneric, call, "StructDeeplyNestedGeneric" ); reverts_with_msg!( MatchEnum::RequireCustomGeneric, simulate, "StructDeeplyNestedGeneric" ); reverts_with_msg!(MatchEnum::RequireWithAdditionalLogs, call, "64"); reverts_with_msg!(MatchEnum::RequireWithAdditionalLogs, simulate, "64"); } { reverts_with_msg!(MatchEnum::RevWLogPrimitive, call, "42"); reverts_with_msg!(MatchEnum::RevWLogPrimitive, simulate, "42"); reverts_with_msg!(MatchEnum::RevWLogString, call, "fuel"); reverts_with_msg!(MatchEnum::RevWLogString, simulate, "fuel"); reverts_with_msg!( MatchEnum::RevWLogCustomGeneric, call, "StructDeeplyNestedGeneric" ); reverts_with_msg!( MatchEnum::RevWLogCustomGeneric, simulate, "StructDeeplyNestedGeneric" ); } Ok(()) } #[tokio::test] async fn contract_require_from_contract() -> Result<()> { setup_program_test!( Wallets("wallet"), Abigen( Contract( name = "MyContract", project = "e2e/sway/contracts/lib_contract", ), Contract( name = "ContractCaller", project = "e2e/sway/contracts/lib_contract_caller", ) ), Deploy( name = "contract_instance", contract = "MyContract", wallet = "wallet", random_salt = false, ), Deploy( name = "contract_caller_instance", contract = "ContractCaller", wallet = "wallet", random_salt = false, ) ); let error = contract_caller_instance .methods() .require_from_contract(contract_instance.id()) .with_contracts(&[&contract_instance]) .call() .await .expect_err("should return a revert error"); assert_revert_containing_msg("require from contract", error); Ok(()) } #[tokio::test] async fn multi_call_contract_require_from_contract() -> Result<()> { setup_program_test!( Wallets("wallet"), Abigen( Contract( name = "MyContract", project = "e2e/sway/contracts/lib_contract", ), Contract( name = "ContractLogs", project = "e2e/sway/logs/contract_logs", ), Contract( name = "ContractCaller", project = "e2e/sway/contracts/lib_contract_caller", ) ), Deploy( name = "lib_contract_instance", contract = "MyContract", wallet = "wallet", random_salt = false, ), Deploy( name = "contract_instance", contract = "ContractLogs", wallet = "wallet", random_salt = false, ), Deploy( name = "contract_caller_instance", contract = "ContractCaller", wallet = "wallet", random_salt = false, ), ); let call_handler_1 = contract_instance.methods().produce_logs_values(); let call_handler_2 = contract_caller_instance .methods() .require_from_contract(lib_contract_instance.id()) .with_contracts(&[&lib_contract_instance]); let multi_call_handler = CallHandler::new_multi_call(wallet.clone()) .add_call(call_handler_1) .add_call(call_handler_2); let error = multi_call_handler .call::<((), ())>() .await .expect_err("should return a revert error"); assert_revert_containing_msg("require from contract", error); Ok(()) } #[tokio::test] async fn script_require_from_contract() -> Result<()> { setup_program_test!( Wallets("wallet"), Abigen( Contract( name = "MyContract", project = "e2e/sway/contracts/lib_contract", ), Script( name = "LogScript", project = "e2e/sway/scripts/require_from_contract" ) ), Deploy( name = "contract_instance", contract = "MyContract", wallet = "wallet", random_salt = false, ), LoadScript( name = "script_instance", script = "LogScript", wallet = "wallet" ) ); let error = script_instance .main(contract_instance.id()) .with_contracts(&[&contract_instance]) .call() .await .expect_err("should return a revert error"); assert_revert_containing_msg("require from contract", error); Ok(()) } #[tokio::test] async fn loader_script_require_from_loader_contract() -> Result<()> { setup_program_test!( Wallets("wallet"), Abigen( Contract( name = "MyContract", project = "e2e/sway/contracts/lib_contract", ), Script( name = "LogScript", project = "e2e/sway/scripts/require_from_contract" ) ), LoadScript( name = "script_instance", script = "LogScript", wallet = "wallet" ) ); let contract_binary = "sway/contracts/lib_contract/out/release/lib_contract.bin"; let contract = Contract::load_from(contract_binary, LoadConfiguration::default())?; let contract_id = contract .convert_to_loader(100_000)? .deploy_if_not_exists(&wallet, TxPolicies::default()) .await? .contract_id; let contract_instance = MyContract::new(contract_id, wallet); let mut script_instance = script_instance; script_instance.convert_into_loader().await?; let error = script_instance .main(contract_instance.id()) .with_contracts(&[&contract_instance]) .call() .await .expect_err("should return a revert error"); assert_revert_containing_msg("require from contract", error); Ok(()) } fn assert_assert_eq_containing_msg(left: T, right: T, error: Error) { let msg = format!( "assertion failed: `(left == right)`\n left: `\"{left:?}\"`\n right: `\"{right:?}\"`" ); assert_revert_containing_msg(&msg, error) } fn assert_assert_ne_containing_msg(left: T, right: T, error: Error) { let msg = format!( "assertion failed: `(left != right)`\n left: `\"{left:?}\"`\n right: `\"{right:?}\"`" ); assert_revert_containing_msg(&msg, error) } #[tokio::test] async fn contract_asserts_log() -> Result<()> { setup_program_test!( Wallets("wallet"), Abigen(Contract( name = "LogContract", project = "e2e/sway/contracts/asserts" )), Deploy( name = "contract_instance", contract = "LogContract", wallet = "wallet", random_salt = false, ), ); macro_rules! reverts_with_msg { (($($arg: expr,)*), $method:ident, call, $msg:expr) => { let error = contract_instance .methods() .$method($($arg,)*) .call() .await .expect_err("should return a revert error"); assert_revert_containing_msg($msg, error); }; (($($arg: expr,)*), $method:ident, simulate, $msg:expr) => { let error = contract_instance .methods() .$method($($arg,)*) .simulate(Execution::realistic()) .await .expect_err("should return a revert error"); assert_revert_containing_msg($msg, error); }; } { reverts_with_msg!((32, 64,), assert_primitive, call, "assertion failed"); reverts_with_msg!((32, 64,), assert_primitive, simulate, "assertion failed"); } macro_rules! reverts_with_assert_eq_msg { (($($arg: expr,)*), $method:ident, $execution: ident, $msg:expr) => { let error = contract_instance .methods() .$method($($arg,)*) .call() .await .expect_err("should return a revert error"); assert_assert_eq_containing_msg($($arg,)* error); } } { reverts_with_assert_eq_msg!((32, 64,), assert_eq_primitive, call, "assertion failed"); reverts_with_assert_eq_msg!((32, 64,), assert_eq_primitive, simulate, "assertion failed"); } { let test_struct = TestStruct { field_1: true, field_2: 64, }; let test_struct2 = TestStruct { field_1: false, field_2: 32, }; reverts_with_assert_eq_msg!( (test_struct.clone(), test_struct2.clone(),), assert_eq_struct, call, "assertion failed" ); reverts_with_assert_eq_msg!( (test_struct.clone(), test_struct2.clone(),), assert_eq_struct, simulate, "assertion failed" ); } { let test_enum = TestEnum::VariantOne; let test_enum2 = TestEnum::VariantTwo; reverts_with_assert_eq_msg!( (test_enum.clone(), test_enum2.clone(),), assert_eq_enum, call, "assertion failed" ); reverts_with_assert_eq_msg!( (test_enum.clone(), test_enum2.clone(),), assert_eq_enum, simulate, "assertion failed" ); } macro_rules! reverts_with_assert_ne_msg { (($($arg: expr,)*), $method:ident, $execution: ident, $msg:expr) => { let error = contract_instance .methods() .$method($($arg,)*) .call() .await .expect_err("should return a revert error"); assert_assert_ne_containing_msg($($arg,)* error); } } { reverts_with_assert_ne_msg!((32, 32,), assert_ne_primitive, call, "assertion failed"); reverts_with_assert_ne_msg!((32, 32,), assert_ne_primitive, simulate, "assertion failed"); } { let test_struct = TestStruct { field_1: true, field_2: 64, }; reverts_with_assert_ne_msg!( (test_struct.clone(), test_struct.clone(),), assert_ne_struct, call, "assertion failed" ); reverts_with_assert_ne_msg!( (test_struct.clone(), test_struct.clone(),), assert_ne_struct, simulate, "assertion failed" ); } { let test_enum = TestEnum::VariantOne; reverts_with_assert_ne_msg!( (test_enum.clone(), test_enum.clone(),), assert_ne_enum, call, "assertion failed" ); reverts_with_assert_ne_msg!( (test_enum.clone(), test_enum.clone(),), assert_ne_enum, simulate, "assertion failed" ); } Ok(()) } #[tokio::test] async fn script_asserts_log() -> Result<()> { setup_program_test!( Wallets("wallet"), Abigen(Script( name = "LogScript", project = "e2e/sway/scripts/script_asserts" )), LoadScript( name = "script_instance", script = "LogScript", wallet = "wallet" ) ); macro_rules! reverts_with_msg { ($arg:expr, call, $msg:expr) => { let error = script_instance .main($arg) .call() .await .expect_err("should return a revert error"); assert_revert_containing_msg($msg, error); }; ($arg:expr, simulate, $msg:expr) => { let error = script_instance .main($arg) .simulate(Execution::realistic()) .await .expect_err("should return a revert error"); assert_revert_containing_msg($msg, error); }; } macro_rules! reverts_with_assert_eq_ne_msg { ($arg:expr, call, $msg:expr) => { let error = script_instance .main($arg) .call() .await .expect_err("should return a revert error"); assert_revert_containing_msg($msg, error); }; ($arg:expr, simulate, $msg:expr) => { let error = script_instance .main($arg) .simulate(Execution::realistic()) .await .expect_err("should return a revert error"); assert_revert_containing_msg($msg, error); }; } { reverts_with_msg!( MatchEnum::AssertPrimitive((32, 64)), call, "assertion failed" ); reverts_with_msg!( MatchEnum::AssertPrimitive((32, 64)), simulate, "assertion failed" ); } { reverts_with_assert_eq_ne_msg!( MatchEnum::AssertEqPrimitive((32, 64)), call, "assertion failed: `(left == right)`" ); reverts_with_assert_eq_ne_msg!( MatchEnum::AssertEqPrimitive((32, 64)), simulate, "assertion failed: `(left == right)`" ); } { let test_struct = TestStruct { field_1: true, field_2: 64, }; let test_struct2 = TestStruct { field_1: false, field_2: 32, }; reverts_with_assert_eq_ne_msg!( MatchEnum::AssertEqStruct((test_struct.clone(), test_struct2.clone(),)), call, "assertion failed: `(left == right)`" ); reverts_with_assert_eq_ne_msg!( MatchEnum::AssertEqStruct((test_struct.clone(), test_struct2.clone(),)), simulate, "assertion failed: `(left == right)`" ); } { let test_enum = TestEnum::VariantOne; let test_enum2 = TestEnum::VariantTwo; reverts_with_assert_eq_ne_msg!( MatchEnum::AssertEqEnum((test_enum.clone(), test_enum2.clone(),)), call, "assertion failed: `(left == right)`" ); reverts_with_assert_eq_ne_msg!( MatchEnum::AssertEqEnum((test_enum.clone(), test_enum2.clone(),)), simulate, "assertion failed: `(left == right)`" ); } { reverts_with_assert_eq_ne_msg!( MatchEnum::AssertNePrimitive((32, 32)), call, "assertion failed: `(left != right)`" ); reverts_with_assert_eq_ne_msg!( MatchEnum::AssertNePrimitive((32, 32)), simulate, "assertion failed: `(left != right)`" ); } { let test_struct = TestStruct { field_1: true, field_2: 64, }; reverts_with_assert_eq_ne_msg!( MatchEnum::AssertNeStruct((test_struct.clone(), test_struct.clone(),)), call, "assertion failed: `(left != right)`" ); reverts_with_assert_eq_ne_msg!( MatchEnum::AssertNeStruct((test_struct.clone(), test_struct.clone(),)), simulate, "assertion failed: `(left != right)`" ); } { let test_enum = TestEnum::VariantOne; reverts_with_assert_eq_ne_msg!( MatchEnum::AssertNeEnum((test_enum.clone(), test_enum.clone(),)), call, "assertion failed: `(left != right)`" ); reverts_with_assert_eq_ne_msg!( MatchEnum::AssertNeEnum((test_enum.clone(), test_enum.clone(),)), simulate, "assertion failed: `(left != right)`" ); } Ok(()) } #[tokio::test] async fn contract_token_ops_error_messages() -> Result<()> { setup_program_test!( Wallets("wallet"), Abigen(Contract( name = "TestContract", project = "e2e/sway/contracts/token_ops" )), Deploy( name = "contract_instance", contract = "TestContract", wallet = "wallet", random_salt = false, ), ); let contract_methods = contract_instance.methods(); { let contract_id = contract_instance.contract_id(); let asset_id = contract_id.asset_id(&SubAssetId::zeroed()); let address = wallet.address(); let error = contract_methods .transfer(1_000_000, asset_id, address.into()) .simulate(Execution::realistic()) .await .expect_err("should return a revert error"); assert_revert_containing_msg("failed transfer to address", error); let error = contract_methods .transfer(1_000_000, asset_id, address.into()) .call() .await .expect_err("should return a revert error"); assert_revert_containing_msg("failed transfer to address", error); } Ok(()) } #[tokio::test] async fn log_results() -> Result<()> { setup_program_test!( Wallets("wallet"), Abigen(Contract( name = "MyContract", project = "e2e/sway/logs/contract_logs" ),), Deploy( contract = "MyContract", name = "contract_instance", wallet = "wallet", random_salt = false, ) ); let response = contract_instance .methods() .produce_bad_logs() .call() .await?; let log = response.decode_logs(); let expected_err = format!( "codec: missing log formatter for log_id: `LogId({:?}, \"128\")`, data: `{:?}`. \ Consider adding external contracts using `with_contracts()`", contract_instance.id(), [0u8; 8] ); let succeeded = log.filter_succeeded(); let failed = log.filter_failed(); assert_eq!(succeeded, vec!["123".to_string()]); assert_eq!(failed.first().unwrap().to_string(), expected_err); Ok(()) } #[tokio::test] async fn can_configure_decoder_for_contract_log_decoding() -> Result<()> { setup_program_test!( Wallets("wallet"), Abigen(Contract( name = "MyContract", project = "e2e/sway/contracts/needs_custom_decoder" ),), Deploy( contract = "MyContract", name = "contract_instance", wallet = "wallet", random_salt = false, ) ); let methods = contract_instance.methods(); { // Single call: decoding with too low max_tokens fails let response = methods .i_log_a_1k_el_array() .with_decoder_config(DecoderConfig { max_tokens: 100, ..Default::default() }) .call() .await?; response.decode_logs_with_type::<[u8; 1000]>().expect_err( "Should have failed since there are more tokens than what is supported by default.", ); let logs = response.decode_logs(); assert!( !logs.filter_failed().is_empty(), "Should have had failed to decode logs since there are more tokens than what is supported by default" ); } { // Single call: increasing limits makes the test pass let response = methods .i_log_a_1k_el_array() .with_decoder_config(DecoderConfig { max_tokens: 1001, ..Default::default() }) .call() .await?; let logs = response.decode_logs_with_type::<[u8; 1000]>()?; assert_eq!(logs, vec![[0u8; 1000]]); let logs = response.decode_logs(); assert!(!logs.filter_succeeded().is_empty()); } { // Multi call: decoding with too low max_tokens will fail let response = CallHandler::new_multi_call(wallet.clone()) .add_call(methods.i_log_a_1k_el_array()) .with_decoder_config(DecoderConfig { max_tokens: 100, ..Default::default() }) .call::<((),)>() .await?; response.decode_logs_with_type::<[u8; 1000]>().expect_err( "should have failed since there are more tokens than what is supported by default", ); let logs = response.decode_logs(); assert!( !logs.filter_failed().is_empty(), "should have had failed to decode logs since there are more tokens than what is supported by default" ); } { // Multi call: increasing limits makes the test pass let response = CallHandler::new_multi_call(wallet.clone()) .add_call(methods.i_log_a_1k_el_array()) .with_decoder_config(DecoderConfig { max_tokens: 1001, ..Default::default() }) .call::<((),)>() .await?; let logs = response.decode_logs_with_type::<[u8; 1000]>()?; assert_eq!(logs, vec![[0u8; 1000]]); let logs = response.decode_logs(); assert!(!logs.filter_succeeded().is_empty()); } Ok(()) } #[tokio::test] async fn can_configure_decoder_for_script_log_decoding() -> Result<()> { setup_program_test!( Wallets("wallet"), Abigen(Script( name = "LogScript", project = "e2e/sway/scripts/script_needs_custom_decoder" )), LoadScript( name = "script_instance", script = "LogScript", wallet = "wallet" ) ); { // Cannot decode the produced log with too low max_tokens let response = script_instance .main(true) .with_decoder_config(DecoderConfig { max_tokens: 100, ..Default::default() }) .call() .await?; response .decode_logs_with_type::<[u8; 1000]>() .expect_err("Cannot decode the log with default decoder config"); let logs = response.decode_logs(); assert!(!logs.filter_failed().is_empty()) } { // When the token limit is bumped log decoding succeeds let response = script_instance .main(true) .with_decoder_config(DecoderConfig { max_tokens: 1001, ..Default::default() }) .call() .await?; let logs = response.decode_logs_with_type::<[u8; 1000]>()?; assert_eq!(logs, vec![[0u8; 1000]]); let logs = response.decode_logs(); assert!(!logs.filter_succeeded().is_empty()) } Ok(()) } #[tokio::test] async fn contract_heap_log() -> Result<()> { setup_program_test!( Wallets("wallet"), Abigen(Contract( name = "MyContract", project = "e2e/sway/logs/contract_logs" ),), Deploy( contract = "MyContract", name = "contract_instance", wallet = "wallet", random_salt = false, ) ); let contract_methods = contract_instance.methods(); { let response = contract_methods.produce_string_slice_log().call().await?; let logs = response.decode_logs_with_type::()?; assert_eq!("fuel".to_string(), logs.first().unwrap().to_string()); } { let response = contract_methods.produce_string_log().call().await?; let logs = response.decode_logs_with_type::()?; assert_eq!(vec!["fuel".to_string()], logs); } { let response = contract_methods.produce_bytes_log().call().await?; let logs = response.decode_logs_with_type::()?; assert_eq!(vec![Bytes("fuel".as_bytes().to_vec())], logs); } { let response = contract_methods.produce_raw_slice_log().call().await?; let logs = response.decode_logs_with_type::()?; assert_eq!(vec![RawSlice("fuel".as_bytes().to_vec())], logs); } { let v = [1u16, 2, 3].to_vec(); let some_enum = EnumWithGeneric::VariantOne(v); let other_enum = EnumWithGeneric::VariantTwo; let v1 = vec![some_enum.clone(), other_enum, some_enum]; let expected_vec = vec![vec![v1.clone(), v1]]; let response = contract_methods.produce_vec_log().call().await?; let logs = response.decode_logs_with_type::>>>>>()?; assert_eq!(vec![expected_vec], logs); } Ok(()) } #[tokio::test] async fn script_heap_log() -> Result<()> { setup_program_test!( Wallets("wallet"), Abigen(Script( name = "LogScript", project = "e2e/sway/logs/script_heap_logs" )), LoadScript( name = "script_instance", script = "LogScript", wallet = "wallet" ) ); let response = script_instance.main().call().await?; { let logs = response.decode_logs_with_type::()?; assert_eq!("fuel".to_string(), logs.first().unwrap().to_string()); } { let logs = response.decode_logs_with_type::()?; assert_eq!(vec!["fuel".to_string()], logs); } { let logs = response.decode_logs_with_type::()?; assert_eq!(vec![Bytes("fuel".as_bytes().to_vec())], logs); } { let logs = response.decode_logs_with_type::()?; assert_eq!(vec![RawSlice("fuel".as_bytes().to_vec())], logs); } { let v = [1u16, 2, 3].to_vec(); let some_enum = EnumWithGeneric::VariantOne(v); let other_enum = EnumWithGeneric::VariantTwo; let v1 = vec![some_enum.clone(), other_enum, some_enum]; let expected_vec = vec![vec![v1.clone(), v1]]; let logs = response.decode_logs_with_type::>>>>>()?; assert_eq!(vec![expected_vec], logs); } Ok(()) } #[tokio::test] async fn contract_panic() -> Result<()> { setup_program_test!( Wallets("wallet"), Abigen(Contract( name = "LogContract", project = "e2e/sway/logs/contract_logs" )), Deploy( name = "contract_instance", contract = "LogContract", wallet = "wallet", random_salt = false, ), ); macro_rules! reverts_with_msg { ($method:ident, call, $msg:expr) => { let error = contract_instance .methods() .$method() .call() .await .expect_err("should return a revert error"); assert_revert_containing_msg($msg, error); }; ($method:ident, simulate, $msg:expr) => { let error = contract_instance .methods() .$method() .simulate(Execution::realistic()) .await .expect_err("should return a revert error"); assert_revert_containing_msg($msg, error); }; } { reverts_with_msg!(produce_panic, call, "some panic message"); reverts_with_msg!(produce_panic, simulate, "some panic message"); } { reverts_with_msg!( produce_panic_with_error, call, "some complex error B: B { id: 42, val: 36 }" ); reverts_with_msg!( produce_panic_with_error, simulate, "some complex error B: B { id: 42, val: 36 }" ); } Ok(()) } #[tokio::test] async fn contract_with_contract_panic() -> Result<()> { setup_program_test!( Wallets("wallet"), Abigen( Contract(name = "MyContract", project = "e2e/sway/logs/contract_logs",), Contract( name = "ContractCaller", project = "e2e/sway/logs/contract_with_contract_logs", ) ), Deploy( name = "contract_instance", contract = "MyContract", wallet = "wallet", random_salt = false, ), Deploy( name = "contract_caller_instance", contract = "ContractCaller", wallet = "wallet", random_salt = false, ) ); macro_rules! reverts_with_msg { ($method:ident, call, $msg:expr) => { let error = contract_caller_instance .methods() .$method(contract_instance.id()) .with_contracts(&[&contract_instance]) .call() .await .expect_err("should return a revert error"); assert_revert_containing_msg($msg, error); }; ($method:ident, simulate, $msg:expr) => { let error = contract_caller_instance .methods() .$method(contract_instance.id()) .with_contracts(&[&contract_instance]) .simulate(Execution::realistic()) .await .expect_err("should return a revert error"); assert_revert_containing_msg($msg, error); }; } { reverts_with_msg!(panic_from_external_contract, call, "some panic message"); reverts_with_msg!(panic_from_external_contract, simulate, "some panic message"); } { reverts_with_msg!( panic_error_from_external_contract, call, "some complex error B: B { id: 42, val: 36 }" ); reverts_with_msg!( panic_error_from_external_contract, simulate, "some complex error B: B { id: 42, val: 36 }" ); } Ok(()) } #[tokio::test] async fn script_panic() -> Result<()> { setup_program_test!( Wallets("wallet"), Abigen(Script( name = "LogScript", project = "e2e/sway/logs/script_revert_logs" )), LoadScript( name = "script_instance", script = "LogScript", wallet = "wallet" ) ); macro_rules! reverts_with_msg { ($arg:expr, call, $msg:expr) => { let error = script_instance .main($arg) .call() .await .expect_err("should return a revert error"); assert_revert_containing_msg($msg, error); }; ($arg:expr, simulate, $msg:expr) => { let error = script_instance .main($arg) .simulate(Execution::realistic()) .await .expect_err("should return a revert error"); assert_revert_containing_msg($msg, error); }; } { reverts_with_msg!(MatchEnum::Panic, call, "some panic message"); reverts_with_msg!(MatchEnum::Panic, simulate, "some panic message"); } { reverts_with_msg!( MatchEnum::PanicError, call, "some complex error B: B { id: 42, val: 36 }" ); reverts_with_msg!( MatchEnum::PanicError, simulate, "some complex error B: B { id: 42, val: 36 }" ); } Ok(()) } #[tokio::test] #[allow(unused_variables)] async fn script_with_contract_panic() -> Result<()> { setup_program_test!( Wallets("wallet"), Abigen( Contract(name = "MyContract", project = "e2e/sway/logs/contract_logs",), Script( name = "LogScript", project = "e2e/sway/logs/script_with_contract_logs" ) ), Deploy( name = "contract_instance", contract = "MyContract", wallet = "wallet", random_salt = false, ), LoadScript( name = "script_instance", script = "LogScript", wallet = "wallet" ) ); macro_rules! reverts_with_msg { ($arg1:expr, $arg2:expr, call, $msg:expr) => { let error = script_instance .main($arg1, $arg2) .with_contracts(&[&contract_instance]) .call() .await .expect_err("should return a revert error"); assert_revert_containing_msg($msg, error); }; ($arg1:expr, $arg2:expr, simulate, $msg:expr) => { let error = script_instance .main($arg1, $arg2) .with_contracts(&[&contract_instance]) .simulate(Execution::realistic()) .await .expect_err("should return a revert error"); assert_revert_containing_msg($msg, error); }; } let contract_id = contract_instance.id(); { reverts_with_msg!(contract_id, MatchEnum::Panic, call, "some panic message"); reverts_with_msg!( contract_id, MatchEnum::Panic, simulate, "some panic message" ); } { reverts_with_msg!( contract_id, MatchEnum::PanicError, call, "some complex error B: B { id: 42, val: 36 }" ); reverts_with_msg!( contract_id, MatchEnum::PanicError, simulate, "some complex error B: B { id: 42, val: 36 }" ); } Ok(()) } ================================================ FILE: e2e/tests/predicates.rs ================================================ use std::default::Default; use fuels::{ accounts::signers::private_key::PrivateKeySigner, core::{ codec::{ABIEncoder, EncoderConfig}, traits::Tokenizable, }, prelude::*, programs::executable::Executable, types::{coin::Coin, coin_type::CoinType, input::Input, message::Message, output::Output}, }; use rand::thread_rng; async fn assert_address_balance( address: &Address, provider: &Provider, asset_id: &AssetId, amount: u64, ) { let balance = provider .get_asset_balance(address, asset_id) .await .expect("Could not retrieve balance"); assert_eq!(balance, amount as u128); } fn get_test_coins_and_messages( address: Address, num_coins: u64, num_messages: u64, amount: u64, start_nonce: u64, ) -> (Vec, Vec, AssetId) { let asset_id = AssetId::zeroed(); let coins = setup_single_asset_coins(address, asset_id, num_coins, amount); let messages = (0..num_messages) .map(|i| { setup_single_message( Address::default(), address, amount, (start_nonce + i).into(), vec![], ) }) .collect(); (coins, messages, asset_id) } fn get_test_message_w_data(address: Address, amount: u64, nonce: u64) -> Message { setup_single_message( Address::default(), address, amount, nonce.into(), vec![1, 2, 3], ) } // Setup function used to assign coins and messages to a predicate address // and create a `receiver` wallet async fn setup_predicate_test( predicate_address: Address, num_coins: u64, num_messages: u64, amount: u64, ) -> Result<(Provider, u64, Wallet, u64, AssetId, Wallet)> { let receiver_num_coins = 1; let receiver_amount = 1; let receiver_balance = receiver_num_coins * receiver_amount; let predicate_balance = (num_coins + num_messages) * amount; let mut rng = thread_rng(); let receiver_signer = PrivateKeySigner::random(&mut rng); let extra_wallet_signer = PrivateKeySigner::random(&mut rng); let (mut coins, messages, asset_id) = get_test_coins_and_messages(predicate_address, num_coins, num_messages, amount, 0); coins.extend(setup_single_asset_coins( receiver_signer.address(), asset_id, receiver_num_coins, receiver_amount, )); coins.extend(setup_single_asset_coins( extra_wallet_signer.address(), AssetId::zeroed(), 10_000, 10_000, )); coins.extend(setup_single_asset_coins( predicate_address, AssetId::from([1u8; 32]), num_coins, amount, )); let provider = setup_test_provider(coins, messages, None, None).await?; let receiver_wallet = Wallet::new(receiver_signer.clone(), provider.clone()); let extra_wallet = Wallet::new(extra_wallet_signer.clone(), provider.clone()); Ok(( provider, predicate_balance, receiver_wallet, receiver_balance, asset_id, extra_wallet, )) } #[tokio::test] async fn transfer_coins_and_messages_to_predicate() -> Result<()> { let num_coins = 16; let num_messages = 32; let amount = 64; let balance_to_send = 42; let signer = PrivateKeySigner::random(&mut thread_rng()); let (coins, messages, asset_id) = get_test_coins_and_messages(signer.address(), num_coins, num_messages, amount, 0); let provider = setup_test_provider(coins, messages, None, None).await?; let wallet = Wallet::new(signer, provider.clone()); let predicate = Predicate::load_from("sway/predicates/basic_predicate/out/release/basic_predicate.bin")? .with_provider(provider.clone()); wallet .transfer( predicate.address(), balance_to_send, asset_id, TxPolicies::default(), ) .await?; // The predicate has received the funds assert_address_balance(&predicate.address(), &provider, &asset_id, balance_to_send).await; Ok(()) } #[tokio::test] async fn spend_predicate_coins_messages_basic() -> Result<()> { abigen!(Predicate( name = "MyPredicate", abi = "e2e/sway/predicates/basic_predicate/out/release/basic_predicate-abi.json" )); let predicate_data = MyPredicateEncoder::default().encode_data(4097, 4097)?; let mut predicate: Predicate = Predicate::load_from("sway/predicates/basic_predicate/out/release/basic_predicate.bin")? .with_data(predicate_data); let num_coins = 4; let num_messages = 8; let amount = 16; let (provider, predicate_balance, receiver, receiver_balance, asset_id, _) = setup_predicate_test(predicate.address(), num_coins, num_messages, amount).await?; predicate.set_provider(provider.clone()); let amount_to_send = 128; let fee = predicate .transfer( receiver.address(), amount_to_send, asset_id, TxPolicies::default(), ) .await? .tx_status .total_fee; // The predicate has spent the funds let predicate_current_balance = predicate_balance - amount_to_send - fee; assert_address_balance( &predicate.address(), &provider, &asset_id, predicate_current_balance, ) .await; // Funds were transferred assert_address_balance( &receiver.address(), &provider, &asset_id, receiver_balance + amount_to_send, ) .await; Ok(()) } #[tokio::test] async fn pay_with_predicate() -> Result<()> { abigen!( Contract( name = "MyContract", abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json" ), Predicate( name = "MyPredicate", abi = "e2e/sway/types/predicates/u64/out/release/u64-abi.json" ) ); let predicate_data = MyPredicateEncoder::default().encode_data(32768)?; let mut predicate: Predicate = Predicate::load_from("sway/types/predicates/u64/out/release/u64.bin")? .with_data(predicate_data); let num_coins = 4; let num_messages = 8; let amount = 16; let (provider, predicate_balance, _receiver, _receiver_balance, _asset_id, _) = setup_predicate_test(predicate.address(), num_coins, num_messages, amount).await?; predicate.set_provider(provider.clone()); let deploy_response = Contract::load_from( "sway/contracts/contract_test/out/release/contract_test.bin", LoadConfiguration::default(), )? .deploy_if_not_exists(&predicate, TxPolicies::default()) .await?; let contract_methods = MyContract::new(deploy_response.contract_id, predicate.clone()).methods(); let consensus_parameters = provider.consensus_parameters().await?; let deploy_fee = deploy_response.tx_status.unwrap().total_fee; assert_eq!( predicate .get_asset_balance(consensus_parameters.base_asset_id()) .await?, (predicate_balance - deploy_fee) as u128 ); let response = contract_methods .initialize_counter(42) // Build the ABI call .call() .await?; assert_eq!(42, response.value); assert_eq!( predicate .get_asset_balance(consensus_parameters.base_asset_id()) .await?, (predicate_balance - deploy_fee - response.tx_status.total_fee) as u128 ); Ok(()) } #[tokio::test] async fn pay_with_predicate_vector_data() -> Result<()> { abigen!( Contract( name = "MyContract", abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json" ), Predicate( name = "MyPredicate", abi = "e2e/sway/types/predicates/predicate_vector/out/release/predicate_vector-abi.json" ) ); let predicate_data = MyPredicateEncoder::default().encode_data(12, 30, vec![2, 4, 42])?; let mut predicate: Predicate = Predicate::load_from( "sway/types/predicates/predicate_vector/out/release/predicate_vector.bin", )? .with_data(predicate_data); let num_coins = 4; let num_messages = 8; let amount = 16; let (provider, predicate_balance, _receiver, _receiver_balance, _asset_id, _) = setup_predicate_test(predicate.address(), num_coins, num_messages, amount).await?; predicate.set_provider(provider.clone()); let deploy_response = Contract::load_from( "sway/contracts/contract_test/out/release/contract_test.bin", LoadConfiguration::default(), )? .deploy_if_not_exists(&predicate, TxPolicies::default()) .await?; let contract_methods = MyContract::new(deploy_response.contract_id, predicate.clone()).methods(); let consensus_parameters = provider.consensus_parameters().await?; let deploy_fee = deploy_response.tx_status.unwrap().total_fee; assert_eq!( predicate .get_asset_balance(consensus_parameters.base_asset_id()) .await?, (predicate_balance - deploy_fee) as u128 ); let response = contract_methods.initialize_counter(42).call().await?; assert_eq!(42, response.value); assert_eq!( predicate .get_asset_balance(consensus_parameters.base_asset_id()) .await?, (predicate_balance - deploy_fee - response.tx_status.total_fee) as u128 ); Ok(()) } #[tokio::test] async fn predicate_contract_transfer() -> Result<()> { abigen!(Predicate( name = "MyPredicate", abi = "e2e/sway/types/predicates/predicate_vector/out/release/predicate_vector-abi.json" )); let predicate_data = MyPredicateEncoder::default().encode_data(2, 40, vec![2, 4, 42])?; let mut predicate: Predicate = Predicate::load_from( "sway/types/predicates/predicate_vector/out/release/predicate_vector.bin", )? .with_data(predicate_data); let num_coins = 4; let num_messages = 8; let amount = 300; let (provider, _predicate_balance, _receiver, _receiver_balance, _asset_id, _) = setup_predicate_test(predicate.address(), num_coins, num_messages, amount).await?; predicate.set_provider(provider.clone()); let contract_id = Contract::load_from( "sway/contracts/contract_test/out/release/contract_test.bin", LoadConfiguration::default(), )? .deploy_if_not_exists(&predicate, TxPolicies::default()) .await? .contract_id; let contract_balances = provider.get_contract_balances(&contract_id).await?; assert!(contract_balances.is_empty()); let amount = 300; predicate .force_transfer_to_contract( contract_id, amount, AssetId::zeroed(), TxPolicies::default(), ) .await?; let contract_balances = predicate .try_provider()? .get_contract_balances(&contract_id) .await?; assert_eq!(contract_balances.len(), 1); let random_asset_balance = contract_balances.get(&AssetId::zeroed()).unwrap(); assert_eq!(*random_asset_balance, 300); Ok(()) } #[tokio::test] async fn predicate_transfer_to_base_layer() -> Result<()> { use std::str::FromStr; abigen!(Predicate( name = "MyPredicate", abi = "e2e/sway/types/predicates/predicate_vector/out/release/predicate_vector-abi.json" )); let predicate_data = MyPredicateEncoder::default().encode_data(22, 20, vec![2, 4, 42])?; let mut predicate: Predicate = Predicate::load_from( "sway/types/predicates/predicate_vector/out/release/predicate_vector.bin", )? .with_data(predicate_data); let num_coins = 4; let num_messages = 8; let amount = 300; let (provider, _predicate_balance, _receiver, _receiver_balance, _asset_id, _) = setup_predicate_test(predicate.address(), num_coins, num_messages, amount).await?; predicate.set_provider(provider.clone()); let amount = 1000; let base_layer_address = Address::from_str("0x4710162c2e3a95a6faff05139150017c9e38e5e280432d546fae345d6ce6d8fe")?; let withdraw_response = predicate .withdraw_to_base_layer(base_layer_address, amount, TxPolicies::default()) .await?; // Create the next commit block to be able generate the proof provider.produce_blocks(1, None).await?; let proof = predicate .try_provider()? .get_message_proof( &withdraw_response.tx_id, &withdraw_response.nonce, None, Some(2), ) .await?; assert_eq!(proof.amount, amount); assert_eq!(proof.recipient, base_layer_address); Ok(()) } #[tokio::test] async fn predicate_transfer_with_signed_resources() -> Result<()> { abigen!(Predicate( name = "MyPredicate", abi = "e2e/sway/types/predicates/predicate_vector/out/release/predicate_vector-abi.json" )); let predicate_data = MyPredicateEncoder::default().encode_data(2, 40, vec![2, 4, 42])?; let mut predicate: Predicate = Predicate::load_from( "sway/types/predicates/predicate_vector/out/release/predicate_vector.bin", )? .with_data(predicate_data); let predicate_num_coins = 4; let predicate_num_messages = 3; let predicate_amount = 1000; let predicate_balance = (predicate_num_coins + predicate_num_messages) * predicate_amount; let signer = PrivateKeySigner::random(&mut thread_rng()); let wallet_num_coins = 4; let wallet_num_messages = 3; let wallet_amount = 1000; let wallet_balance = (wallet_num_coins + wallet_num_messages) * wallet_amount; let (mut coins, mut messages, asset_id) = get_test_coins_and_messages( predicate.address(), predicate_num_coins, predicate_num_messages, predicate_amount, 0, ); let (wallet_coins, wallet_messages, _) = get_test_coins_and_messages( signer.address(), wallet_num_coins, wallet_num_messages, wallet_amount, predicate_num_messages, ); coins.extend(wallet_coins); messages.extend(wallet_messages); let provider = setup_test_provider(coins, messages, None, None).await?; let wallet = Wallet::new(signer.clone(), provider.clone()); predicate.set_provider(provider.clone()); let mut inputs = wallet .get_asset_inputs_for_amount(asset_id, wallet_balance.into(), None) .await?; let predicate_inputs = predicate .get_asset_inputs_for_amount(asset_id, predicate_balance.into(), None) .await?; inputs.extend(predicate_inputs); let outputs = vec![Output::change(predicate.address(), 0, asset_id)]; let mut tb = ScriptTransactionBuilder::prepare_transfer(inputs, outputs, Default::default()); tb.add_signer(signer)?; let tx = tb.build(&provider).await?; let tx_status = provider.send_transaction_and_await_commit(tx).await?; assert_address_balance( &predicate.address(), &provider, &asset_id, predicate_balance + wallet_balance - tx_status.total_fee(), ) .await; Ok(()) } #[tokio::test] #[allow(unused_variables)] async fn contract_tx_and_call_params_with_predicate() -> Result<()> { use fuels::prelude::*; abigen!( Contract( name = "MyContract", abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json" ), Predicate( name = "MyPredicate", abi = "e2e/sway/types/predicates/predicate_vector/out/release/predicate_vector-abi.json" ) ); let predicate_data = MyPredicateEncoder::default().encode_data(22, 20, vec![2, 4, 42])?; let mut predicate: Predicate = Predicate::load_from( "sway/types/predicates/predicate_vector/out/release/predicate_vector.bin", )? .with_data(predicate_data); let num_coins = 1; let num_messages = 1; let amount = 1000; let (provider, predicate_balance, _receiver, _receiver_balance, _asset_id, _) = setup_predicate_test(predicate.address(), num_coins, num_messages, amount).await?; predicate.set_provider(provider.clone()); let deploy_response = Contract::load_from( "./sway/contracts/contract_test/out/release/contract_test.bin", LoadConfiguration::default(), )? .deploy_if_not_exists(&predicate, TxPolicies::default()) .await?; let contract_methods = MyContract::new(deploy_response.contract_id, predicate.clone()).methods(); let tx_policies = TxPolicies::default().with_tip(100); let call_params_amount = 100; let call_params = CallParameters::default() .with_amount(call_params_amount) .with_asset_id(AssetId::zeroed()); { let call_response = contract_methods .get_msg_amount() .with_tx_policies(tx_policies) .call_params(call_params.clone())? .call() .await?; let deploy_fee = deploy_response.tx_status.unwrap().total_fee; let call_fee = call_response.tx_status.total_fee; assert_eq!( predicate.get_asset_balance(&AssetId::zeroed()).await?, (predicate_balance - deploy_fee - call_params_amount - call_fee) as u128 ); } { let custom_asset = AssetId::from([1u8; 32]); let response = contract_methods .get_msg_amount() .call_params(call_params)? .add_custom_asset(custom_asset, 100, Some(Address::default())) .call() .await?; assert_eq!(predicate.get_asset_balance(&custom_asset).await?, 900); } Ok(()) } #[tokio::test] #[allow(unused_variables)] async fn diff_asset_predicate_payment() -> Result<()> { use fuels::prelude::*; abigen!( Contract( name = "MyContract", abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json" ), Predicate( name = "MyPredicate", abi = "e2e/sway/types/predicates/predicate_vector/out/release/predicate_vector-abi.json" ) ); let predicate_data = MyPredicateEncoder::default().encode_data(28, 14, vec![2, 4, 42])?; let mut predicate: Predicate = Predicate::load_from( "sway/types/predicates/predicate_vector/out/release/predicate_vector.bin", )? .with_data(predicate_data); let num_coins = 1; let num_messages = 1; let amount = 1_000_000_000; let (provider, _predicate_balance, _receiver, _receiver_balance, _asset_id, _) = setup_predicate_test(predicate.address(), num_coins, num_messages, amount).await?; predicate.set_provider(provider.clone()); let contract_id = Contract::load_from( "./sway/contracts/contract_test/out/release/contract_test.bin", LoadConfiguration::default(), )? .deploy_if_not_exists(&predicate, TxPolicies::default()) .await? .contract_id; let contract_methods = MyContract::new(contract_id, predicate.clone()).methods(); let call_params = CallParameters::default() .with_amount(1_000_000) .with_asset_id(AssetId::from([1u8; 32])); let response = contract_methods .get_msg_amount() .call_params(call_params)? .call() .await?; Ok(()) } #[tokio::test] async fn predicate_default_configurables() -> Result<()> { abigen!(Predicate( name = "MyPredicate", abi = "e2e/sway/predicates/predicate_configurables/out/release/predicate_configurables-abi.json" )); let new_struct = StructWithGeneric { field_1: 8u8, field_2: 16, }; let new_enum = EnumWithGeneric::VariantOne(true); let predicate_data = MyPredicateEncoder::default().encode_data( true, 8, (8, true), [253, 254, 255], new_struct, new_enum, )?; let mut predicate: Predicate = Predicate::load_from( "sway/predicates/predicate_configurables/out/release/predicate_configurables.bin", )? .with_data(predicate_data); let num_coins = 4; let num_messages = 8; let amount = 16; let (provider, predicate_balance, receiver, receiver_balance, asset_id, _) = setup_predicate_test(predicate.address(), num_coins, num_messages, amount).await?; predicate.set_provider(provider.clone()); let amount_to_send = predicate_balance - 1; predicate .transfer( receiver.address(), amount_to_send, asset_id, TxPolicies::default(), ) .await?; // The predicate has spent the funds assert_address_balance(&predicate.address(), &provider, &asset_id, 0).await; // Funds were transferred assert_address_balance( &receiver.address(), &provider, &asset_id, receiver_balance + amount_to_send, ) .await; Ok(()) } #[tokio::test] async fn predicate_configurables() -> Result<()> { // ANCHOR: predicate_configurables abigen!(Predicate( name = "MyPredicate", abi = "e2e/sway/predicates/predicate_configurables/out/release/predicate_configurables-abi.json" )); let new_tuple = (16, false); let new_array = [123, 124, 125]; let new_struct = StructWithGeneric { field_1: 32u8, field_2: 64, }; let new_enum = EnumWithGeneric::VariantTwo; let configurables = MyPredicateConfigurables::default() .with_U8(8)? .with_TUPLE(new_tuple)? .with_ARRAY(new_array)? .with_STRUCT(new_struct.clone())? .with_ENUM(new_enum.clone())?; let predicate_data = MyPredicateEncoder::default() .encode_data(true, 8u8, new_tuple, new_array, new_struct, new_enum)?; let mut predicate: Predicate = Predicate::load_from( "sway/predicates/predicate_configurables/out/release/predicate_configurables.bin", )? .with_data(predicate_data) .with_configurables(configurables); // ANCHOR_END: predicate_configurables let num_coins = 4; let num_messages = 8; let amount = 16; let (provider, predicate_balance, receiver, receiver_balance, asset_id, _) = setup_predicate_test(predicate.address(), num_coins, num_messages, amount).await?; predicate.set_provider(provider.clone()); let amount_to_send = predicate_balance - 1; let fee = predicate .transfer( receiver.address(), amount_to_send, asset_id, TxPolicies::default(), ) .await? .tx_status .total_fee; // The predicate has spent the funds assert_address_balance(&predicate.address(), &provider, &asset_id, 0).await; // Funds were transferred assert_address_balance( &receiver.address(), &provider, &asset_id, receiver_balance + predicate_balance - fee, ) .await; Ok(()) } #[tokio::test] async fn predicate_adjust_fee_persists_message_w_data() -> Result<()> { abigen!(Predicate( name = "MyPredicate", abi = "e2e/sway/predicates/basic_predicate/out/release/basic_predicate-abi.json" )); let predicate_data = MyPredicateEncoder::default().encode_data(4097, 4097)?; let mut predicate: Predicate = Predicate::load_from("sway/predicates/basic_predicate/out/release/basic_predicate.bin")? .with_data(predicate_data); let amount = 1000; let coins = setup_single_asset_coins(predicate.address(), AssetId::zeroed(), 1, amount); let message = get_test_message_w_data(predicate.address(), amount, Default::default()); let message_input = Input::resource_predicate( CoinType::Message(message.clone()), predicate.code().to_vec(), predicate.data().to_vec(), ); let provider = setup_test_provider(coins, vec![message.clone()], None, None).await?; predicate.set_provider(provider.clone()); let mut tb = ScriptTransactionBuilder::prepare_transfer( vec![message_input.clone()], vec![], TxPolicies::default(), ); predicate.adjust_for_fee(&mut tb, 0).await?; let tx = tb.build(&provider).await?; assert_eq!(tx.inputs().len(), 2); assert_eq!(tx.inputs()[0].message_id().unwrap(), message.message_id()); Ok(()) } #[tokio::test] async fn predicate_transfer_non_base_asset() -> Result<()> { abigen!(Predicate( name = "MyPredicate", abi = "e2e/sway/predicates/basic_predicate/out/release/basic_predicate-abi.json" )); let predicate_data = MyPredicateEncoder::default().encode_data(32, 32)?; let mut predicate: Predicate = Predicate::load_from("sway/predicates/basic_predicate/out/release/basic_predicate.bin")? .with_data(predicate_data); let signer = PrivateKeySigner::random(&mut thread_rng()); let amount = 5; let non_base_asset_id = AssetId::new([1; 32]); // wallet has base and predicate non base asset let mut coins = setup_single_asset_coins(signer.address(), AssetId::zeroed(), 1, amount); coins.extend(setup_single_asset_coins( predicate.address(), non_base_asset_id, 1, amount, )); let provider = setup_test_provider(coins, vec![], None, None).await?; predicate.set_provider(provider.clone()); let wallet = Wallet::new(signer.clone(), provider.clone()); let inputs = predicate .get_asset_inputs_for_amount(non_base_asset_id, amount.into(), None) .await?; let consensus_parameters = provider.consensus_parameters().await?; let outputs = vec![ Output::change(wallet.address(), 0, non_base_asset_id), Output::change(wallet.address(), 0, *consensus_parameters.base_asset_id()), ]; let mut tb = ScriptTransactionBuilder::prepare_transfer( inputs, outputs, TxPolicies::default().with_tip(1), ); tb.add_signer(signer)?; wallet.adjust_for_fee(&mut tb, 0).await?; let tx = tb.build(&provider).await?; provider .send_transaction_and_await_commit(tx) .await? .check(None)?; let wallet_balance = wallet.get_asset_balance(&non_base_asset_id).await?; assert_eq!(wallet_balance, amount as u128); Ok(()) } #[tokio::test] async fn predicate_can_access_manually_added_witnesses() -> Result<()> { abigen!(Predicate( name = "MyPredicate", abi = "e2e/sway/predicates/predicate_witnesses/out/release/predicate_witnesses-abi.json" )); let predicate_data = MyPredicateEncoder::default().encode_data(0, 1)?; let mut predicate: Predicate = Predicate::load_from( "sway/predicates/predicate_witnesses/out/release/predicate_witnesses.bin", )? .with_data(predicate_data); let num_coins = 4; let num_messages = 0; let amount = 16; let (provider, predicate_balance, receiver, receiver_balance, asset_id, _) = setup_predicate_test(predicate.address(), num_coins, num_messages, amount).await?; predicate.set_provider(provider.clone()); let amount_to_send = 12u64; let inputs = predicate .get_asset_inputs_for_amount(asset_id, amount_to_send.into(), None) .await?; let outputs = predicate.get_asset_outputs_for_amount(receiver.address(), asset_id, amount_to_send); let mut tx = ScriptTransactionBuilder::prepare_transfer( inputs, outputs, TxPolicies::default().with_witness_limit(32), ) .build(&provider) .await?; let witness = ABIEncoder::default().encode(&[64u64.into_token()])?; // u64 because this is VM memory let witness2 = ABIEncoder::default().encode(&[4096u64.into_token()])?; tx.append_witness(witness.into())?; tx.append_witness(witness2.into())?; let tx_status = provider.send_transaction_and_await_commit(tx).await?; let fee = tx_status.total_fee(); // The predicate has spent the funds assert_address_balance( &predicate.address(), &provider, &asset_id, predicate_balance - amount_to_send - fee, ) .await; // Funds were transferred assert_address_balance( &receiver.address(), &provider, &asset_id, receiver_balance + amount_to_send, ) .await; Ok(()) } #[tokio::test] async fn tx_id_not_changed_after_adding_witnesses() -> Result<()> { abigen!(Predicate( name = "MyPredicate", abi = "e2e/sway/predicates/predicate_witnesses/out/release/predicate_witnesses-abi.json" )); let predicate_data = MyPredicateEncoder::default().encode_data(0, 1)?; let mut predicate: Predicate = Predicate::load_from( "sway/predicates/predicate_witnesses/out/release/predicate_witnesses.bin", )? .with_data(predicate_data); let num_coins = 4; let num_messages = 0; let amount = 16; let (provider, _predicate_balance, receiver, _receiver_balance, asset_id, _) = setup_predicate_test(predicate.address(), num_coins, num_messages, amount).await?; predicate.set_provider(provider.clone()); let amount_to_send = 12u64; let inputs = predicate .get_asset_inputs_for_amount(asset_id, amount_to_send.into(), None) .await?; let outputs = predicate.get_asset_outputs_for_amount(receiver.address(), asset_id, amount_to_send); let mut tx = ScriptTransactionBuilder::prepare_transfer( inputs, outputs, TxPolicies::default().with_witness_limit(32), ) .build(&provider) .await?; let consensus_parameters = provider.consensus_parameters().await?; let chain_id = consensus_parameters.chain_id(); let tx_id = tx.id(chain_id); let witness = ABIEncoder::default().encode(&[64u64.into_token()])?; // u64 because this is VM memory let witness2 = ABIEncoder::default().encode(&[4096u64.into_token()])?; tx.append_witness(witness.into())?; tx.append_witness(witness2.into())?; let tx_id_after_witnesses = tx.id(chain_id); let tx_id_from_provider = provider.send_transaction(tx).await?; assert_eq!(tx_id, tx_id_after_witnesses); assert_eq!(tx_id, tx_id_from_provider); Ok(()) } #[tokio::test] async fn predicate_encoder_config_is_applied() -> Result<()> { abigen!(Predicate( name = "MyPredicate", abi = "e2e/sway/predicates/basic_predicate/out/release/basic_predicate-abi.json" )); { let _encoding_ok = MyPredicateEncoder::default() .encode_data(4097, 4097) .expect("should not fail as it uses the default encoder config"); } { let encoder_config = EncoderConfig { max_tokens: 1, ..Default::default() }; let encoding_error = MyPredicateEncoder::new(encoder_config) .encode_data(4097, 4097) .expect_err("should fail"); assert!( encoding_error .to_string() .contains("token limit `1` reached while encoding") ); } Ok(()) } #[tokio::test] async fn predicate_transfers_non_base_asset() -> Result<()> { abigen!(Predicate( name = "MyPredicate", abi = "e2e/sway/predicates/basic_predicate/out/release/basic_predicate-abi.json" )); let predicate_data = MyPredicateEncoder::default().encode_data(4097, 4097)?; let mut predicate: Predicate = Predicate::load_from("sway/predicates/basic_predicate/out/release/basic_predicate.bin")? .with_data(predicate_data); let num_coins = 4; let num_message = 6; let amount = 20; let (provider, _, receiver, _, _, _) = setup_predicate_test(predicate.address(), num_coins, num_message, amount).await?; predicate.set_provider(provider); let other_asset_id = AssetId::from([1u8; 32]); let send_amount = num_coins * amount; predicate .transfer( receiver.address(), send_amount, other_asset_id, TxPolicies::default(), ) .await?; assert_eq!(predicate.get_asset_balance(&other_asset_id).await?, 0,); assert_eq!( receiver.get_asset_balance(&other_asset_id).await?, send_amount as u128, ); Ok(()) } #[tokio::test] async fn predicate_with_invalid_data_fails() -> Result<()> { abigen!(Predicate( name = "MyPredicate", abi = "e2e/sway/predicates/basic_predicate/out/release/basic_predicate-abi.json" )); let predicate_data = MyPredicateEncoder::default().encode_data(0, 100)?; let mut predicate: Predicate = Predicate::load_from("sway/predicates/basic_predicate/out/release/basic_predicate.bin")? .with_data(predicate_data); let num_coins = 4; let num_message = 6; let amount = 20; let (provider, _, receiver, _, _, _) = setup_predicate_test(predicate.address(), num_coins, num_message, amount).await?; predicate.set_provider(provider); let other_asset_id = AssetId::from([1u8; 32]); let send_amount = num_coins * amount; let error_string = predicate .transfer( receiver.address(), send_amount, other_asset_id, TxPolicies::default(), ) .await .unwrap_err() .to_string(); assert!(error_string.contains( "PredicateVerificationFailed(Panic { index: 0, reason: PredicateReturnedNonOne })" )); assert_eq!(receiver.get_asset_balance(&other_asset_id).await?, 0); Ok(()) } #[tokio::test] async fn predicate_blobs() -> Result<()> { abigen!(Predicate( name = "MyPredicate", abi = "e2e/sway/predicates/predicate_blobs/out/release/predicate_blobs-abi.json" )); // ANCHOR: preparing_the_predicate let configurables = MyPredicateConfigurables::default().with_SECRET_NUMBER(10001)?; let predicate_data = MyPredicateEncoder::default().encode_data(1, 19)?; let executable = Executable::load_from("sway/predicates/predicate_blobs/out/release/predicate_blobs.bin")?; let loader = executable .convert_to_loader()? .with_configurables(configurables); let mut predicate: Predicate = Predicate::from_code(loader.code()).with_data(predicate_data); // ANCHOR_END: preparing_the_predicate let num_coins = 4; let num_messages = 8; let amount = 16; let (provider, predicate_balance, receiver, receiver_balance, asset_id, extra_wallet) = setup_predicate_test(predicate.address(), num_coins, num_messages, amount).await?; // we don't want to pay with the recipient wallet so that we don't affect the assertion we're // gonna make later on // ANCHOR: uploading_the_blob loader.upload_blob(extra_wallet).await?; predicate.set_provider(provider.clone()); let amount_to_send = 42; let response = predicate .transfer( receiver.address(), amount_to_send, asset_id, TxPolicies::default(), ) .await?; // ANCHOR_END: uploading_the_blob // The predicate has spent the funds let transaction_fee = response.tx_status.total_fee; assert_address_balance( &predicate.address(), &provider, &asset_id, predicate_balance - amount_to_send - transaction_fee, ) .await; // Funds were transferred assert_address_balance( &receiver.address(), &provider, &asset_id, receiver_balance + amount_to_send, ) .await; Ok(()) } #[tokio::test] async fn predicate_configurables_in_blobs() -> Result<()> { abigen!(Predicate( name = "MyPredicate", abi = "e2e/sway/predicates/predicate_configurables/out/release/predicate_configurables-abi.json" )); let new_tuple = (16, false); let new_array = [123, 124, 125]; let new_struct = StructWithGeneric { field_1: 32u8, field_2: 64, }; let new_enum = EnumWithGeneric::VariantTwo; let configurables = MyPredicateConfigurables::default() .with_U8(8)? .with_TUPLE(new_tuple)? .with_ARRAY(new_array)? .with_STRUCT(new_struct.clone())? .with_ENUM(new_enum.clone())?; let predicate_data = MyPredicateEncoder::default() .encode_data(true, 8u8, new_tuple, new_array, new_struct, new_enum)?; let executable = Executable::load_from( "sway/predicates/predicate_configurables/out/release/predicate_configurables.bin", )?; let loader = executable .convert_to_loader()? .with_configurables(configurables); let mut predicate: Predicate = Predicate::from_code(loader.code()).with_data(predicate_data); let num_coins = 4; let num_messages = 8; let amount = 16; let (provider, predicate_balance, receiver, receiver_balance, asset_id, extra_wallet) = setup_predicate_test(predicate.address(), num_coins, num_messages, amount).await?; predicate.set_provider(provider.clone()); loader .upload_blob(extra_wallet) .await? .expect("has tx_status"); let amount_to_send = predicate_balance - 1; predicate .transfer( receiver.address(), amount_to_send, asset_id, TxPolicies::default(), ) .await?; // The predicate has spent the funds assert_address_balance(&predicate.address(), &provider, &asset_id, 0).await; // Funds were transferred assert_address_balance( &receiver.address(), &provider, &asset_id, receiver_balance + amount_to_send, ) .await; Ok(()) } #[tokio::test] async fn predicate_transfer_respects_maturity_and_expiration() -> Result<()> { abigen!(Predicate( name = "MyPredicate", abi = "e2e/sway/predicates/basic_predicate/out/release/basic_predicate-abi.json" )); let predicate_data = MyPredicateEncoder::default().encode_data(4097, 4097)?; let mut predicate: Predicate = Predicate::load_from("sway/predicates/basic_predicate/out/release/basic_predicate.bin")? .with_data(predicate_data); let num_coins = 4; let num_messages = 8; let amount = 16; let (provider, predicate_balance, receiver, receiver_balance, asset_id, _) = setup_predicate_test(predicate.address(), num_coins, num_messages, amount).await?; predicate.set_provider(provider.clone()); let maturity = 10; let expiration = 20; let tx_policies = TxPolicies::default() .with_maturity(maturity) .with_expiration(expiration); let amount_to_send = 10; { let err = predicate .transfer(receiver.address(), amount_to_send, asset_id, tx_policies) .await .expect_err("maturity not reached"); assert!(err.to_string().contains("TransactionMaturity")); } let transaction_fee = { provider.produce_blocks(15, None).await?; predicate .transfer(receiver.address(), amount_to_send, asset_id, tx_policies) .await .expect("should succeed. Block height between `maturity` and `expiration`") .tx_status .total_fee }; { provider.produce_blocks(15, None).await?; let err = predicate .transfer(receiver.address(), amount_to_send, asset_id, tx_policies) .await .expect_err("expiration reached"); assert!(err.to_string().contains("TransactionExpiration")); } // The predicate has spent the funds assert_address_balance( &predicate.address(), &provider, &asset_id, predicate_balance - amount_to_send - transaction_fee, ) .await; // Funds were transferred assert_address_balance( &receiver.address(), &provider, &asset_id, receiver_balance + amount_to_send, ) .await; Ok(()) } async fn transfer_to_predicate( from: &impl Account, address: &Address, amount: u64, asset_id: AssetId, ) { from.transfer(*address, amount, asset_id, TxPolicies::default()) .await .unwrap(); assert_address_balance(address, from.try_provider().unwrap(), &asset_id, amount).await; } #[tokio::test] async fn predicate_tx_input_output() -> Result<()> { setup_program_test!( Wallets("wallet_1", "wallet_2"), Abigen( Contract( name = "TestContract", project = "e2e/sway/contracts/contract_test" ), Predicate( name = "MyPredicate", project = "e2e/sway/predicates/predicate_tx_input_output" ), ), Deploy( name = "contract_instance", contract = "TestContract", wallet = "wallet_1", random_salt = false, ), ); let provider = wallet_1.try_provider()?; // Predicate expects `wallet_2` as owner let configurables = MyPredicateConfigurables::default().with_OWNER(wallet_2.address())?; // Predicate will check first input and first output let predicate_data = MyPredicateEncoder::default().encode_data(0, 0)?; let mut predicate: Predicate = Predicate::load_from( "sway/predicates/predicate_tx_input_output/out/release/predicate_tx_input_output.bin", )? .with_data(predicate_data) .with_configurables(configurables); predicate.set_provider(provider.clone()); let asset_id = AssetId::zeroed(); { transfer_to_predicate(&wallet_2, &predicate.address(), 42, asset_id).await; // Call contract method with custom `wallet_2` input at first place, predicate at second // and custom change to `wallet_2` let wallet_input = wallet_2 .get_asset_inputs_for_amount(asset_id, 10, None) .await? .pop() .unwrap(); let predicate_input = predicate .get_asset_inputs_for_amount(asset_id, 10, None) .await? .pop() .unwrap(); let custom_inputs = vec![wallet_input, predicate_input]; let custom_output = vec![Output::change(wallet_2.address(), 0, asset_id)]; let value = contract_instance .methods() .initialize_counter(36) .with_inputs(custom_inputs) .add_signer(wallet_2.signer().clone()) .with_outputs(custom_output) .call() .await? .value; assert_eq!(value, 36); } { transfer_to_predicate(&wallet_2, &predicate.address(), 42, asset_id).await; // Add coin with wrong owner (`wallet_1`) let wallet_input = wallet_1 .get_asset_inputs_for_amount(asset_id, 10, None) .await? .pop() .unwrap(); let predicate_input = predicate .get_asset_inputs_for_amount(asset_id, 10, None) .await? .pop() .unwrap(); let custom_inputs = vec![wallet_input, predicate_input]; let err = contract_instance .methods() .initialize_counter(36) .with_inputs(custom_inputs) .call() .await .unwrap_err(); assert!(err.to_string().contains("PredicateVerificationFailed")); } Ok(()) } ================================================ FILE: e2e/tests/providers.rs ================================================ use std::{ops::Add, path::Path}; use chrono::{DateTime, Duration, TimeZone, Utc}; use fuel_asm::RegId; use fuel_tx::SubAssetId; use fuels::{ accounts::{ Account, signers::{fake::FakeSigner, private_key::PrivateKeySigner}, }, client::{PageDirection, PaginationRequest}, prelude::*, tx::{ContractIdExt, Receipt, Witness}, types::{ coin_type::CoinType, message::Message, transaction_builders::{BuildableTransaction, ScriptTransactionBuilder}, tx_status::{Failure, Success, TxStatus}, }, }; use futures::StreamExt; use rand::thread_rng; #[tokio::test] async fn test_provider_launch_and_connect() -> Result<()> { abigen!(Contract( name = "MyContract", abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json" )); let signer = PrivateKeySigner::random(&mut thread_rng()); let coins = setup_single_asset_coins( signer.address(), AssetId::zeroed(), DEFAULT_NUM_COINS, DEFAULT_COIN_AMOUNT, ); let provider = setup_test_provider(coins, vec![], None, None).await?; let wallet = Wallet::new(signer, provider.clone()); let contract_id = Contract::load_from( "sway/contracts/contract_test/out/release/contract_test.bin", LoadConfiguration::default(), )? .deploy_if_not_exists(&wallet, TxPolicies::default()) .await? .contract_id; let contract_instance_connected = MyContract::new(contract_id, wallet.clone()); let response = contract_instance_connected .methods() .initialize_counter(42) .call() .await?; assert_eq!(42, response.value); let contract_instance_launched = MyContract::new(contract_id, wallet); let response = contract_instance_launched .methods() .increment_counter(10) .call() .await?; assert_eq!(52, response.value); Ok(()) } #[tokio::test] async fn test_network_error() -> Result<()> { abigen!(Contract( name = "MyContract", abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json" )); let node_config = NodeConfig::default(); let chain_config = ChainConfig::default(); let state_config = StateConfig::default(); let service = FuelService::start(node_config, chain_config, state_config).await?; let provider = Provider::connect(service.bound_address().to_string()).await?; let wallet = Wallet::random(&mut thread_rng(), provider.clone()); // Simulate an unreachable node service.stop().await.unwrap(); let response = Contract::load_from( "sway/contracts/contract_test/out/release/contract_test.bin", LoadConfiguration::default(), )? .deploy_if_not_exists(&wallet, TxPolicies::default()) .await; assert!(matches!(response, Err(Error::Provider(_)))); Ok(()) } #[tokio::test] async fn test_input_message() -> Result<()> { let compare_messages = |messages_from_provider: Vec, used_messages: Vec| -> bool { std::iter::zip(&used_messages, &messages_from_provider).all(|(a, b)| { a.sender == b.sender && a.recipient == b.recipient && a.nonce == b.nonce && a.amount == b.amount }) }; let signer = PrivateKeySigner::random(&mut thread_rng()); // coin to pay transaction fee let coins = setup_single_asset_coins(signer.address(), AssetId::zeroed(), 1, DEFAULT_COIN_AMOUNT); let messages = vec![setup_single_message( Address::default(), signer.address(), DEFAULT_COIN_AMOUNT, 0.into(), vec![1, 2], )]; let provider = setup_test_provider(coins, messages.clone(), None, None).await?; let wallet = Wallet::new(signer, provider.clone()); setup_program_test!( Abigen(Contract( name = "TestContract", project = "e2e/sway/contracts/contract_test" )), Deploy( name = "contract_instance", contract = "TestContract", wallet = "wallet", random_salt = false, ), ); let spendable_messages = wallet.get_messages().await?; assert!(compare_messages(spendable_messages, messages)); let response = contract_instance .methods() .initialize_counter(42) .call() .await?; assert_eq!(42, response.value); Ok(()) } #[tokio::test] async fn test_input_message_pays_fee() -> Result<()> { let signer = PrivateKeySigner::random(&mut thread_rng()); let messages = setup_single_message( Address::default(), signer.address(), DEFAULT_COIN_AMOUNT, 0.into(), vec![], ); let provider = setup_test_provider(vec![], vec![messages], None, None).await?; let consensus_parameters = provider.consensus_parameters().await?; let base_asset_id = consensus_parameters.base_asset_id(); let wallet = Wallet::new(signer, provider.clone()); abigen!(Contract( name = "MyContract", abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json" )); let deploy_response = Contract::load_from( "sway/contracts/contract_test/out/release/contract_test.bin", LoadConfiguration::default(), )? .deploy_if_not_exists(&wallet, TxPolicies::default()) .await?; let contract_instance = MyContract::new(deploy_response.contract_id, wallet.clone()); let call_response = contract_instance .methods() .initialize_counter(42) .call() .await?; assert_eq!(42, call_response.value); let balance = wallet.get_asset_balance(base_asset_id).await?; let deploy_fee = deploy_response.tx_status.unwrap().total_fee; let call_fee = call_response.tx_status.total_fee; assert_eq!( balance, (DEFAULT_COIN_AMOUNT - deploy_fee - call_fee) as u128 ); Ok(()) } #[tokio::test] async fn can_increase_block_height() -> Result<()> { // ANCHOR: use_produce_blocks_to_increase_block_height let wallets = launch_custom_provider_and_get_wallets(WalletsConfig::default(), None, None).await?; let wallet = &wallets[0]; let provider = wallet.provider(); assert_eq!(provider.latest_block_height().await?, 0u32); provider.produce_blocks(3, None).await?; assert_eq!(provider.latest_block_height().await?, 3u32); // ANCHOR_END: use_produce_blocks_to_increase_block_height Ok(()) } // debug builds are slower (20x for `fuel-core-lib`, 4x for a release-fuel-core-binary), makes for // flaky tests #[cfg(not(feature = "fuel-core-lib"))] #[tokio::test] async fn can_set_custom_block_time() -> Result<()> { // ANCHOR: use_produce_blocks_custom_time let block_time = 20u32; // seconds let config = NodeConfig { // This is how you specify the time between blocks block_production: Trigger::Interval { block_time: std::time::Duration::from_secs(block_time.into()), }, ..NodeConfig::default() }; let wallets = launch_custom_provider_and_get_wallets(WalletsConfig::default(), Some(config), None) .await?; let wallet = &wallets[0]; let provider = wallet.provider(); assert_eq!(provider.latest_block_height().await?, 0u32); let origin_block_time = provider.latest_block_time().await?.unwrap(); let blocks_to_produce = 3; provider.produce_blocks(blocks_to_produce, None).await?; assert_eq!(provider.latest_block_height().await?, blocks_to_produce); let expected_latest_block_time = origin_block_time .checked_add_signed(Duration::try_seconds((blocks_to_produce * block_time) as i64).unwrap()) .unwrap(); assert_eq!( provider.latest_block_time().await?.unwrap(), expected_latest_block_time ); // ANCHOR_END: use_produce_blocks_custom_time let req = PaginationRequest { cursor: None, results: 10, direction: PageDirection::Forward, }; let blocks: Vec = provider.get_blocks(req).await?.results; assert_eq!(blocks[1].header.time.unwrap().timestamp(), 20); assert_eq!(blocks[2].header.time.unwrap().timestamp(), 40); assert_eq!(blocks[3].header.time.unwrap().timestamp(), 60); Ok(()) } #[tokio::test] async fn can_retrieve_latest_block_time() -> Result<()> { let provider = setup_test_provider(vec![], vec![], None, None).await?; let since_epoch = 1676039910; let latest_timestamp = Utc.timestamp_opt(since_epoch, 0).unwrap(); provider.produce_blocks(1, Some(latest_timestamp)).await?; assert_eq!( provider.latest_block_time().await?.unwrap(), latest_timestamp ); Ok(()) } #[tokio::test] async fn contract_deployment_respects_maturity_and_expiration() -> Result<()> { abigen!(Contract( name = "MyContract", abi = "e2e/sway/contracts/transaction_block_height/out/release/transaction_block_height-abi.json" )); let wallet = launch_provider_and_get_wallet().await?; let provider = wallet.provider().clone(); let maturity = 10; let expiration = 20; let deploy_w_maturity_and_expiration = || { Contract::load_from( "sway/contracts/transaction_block_height/out/release/transaction_block_height.bin", LoadConfiguration::default(), ) .map(|loaded_contract| { loaded_contract.deploy( &wallet, TxPolicies::default() .with_maturity(maturity) .with_expiration(expiration), ) }) }; { let err = deploy_w_maturity_and_expiration()? .await .expect_err("maturity not reached"); assert!(err.to_string().contains("TransactionMaturity")); } { provider.produce_blocks(15, None).await?; deploy_w_maturity_and_expiration()? .await .expect("should succeed. Block height between `maturity` and `expiration`"); } { provider.produce_blocks(15, None).await?; let err = deploy_w_maturity_and_expiration()? .await .expect_err("expiration reached"); assert!(err.to_string().contains("TransactionExpiration")); } Ok(()) } #[tokio::test] async fn test_gas_forwarded_defaults_to_tx_limit() -> Result<()> { setup_program_test!( Wallets("wallet"), Abigen(Contract( name = "TestContract", project = "e2e/sway/contracts/contract_test" )), Deploy( name = "contract_instance", contract = "TestContract", wallet = "wallet", random_salt = false, ), ); // The gas used by the script to call a contract and forward remaining gas limit. let gas_used_by_script = 205; let gas_limit = 225_883; let response = contract_instance .methods() .initialize_counter(42) .with_tx_policies(TxPolicies::default().with_script_gas_limit(gas_limit)) .call() .await?; let gas_forwarded = response .tx_status .receipts .iter() .find(|r| matches!(r, Receipt::Call { .. })) .unwrap() .gas() .unwrap(); assert_eq!(gas_limit, gas_forwarded + gas_used_by_script); Ok(()) } #[tokio::test] async fn test_amount_and_asset_forwarding() -> Result<()> { setup_program_test!( Wallets("wallet"), Abigen(Contract( name = "TokenContract", project = "e2e/sway/contracts/token_ops" )), Deploy( name = "contract_instance", contract = "TokenContract", wallet = "wallet", random_salt = false, ), ); let contract_id = contract_instance.contract_id(); let contract_methods = contract_instance.methods(); let asset_id = contract_id.asset_id(&SubAssetId::zeroed()); let mut balance_response = contract_methods .get_balance(contract_id, asset_id) .call() .await?; assert_eq!(balance_response.value, 0); contract_methods.mint_coins(5_000_000).call().await?; balance_response = contract_methods .get_balance(contract_id, asset_id) .call() .await?; assert_eq!(balance_response.value, 5_000_000); let tx_policies = TxPolicies::default().with_script_gas_limit(1_000_000); // Forward 1_000_000 coin amount of base asset_id // this is a big number for checking that amount can be a u64 let call_params = CallParameters::default().with_amount(1_000_000); let response = contract_methods .get_msg_amount() .with_tx_policies(tx_policies) .call_params(call_params)? .call() .await?; assert_eq!(response.value, 1_000_000); let call_response = response .tx_status .receipts .iter() .find(|&r| matches!(r, Receipt::Call { .. })); assert!(call_response.is_some()); assert_eq!(call_response.unwrap().amount().unwrap(), 1_000_000); assert_eq!( call_response.unwrap().asset_id().unwrap(), &AssetId::zeroed() ); let address = wallet.address(); // withdraw some tokens to wallet contract_methods .transfer(1_000_000, asset_id, address.into()) .with_variable_output_policy(VariableOutputPolicy::Exactly(1)) .call() .await?; let asset_id = AssetId::from(*contract_id); let call_params = CallParameters::default() .with_amount(0) .with_asset_id(asset_id); let tx_policies = TxPolicies::default().with_script_gas_limit(1_000_000); let response = contract_methods .get_msg_amount() .with_tx_policies(tx_policies) .call_params(call_params)? .call() .await?; assert_eq!(response.value, 0); let call_response = response .tx_status .receipts .iter() .find(|&r| matches!(r, Receipt::Call { .. })); assert!(call_response.is_some()); assert_eq!(call_response.unwrap().amount().unwrap(), 0); assert_eq!( call_response.unwrap().asset_id().unwrap(), &AssetId::from(*contract_id) ); Ok(()) } #[tokio::test] async fn test_gas_errors() -> Result<()> { let signer = PrivateKeySigner::random(&mut thread_rng()); let number_of_coins = 1; let amount_per_coin = 1_000_000; let coins = setup_single_asset_coins( signer.address(), AssetId::zeroed(), number_of_coins, amount_per_coin, ); let provider = setup_test_provider(coins.clone(), vec![], None, None).await?; let wallet = Wallet::new(signer, provider.clone()); setup_program_test!( Abigen(Contract( name = "TestContract", project = "e2e/sway/contracts/contract_test" )), Deploy( name = "contract_instance", contract = "TestContract", wallet = "wallet", random_salt = false, ), ); // Test running out of gas. Gas price as `None` will be 0. let gas_limit = 42; let contract_instance_call = contract_instance .methods() .initialize_counter(42) // Build the ABI call .with_tx_policies(TxPolicies::default().with_script_gas_limit(gas_limit)); // Test that the call will use more gas than the gas limit let total_gas = contract_instance_call .estimate_transaction_cost(None, None) .await? .total_gas; assert!(total_gas > gas_limit); let response = contract_instance_call .call() .await .expect_err("should error"); let expected = "transaction reverted: OutOfGas"; assert!(response.to_string().starts_with(expected)); // Test for insufficient base asset amount to pay for the transaction fee let response = contract_instance .methods() .initialize_counter(42) // Build the ABI call .with_tx_policies(TxPolicies::default().with_tip(100_000_000_000)) .call() .await .expect_err("should error"); let expected = "Response errors; Validity(InsufficientFeeAmount"; assert!(response.to_string().contains(expected)); Ok(()) } #[tokio::test] async fn test_call_param_gas_errors() -> Result<()> { setup_program_test!( Wallets("wallet"), Abigen(Contract( name = "TestContract", project = "e2e/sway/contracts/contract_test" )), Deploy( name = "contract_instance", contract = "TestContract", wallet = "wallet", random_salt = false, ), ); // Transaction gas_limit is sufficient, call gas_forwarded is too small let contract_methods = contract_instance.methods(); let response = contract_methods .initialize_counter(42) .with_tx_policies(TxPolicies::default().with_script_gas_limit(446000)) .call_params(CallParameters::default().with_gas_forwarded(1))? .call() .await .expect_err("should error"); let expected = "transaction reverted: OutOfGas"; assert!(response.to_string().starts_with(expected)); // Call params gas_forwarded exceeds transaction limit let response = contract_methods .initialize_counter(42) .with_tx_policies(TxPolicies::default().with_script_gas_limit(1)) .call_params(CallParameters::default().with_gas_forwarded(1_000))? .call() .await .expect_err("should error"); assert!(response.to_string().contains(expected)); Ok(()) } #[tokio::test] async fn test_get_gas_used() -> Result<()> { setup_program_test!( Wallets("wallet"), Abigen(Contract( name = "TestContract", project = "e2e/sway/contracts/contract_test" )), Deploy( name = "contract_instance", contract = "TestContract", wallet = "wallet", random_salt = false, ), ); let total_gas = contract_instance .methods() .initialize_counter(42) .call() .await? .tx_status .total_gas; assert!(total_gas > 0); Ok(()) } #[tokio::test] async fn test_parse_block_time() -> Result<()> { let signer = PrivateKeySigner::random(&mut thread_rng()); let asset_id = AssetId::zeroed(); let coins = setup_single_asset_coins(signer.address(), asset_id, 1, DEFAULT_COIN_AMOUNT); let provider = setup_test_provider(coins.clone(), vec![], None, None).await?; let wallet = Wallet::new(signer, provider.clone()); let tx_policies = TxPolicies::default().with_script_gas_limit(2000); let wallet_2 = wallet.lock(); let tx_response = wallet .transfer(wallet_2.address(), 100, asset_id, tx_policies) .await?; let tx_response = wallet .try_provider()? .get_transaction_by_id(&tx_response.tx_id) .await? .unwrap(); assert!(tx_response.time.is_some()); let block = wallet .try_provider()? .block_by_height(tx_response.block_height.unwrap()) .await? .unwrap(); assert!(block.header.time.is_some()); Ok(()) } #[tokio::test] async fn test_get_spendable_with_exclusion() -> Result<()> { let coin_amount_1 = 1000; let coin_amount_2 = 500; let signer = PrivateKeySigner::random(&mut thread_rng()); let address = signer.address(); let coins = [coin_amount_1, coin_amount_2] .into_iter() .flat_map(|amount| setup_single_asset_coins(address, AssetId::zeroed(), 1, amount)) .collect::>(); let message_amount = 200; let message = given_a_message(address, message_amount); let coin_1_utxo_id = coins[0].utxo_id; let coin_2_utxo_id = coins[1].utxo_id; let message_nonce = message.nonce; let provider = setup_test_provider(coins, vec![message], None, None).await?; let wallet = Wallet::new(signer, provider.clone()); let requested_amount = coin_amount_1 + coin_amount_2 + message_amount; let consensus_parameters = provider.consensus_parameters().await?; { let resources = wallet .get_spendable_resources( *consensus_parameters.base_asset_id(), requested_amount.into(), None, ) .await .unwrap(); assert_eq!(resources.len(), 3); } { let filter = ResourceFilter { from: wallet.address(), amount: coin_amount_1.into(), excluded_utxos: vec![coin_2_utxo_id], excluded_message_nonces: vec![message_nonce], ..Default::default() }; let resources = provider.get_spendable_resources(filter).await.unwrap(); match resources.as_slice() { [CoinType::Coin(coin)] => { assert_eq!(coin.utxo_id, coin_1_utxo_id); } _ => { panic!("This shouldn't happen!") } } } Ok(()) } fn given_a_message(address: Address, message_amount: u64) -> Message { setup_single_message( Address::default(), address, message_amount, 0.into(), vec![], ) } fn convert_to_datetime(timestamp: u64) -> DateTime { let unix = tai64::Tai64(timestamp).to_unix(); DateTime::from_timestamp(unix, 0).unwrap() } /// This test is here in addition to `can_set_custom_block_time` because even though this test /// passed, the Sway `timestamp` function didn't take into account the block time change. This /// was fixed and this test is here to demonstrate the fix. #[tokio::test] async fn test_sway_timestamp() -> Result<()> { let block_time = 1u32; // seconds let provider_config = NodeConfig { block_production: Trigger::Interval { block_time: std::time::Duration::from_secs(block_time.into()), }, ..NodeConfig::default() }; let mut wallets = launch_custom_provider_and_get_wallets( WalletsConfig::new(Some(1), Some(1), Some(100)), Some(provider_config), None, ) .await?; let wallet = wallets.pop().unwrap(); let provider = wallet.provider(); setup_program_test!( Abigen(Contract( name = "TestContract", project = "e2e/sway/contracts/block_timestamp" )), Deploy( name = "contract_instance", contract = "TestContract", wallet = "wallet", random_salt = false, ), ); let origin_timestamp = provider.latest_block_time().await?.unwrap(); let methods = contract_instance.methods(); let response = methods.return_timestamp().call().await?; let mut expected_datetime = origin_timestamp.add(Duration::try_seconds(block_time as i64).unwrap()); assert_eq!(convert_to_datetime(response.value), expected_datetime); let blocks_to_produce = 600; provider.produce_blocks(blocks_to_produce, None).await?; let response = methods.return_timestamp().call().await?; // `produce_blocks` call expected_datetime = expected_datetime .add(Duration::try_seconds((block_time * blocks_to_produce) as i64).unwrap()); // method call expected_datetime = expected_datetime.add(Duration::try_seconds(block_time as i64).unwrap()); assert_eq!(convert_to_datetime(response.value), expected_datetime); assert_eq!( provider.latest_block_time().await?.unwrap(), expected_datetime ); Ok(()) } #[cfg(feature = "coin-cache")] async fn create_transfer(wallet: &Wallet, amount: u64, to: Address) -> Result { let asset_id = AssetId::zeroed(); let inputs = wallet .get_asset_inputs_for_amount(asset_id, amount.into(), None) .await?; let outputs = wallet.get_asset_outputs_for_amount(to, asset_id, amount); let mut tb = ScriptTransactionBuilder::prepare_transfer(inputs, outputs, TxPolicies::default()); wallet.adjust_for_fee(&mut tb, amount.into()).await?; wallet.add_witnesses(&mut tb)?; tb.build(wallet.provider()).await } #[cfg(feature = "coin-cache")] #[tokio::test] async fn transactions_with_the_same_utxo() -> Result<()> { use fuels::types::errors::transaction; let wallet_1 = launch_provider_and_get_wallet().await?; let provider = wallet_1.provider(); let wallet_2 = Wallet::random(&mut thread_rng(), provider.clone()); let tx_1 = create_transfer(&wallet_1, 100, wallet_2.address()).await?; let tx_2 = create_transfer(&wallet_1, 101, wallet_2.address()).await?; let _tx_id = provider.send_transaction(tx_1).await?; let res = provider.send_transaction(tx_2).await; let err = res.expect_err("is error"); assert!(matches!( err, Error::Transaction(transaction::Reason::Validation(..)) )); assert!( err.to_string() .contains("was submitted recently in a transaction ") ); Ok(()) } #[cfg(feature = "coin-cache")] #[tokio::test] async fn coin_caching() -> Result<()> { let amount = 1000; let num_coins = 50; let mut wallets = launch_custom_provider_and_get_wallets( WalletsConfig::new(Some(1), Some(num_coins), Some(amount)), Some(NodeConfig::default()), None, ) .await?; let wallet_1 = wallets.pop().unwrap(); let provider = wallet_1.provider(); let wallet_2 = Wallet::random(&mut thread_rng(), provider.clone()); // Consecutively send transfer txs. Without caching, the txs will // end up trying to use the same input coins because 'get_spendable_coins()' // won't filter out recently used coins. let num_iterations = 10; let amount_to_send = 100; let mut tx_ids = vec![]; for _ in 0..num_iterations { let tx = create_transfer(&wallet_1, amount_to_send, wallet_2.address()).await?; let tx_id = provider.send_transaction(tx).await?; tx_ids.push(tx_id); } provider.produce_blocks(10, None).await?; // Confirm all txs are settled for tx_id in tx_ids { let status = provider.tx_status(&tx_id).await?; assert!(matches!(status, TxStatus::Success { .. })); } // Verify the transfers were successful assert_eq!( wallet_2.get_asset_balance(&AssetId::zeroed()).await?, (num_iterations * amount_to_send) as u128 ); Ok(()) } #[cfg(feature = "coin-cache")] async fn create_revert_tx(wallet: &Wallet) -> Result { let script = std::fs::read("sway/scripts/reverting/out/release/reverting.bin")?; let amount = 1u64; let asset_id = AssetId::zeroed(); let inputs = wallet .get_asset_inputs_for_amount(asset_id, amount.into(), None) .await?; let outputs = wallet.get_asset_outputs_for_amount(Address::default(), asset_id, amount); let mut tb = ScriptTransactionBuilder::prepare_transfer(inputs, outputs, TxPolicies::default()) .with_script(script); wallet.adjust_for_fee(&mut tb, amount.into()).await?; wallet.add_witnesses(&mut tb)?; tb.build(wallet.provider()).await } #[cfg(feature = "coin-cache")] #[tokio::test] async fn test_cache_invalidation_on_await() -> Result<()> { let block_time = 1u32; let provider_config = NodeConfig { block_production: Trigger::Interval { block_time: std::time::Duration::from_secs(block_time.into()), }, ..NodeConfig::default() }; // create wallet with 1 coin so that the cache prevents further // spending unless the coin is invalidated from the cache let mut wallets = launch_custom_provider_and_get_wallets( WalletsConfig::new(Some(1), Some(1), Some(100)), Some(provider_config), None, ) .await?; let wallet = wallets.pop().unwrap(); let provider = wallet.provider(); let tx = create_revert_tx(&wallet).await?; // Pause time so that the cache doesn't invalidate items based on TTL tokio::time::pause(); // tx inputs should be cached and then invalidated due to the tx failing let tx_status = provider.send_transaction_and_await_commit(tx).await?; assert!(matches!(tx_status, TxStatus::Failure { .. })); let consensus_parameters = provider.consensus_parameters().await?; let coins = wallet .get_spendable_resources(*consensus_parameters.base_asset_id(), 1, None) .await?; assert_eq!(coins.len(), 1); Ok(()) } #[tokio::test] async fn can_fetch_mint_transactions() -> Result<()> { setup_program_test!( Wallets("wallet"), Abigen(Contract( name = "TestContract", project = "e2e/sway/contracts/contract_test" )), Deploy( name = "contract_instance", contract = "TestContract", wallet = "wallet", random_salt = false, ), ); let provider = wallet.provider(); let transactions = provider .get_transactions(PaginationRequest { cursor: None, results: 20, direction: PageDirection::Forward, }) .await? .results; // TODO: remove once (fuels-rs#1093)[https://github.com/FuelLabs/fuels-rs/issues/1093] is in // until then the type is explicitly mentioned to check that we're reexporting it through fuels let _: ::fuels::types::transaction::MintTransaction = transactions .into_iter() .find_map(|tx| match tx.transaction { TransactionType::Mint(tx) => Some(tx), _ => None, }) .expect("Should have had at least one mint transaction"); Ok(()) } #[tokio::test] async fn test_build_with_provider() -> Result<()> { let wallet = launch_provider_and_get_wallet().await?; let provider = wallet.provider(); let receiver = Wallet::random(&mut thread_rng(), provider.clone()); let consensus_parameters = provider.consensus_parameters().await?; let inputs = wallet .get_asset_inputs_for_amount(*consensus_parameters.base_asset_id(), 100, None) .await?; let outputs = wallet.get_asset_outputs_for_amount( receiver.address(), *consensus_parameters.base_asset_id(), 100, ); let mut tb = ScriptTransactionBuilder::prepare_transfer(inputs, outputs, TxPolicies::default()); wallet.add_witnesses(&mut tb)?; let tx = tb.build(provider).await?; provider.send_transaction_and_await_commit(tx).await?; let receiver_balance = receiver .get_asset_balance(consensus_parameters.base_asset_id()) .await?; assert_eq!(receiver_balance, 100); Ok(()) } #[tokio::test] async fn send_transaction_and_await_status() -> Result<()> { let wallet = launch_provider_and_get_wallet().await?; let provider = wallet.provider(); let consensus_parameters = provider.consensus_parameters().await?; let inputs = wallet .get_asset_inputs_for_amount(*consensus_parameters.base_asset_id(), 100, None) .await?; let outputs = wallet.get_asset_outputs_for_amount( Address::default(), *consensus_parameters.base_asset_id(), 100, ); // Given let mut tb = ScriptTransactionBuilder::prepare_transfer(inputs, outputs, TxPolicies::default()); wallet.add_witnesses(&mut tb)?; let tx = tb.build(provider).await?; // When let status = provider.send_transaction_and_await_status(tx, true).await?; // Then assert_eq!(status.len(), 3); assert!(status.iter().enumerate().all(|(i, tx_status)| { matches!( (i, tx_status.clone().unwrap()), (0, TxStatus::Submitted) | (1, TxStatus::PreconfirmationSuccess { .. }) | (2, TxStatus::Success { .. }) ) })); Ok(()) } #[tokio::test] async fn send_transaction_and_subscribe_status() -> Result<()> { let config = NodeConfig { block_production: Trigger::Never, ..NodeConfig::default() }; let wallet = launch_custom_provider_and_get_wallets(WalletsConfig::default(), Some(config), None) .await?[0] .clone(); let provider = wallet.provider(); let consensus_parameters = provider.consensus_parameters().await?; let inputs = wallet .get_asset_inputs_for_amount(*consensus_parameters.base_asset_id(), 100, None) .await?; let outputs = wallet.get_asset_outputs_for_amount( Address::default(), *consensus_parameters.base_asset_id(), 100, ); // Given let mut tb = ScriptTransactionBuilder::prepare_transfer(inputs, outputs, TxPolicies::default()); wallet.add_witnesses(&mut tb)?; let tx = tb.build(provider).await?; let tx_id = tx.id(consensus_parameters.chain_id()); // When let mut statuses = provider.subscribe_transaction_status(&tx_id, true).await?; let _ = provider.send_transaction(tx).await?; // Then assert!(matches!( statuses.next().await.unwrap()?, TxStatus::Submitted )); provider.produce_blocks(1, None).await?; assert!(matches!( statuses.next().await.unwrap()?, TxStatus::PreconfirmationSuccess { .. } )); assert!(matches!( statuses.next().await.unwrap()?, TxStatus::Success { .. } )); Ok(()) } #[tokio::test] async fn can_produce_blocks_with_trig_never() -> Result<()> { let config = NodeConfig { block_production: Trigger::Never, ..NodeConfig::default() }; let wallets = launch_custom_provider_and_get_wallets(WalletsConfig::default(), Some(config), None) .await?; let wallet = &wallets[0]; let provider = wallet.provider(); let consensus_parameters = provider.consensus_parameters().await?; let inputs = wallet .get_asset_inputs_for_amount(*consensus_parameters.base_asset_id(), 100, None) .await?; let outputs = wallet.get_asset_outputs_for_amount( Address::default(), *consensus_parameters.base_asset_id(), 100, ); let mut tb = ScriptTransactionBuilder::prepare_transfer(inputs, outputs, TxPolicies::default()); wallet.add_witnesses(&mut tb)?; let tx = tb.build(provider).await?; let tx_id = tx.id(consensus_parameters.chain_id()); provider.send_transaction(tx).await?; provider.produce_blocks(1, None).await?; tokio::time::sleep(std::time::Duration::from_millis(500)).await; let status = provider.tx_status(&tx_id).await?; assert!(matches!(status, TxStatus::Success { .. })); Ok(()) } #[tokio::test] async fn can_upload_executor_and_trigger_upgrade() -> Result<()> { let signer = PrivateKeySigner::random(&mut thread_rng()); // Need more coins to avoid "not enough coins to fit the target" let num_coins = 100; let coins = setup_single_asset_coins( signer.address(), AssetId::zeroed(), num_coins, DEFAULT_COIN_AMOUNT, ); let mut chain_config = ChainConfig::local_testnet(); chain_config .consensus_parameters .set_privileged_address(signer.address()); let provider = setup_test_provider(coins, vec![], None, Some(chain_config)).await?; let wallet = Wallet::new(signer, provider.clone()); // This is downloaded over in `build.rs` let executor = std::fs::read(Path::new(env!("OUT_DIR")).join("fuel-core-wasm-executor.wasm"))?; let subsection_size = 65536; let subsections = UploadSubsection::split_bytecode(&executor, subsection_size).unwrap(); let root = subsections[0].root; for subsection in subsections { let mut builder = UploadTransactionBuilder::prepare_subsection_upload(subsection, TxPolicies::default()); wallet.add_witnesses(&mut builder)?; wallet.adjust_for_fee(&mut builder, 0).await?; let tx = builder.build(&provider).await?; provider.send_transaction_and_await_commit(tx).await?; } let mut builder = UpgradeTransactionBuilder::prepare_state_transition_upgrade(root, TxPolicies::default()); wallet.add_witnesses(&mut builder)?; wallet.adjust_for_fee(&mut builder, 0).await?; let tx = builder.build(provider.clone()).await?; provider.send_transaction(tx).await?; Ok(()) } #[tokio::test] async fn tx_respects_policies() -> Result<()> { setup_program_test!( Wallets("wallet"), Abigen(Contract( name = "TestContract", project = "e2e/sway/contracts/contract_test" )), Deploy( name = "contract_instance", contract = "TestContract", wallet = "wallet", random_salt = false, ), ); let tip = 22; let witness_limit = 1000; let maturity = 4; let expiration = 128; let max_fee = 10_000; let script_gas_limit = 3000; let owner_index = 1; let tx_policies = TxPolicies::new( Some(tip), Some(witness_limit), Some(maturity), Some(expiration), Some(max_fee), Some(script_gas_limit), Some(owner_index), ); // advance the block height to ensure the maturity is respected let provider = wallet.provider(); provider.produce_blocks(4, None).await?; // trigger a transaction that contains script code to verify // that policies precede estimated values let response = contract_instance .methods() .initialize_counter(42) .with_tx_policies(tx_policies) .call() .await?; let tx_response = provider .get_transaction_by_id(&response.tx_id.unwrap()) .await? .expect("tx should exist"); let script = match tx_response.transaction { TransactionType::Script(tx) => tx, _ => panic!("expected script transaction"), }; assert_eq!(script.maturity().unwrap(), maturity); assert_eq!(script.expiration().unwrap(), expiration); assert_eq!(script.tip().unwrap(), tip); assert_eq!(script.witness_limit().unwrap(), witness_limit); assert_eq!(script.max_fee().unwrap(), max_fee); assert_eq!(script.gas_limit(), script_gas_limit); assert_eq!(script.owner().unwrap(), owner_index); Ok(()) } #[tokio::test] #[ignore] // TODO: https://github.com/FuelLabs/fuels-rs/issues/1581 async fn can_setup_static_gas_price() -> Result<()> { let expected_gas_price = 474; let node_config = NodeConfig { starting_gas_price: expected_gas_price, ..Default::default() }; let provider = setup_test_provider(vec![], vec![], Some(node_config), None).await?; let gas_price = provider.estimate_gas_price(0).await?.gas_price; let da_cost = 1000; assert_eq!(gas_price, da_cost + expected_gas_price); Ok(()) } #[tokio::test] async fn tx_with_witness_data() -> Result<()> { use fuel_asm::{GTFArgs, op}; let wallet = launch_provider_and_get_wallet().await?; let provider = wallet.provider(); let receiver = Wallet::random(&mut thread_rng(), provider.clone()); let consensus_parameters = provider.consensus_parameters().await?; let inputs = wallet .get_asset_inputs_for_amount(*consensus_parameters.base_asset_id(), 10000, None) .await?; let outputs = wallet.get_asset_outputs_for_amount( receiver.address(), *consensus_parameters.base_asset_id(), 1, ); let mut tb = ScriptTransactionBuilder::prepare_transfer(inputs, outputs, TxPolicies::default()); wallet.add_witnesses(&mut tb)?; // we test that the witness data wasn't tempered with during the build (gas estimation) process // if the witness data is tempered with, the estimation will be off and the transaction // will error out with `OutOfGas` let script: Vec = vec![ // load witness data into register 0x10 op::gtf(0x10, 0x00, GTFArgs::WitnessData.into()), op::lw(0x10, 0x10, 0x00), // load expected value into register 0x11 op::movi(0x11, 0x0f), // load the offset of the revert instruction into register 0x12 op::movi(0x12, 0x08), // compare the two values and jump to the revert instruction if they are not equal op::jne(0x10, 0x11, 0x12), // do some expensive operation so gas estimation is higher if comparison passes op::gtf(0x13, 0x01, GTFArgs::WitnessData.into()), op::gtf(0x14, 0x01, GTFArgs::WitnessDataLength.into()), op::aloc(0x14), op::eck1(RegId::HP, 0x13, 0x13), // return the witness data op::ret(0x10), op::rvrt(RegId::ZERO), ] .into_iter() .collect(); tb.script = script; let expected_data = 15u64; let witness = Witness::from(expected_data.to_be_bytes().to_vec()); tb.witnesses_mut().push(witness); let tx = tb .with_tx_policies(TxPolicies::default().with_witness_limit(1000)) .build(provider) .await?; let status = provider.send_transaction_and_await_commit(tx).await?; match status { TxStatus::Success(Success { receipts, .. }) => { let ret: u64 = receipts .as_ref() .iter() .find_map(|receipt| match receipt { Receipt::Return { val, .. } => Some(*val), _ => None, }) .expect("should have return value"); assert_eq!(ret, expected_data); } _ => panic!("expected success status"), } Ok(()) } #[tokio::test] async fn contract_call_with_impersonation() -> Result<()> { let provider_config = NodeConfig { utxo_validation: false, ..NodeConfig::default() }; let mut wallets = launch_custom_provider_and_get_wallets( WalletsConfig::new(Some(1), Some(10), Some(1000)), Some(provider_config), None, ) .await?; let wallet = wallets.pop().unwrap(); let provider = wallet.provider(); let impersonator = Wallet::new(FakeSigner::new(wallet.address()), provider.clone()); abigen!(Contract( name = "MyContract", abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json" )); let contract_id = Contract::load_from( "sway/contracts/contract_test/out/release/contract_test.bin", LoadConfiguration::default(), )? .deploy_if_not_exists(&wallet, TxPolicies::default()) .await? .contract_id; let contract_instance = MyContract::new(contract_id, impersonator.clone()); // The gas used by the script to call a contract and forward remaining gas limit. contract_instance .methods() .initialize_counter(42) .call() .await?; Ok(()) } #[tokio::test] async fn is_account_query_test() -> Result<()> { { let wallet = launch_provider_and_get_wallet().await?; let provider = wallet.provider().clone(); let blob = Blob::new(vec![1; 100]); let blob_id = blob.id(); let is_account = provider.is_user_account(blob_id).await?; assert!(is_account); let mut tb = BlobTransactionBuilder::default().with_blob(blob); wallet.adjust_for_fee(&mut tb, 0).await?; wallet.add_witnesses(&mut tb)?; let tx = tb.build(provider.clone()).await?; provider .send_transaction_and_await_commit(tx) .await? .check(None)?; let is_account = provider.is_user_account(blob_id).await?; assert!(!is_account); } { let wallet = launch_provider_and_get_wallet().await?; let provider = wallet.provider().clone(); let contract = Contract::load_from( "sway/contracts/contract_test/out/release/contract_test.bin", LoadConfiguration::default(), )?; let contract_id = contract.contract_id(); let is_account = provider.is_user_account(*contract_id).await?; assert!(is_account); contract.deploy(&wallet, TxPolicies::default()).await?; let is_account = provider.is_user_account(*contract_id).await?; assert!(!is_account); } { let wallet = launch_provider_and_get_wallet().await?; let provider = wallet.provider().clone(); let mut tb = ScriptTransactionBuilder::default(); wallet.adjust_for_fee(&mut tb, 0).await?; wallet.add_witnesses(&mut tb)?; let tx = tb.build(provider.clone()).await?; let consensus_parameters = provider.consensus_parameters().await?; let tx_id = tx.id(consensus_parameters.chain_id()); let is_account = provider.is_user_account(tx_id).await?; assert!(is_account); provider .send_transaction_and_await_commit(tx) .await? .check(None)?; let is_account = provider.is_user_account(tx_id).await?; assert!(!is_account); } Ok(()) } #[tokio::test] async fn script_tx_get_owner_returns_owner_when_policy_set_multiple_inputs() -> Result<()> { use fuel_asm::{GMArgs, op}; let amount = 1000; let num_coins = 1; let mut wallets = launch_custom_provider_and_get_wallets( WalletsConfig::new(Some(3), Some(num_coins), Some(amount)), Some(NodeConfig::default()), None, ) .await?; let wallet_0 = wallets.pop().unwrap(); let wallet_1 = wallets.pop().unwrap(); let wallet_2 = wallets.pop().unwrap(); let provider = wallet_0.provider().clone(); let consensus_parameters = provider.consensus_parameters().await?; let inputs_0 = wallet_0 .get_asset_inputs_for_amount(*consensus_parameters.base_asset_id(), amount as u128, None) .await?; let inputs_1 = wallet_1 .get_asset_inputs_for_amount(*consensus_parameters.base_asset_id(), amount as u128, None) .await?; let inputs_2 = wallet_2 .get_asset_inputs_for_amount(*consensus_parameters.base_asset_id(), amount as u128, None) .await?; let mut inputs = vec![]; inputs.extend(inputs_0); inputs.extend(inputs_1); inputs.extend(inputs_2); let tx_policies = TxPolicies::default().with_owner(1); let mut tb = ScriptTransactionBuilder::prepare_transfer(inputs, vec![], tx_policies).enable_burn(true); wallet_0.add_witnesses(&mut tb)?; wallet_1.add_witnesses(&mut tb)?; wallet_2.add_witnesses(&mut tb)?; let script = vec![ op::gm_args(0x20, GMArgs::GetOwner), op::movi(0x21, 32), op::retd(0x20, 0x21), ] .into_iter() .collect(); tb.script = script; let expected_data = wallet_1.address(); let tx = tb.build(&provider).await?; let status = provider.send_transaction_and_await_commit(tx).await?; match status { TxStatus::Success(Success { receipts, .. }) => { let ret = receipts .as_ref() .iter() .find_map(|receipt| match receipt { Receipt::ReturnData { data, .. } if data.is_some() => { Some(data.clone().unwrap()) } _ => None, }) .expect("should have return value"); assert_eq!(ret, expected_data.as_ref().to_vec().into()); } _ => panic!("expected success status"), } Ok(()) } #[tokio::test] async fn script_tx_get_owner_panics_when_policy_unset_multiple_inputs() -> Result<()> { use fuel_asm::{GMArgs, op}; let amount = 1000; let num_coins = 1; let mut wallets = launch_custom_provider_and_get_wallets( WalletsConfig::new(Some(3), Some(num_coins), Some(amount)), Some(NodeConfig::default()), None, ) .await?; let wallet_0 = wallets.pop().unwrap(); let wallet_1 = wallets.pop().unwrap(); let wallet_2 = wallets.pop().unwrap(); let provider = wallet_0.provider().clone(); let consensus_parameters = provider.consensus_parameters().await?; let inputs_0 = wallet_0 .get_asset_inputs_for_amount(*consensus_parameters.base_asset_id(), amount as u128, None) .await?; let inputs_1 = wallet_1 .get_asset_inputs_for_amount(*consensus_parameters.base_asset_id(), amount as u128, None) .await?; let inputs_2 = wallet_2 .get_asset_inputs_for_amount(*consensus_parameters.base_asset_id(), amount as u128, None) .await?; let mut inputs = vec![]; inputs.extend(inputs_0); inputs.extend(inputs_1); inputs.extend(inputs_2); let mut tb = ScriptTransactionBuilder::prepare_transfer(inputs, vec![], TxPolicies::default()) .enable_burn(true); wallet_0.add_witnesses(&mut tb)?; wallet_1.add_witnesses(&mut tb)?; wallet_2.add_witnesses(&mut tb)?; let script = vec![ op::gm_args(0x20, GMArgs::GetOwner), op::movi(0x21, 32), op::retd(0x20, 0x21), ] .into_iter() .collect(); tb.script = script; let tx = tb.build(&provider).await?; let status = provider.send_transaction_and_await_commit(tx).await?; match status { TxStatus::Failure(Failure { .. }) => {} _ => panic!("expected failure status"), } Ok(()) } #[tokio::test] async fn script_tx_get_owner_returns_owner_when_policy_unset_all_inputs_same_owner() -> Result<()> { use fuel_asm::{GMArgs, op}; let wallet = launch_provider_and_get_wallet().await?; let provider = wallet.provider(); let receiver = Wallet::random(&mut thread_rng(), provider.clone()); let consensus_parameters = provider.consensus_parameters().await?; let inputs = wallet .get_asset_inputs_for_amount(*consensus_parameters.base_asset_id(), 1000, None) .await?; let outputs = wallet.get_asset_outputs_for_amount( receiver.address(), *consensus_parameters.base_asset_id(), 1, ); let tx_policies = TxPolicies::default(); let mut tb = ScriptTransactionBuilder::prepare_transfer(inputs, outputs, tx_policies); wallet.add_witnesses(&mut tb)?; let script = vec![ op::gm_args(0x20, GMArgs::GetOwner), op::movi(0x21, 32), op::retd(0x20, 0x21), ] .into_iter() .collect(); tb.script = script; let expected_data = wallet.address(); let tx = tb.build(provider).await?; let status = provider.send_transaction_and_await_commit(tx).await?; match status { TxStatus::Success(Success { receipts, .. }) => { let ret = receipts .as_ref() .iter() .find_map(|receipt| match receipt { Receipt::ReturnData { data, .. } if data.is_some() => { Some(data.clone().unwrap()) } _ => None, }) .expect("should have return value"); assert_eq!(ret, expected_data.as_ref().to_vec().into()); } _ => panic!("expected success status"), } Ok(()) } ================================================ FILE: e2e/tests/scripts.rs ================================================ use std::time::Duration; use fuel_tx::Output; use fuels::{ accounts::signers::private_key::PrivateKeySigner, client::{PageDirection, PaginationRequest}, core::{ Configurables, codec::{DecoderConfig, EncoderConfig}, traits::Tokenizable, }, prelude::*, programs::{DEFAULT_MAX_FEE_ESTIMATION_TOLERANCE, executable::Executable}, types::{Bits256, Identity}, }; use rand::thread_rng; #[tokio::test] async fn main_function_arguments() -> Result<()> { // ANCHOR: script_with_arguments // The abigen is used for the same purpose as with contracts (Rust bindings) abigen!(Script( name = "MyScript", abi = "e2e/sway/scripts/arguments/out/release/arguments-abi.json" )); let wallet = launch_provider_and_get_wallet().await?; let bin_path = "sway/scripts/arguments/out/release/arguments.bin"; let script_instance = MyScript::new(wallet, bin_path); let bim = Bimbam { val: 90 }; let bam = SugarySnack { twix: 100, mars: 1000, }; let result = script_instance.main(bim, bam).call().await?; let expected = Bimbam { val: 2190 }; assert_eq!(result.value, expected); // ANCHOR_END: script_with_arguments Ok(()) } #[tokio::test] async fn script_call_has_same_estimated_and_used_gas() -> Result<()> { setup_program_test!( Wallets("wallet"), Abigen(Script( name = "MyScript", project = "e2e/sway/scripts/basic_script" )), LoadScript( name = "script_instance", script = "MyScript", wallet = "wallet" ) ); let tolerance = Some(0.0); let block_horizon = Some(1); let a = 4u64; let b = 2u32; let estimated_total_gas = script_instance .main(a, b) .estimate_transaction_cost(tolerance, block_horizon) .await? .total_gas; let total_gas = script_instance.main(a, b).call().await?.tx_status.total_gas; assert_eq!(estimated_total_gas, total_gas); Ok(()) } #[tokio::test] async fn test_basic_script_with_tx_policies() -> Result<()> { setup_program_test!( Wallets("wallet"), Abigen(Script( name = "bimbam_script", project = "e2e/sway/scripts/basic_script" )), LoadScript( name = "script_instance", script = "bimbam_script", wallet = "wallet" ) ); let a = 1000u64; let b = 2000u32; let result = script_instance.main(a, b).call().await?; assert_eq!(result.value, "hello"); // ANCHOR: script_with_tx_policies let tx_policies = TxPolicies::default().with_script_gas_limit(1_000_000); let result = script_instance .main(a, b) .with_tx_policies(tx_policies) .call() .await?; // ANCHOR_END: script_with_tx_policies assert_eq!(result.value, "hello"); Ok(()) } #[tokio::test] async fn test_output_variable_estimation() -> Result<()> { setup_program_test!( Wallets("wallet"), Abigen(Script( name = "transfer_script", project = "e2e/sway/scripts/transfer_script" )), LoadScript( name = "script_instance", script = "transfer_script", wallet = "wallet" ) ); let provider = wallet.provider().clone(); let receiver = Wallet::random(&mut thread_rng(), provider); let amount = 1000; let asset_id = AssetId::zeroed(); let script_call = script_instance.main(amount, asset_id, Identity::Address(receiver.address())); let inputs = wallet .get_asset_inputs_for_amount(asset_id, amount.into(), None) .await?; let output = Output::change(wallet.address(), 0, asset_id); let _ = script_call .with_inputs(inputs) .with_outputs(vec![output]) .with_variable_output_policy(VariableOutputPolicy::EstimateMinimum) .call() .await?; let receiver_balance = receiver.get_asset_balance(&asset_id).await?; assert_eq!(receiver_balance, amount as u128); Ok(()) } #[tokio::test] async fn test_script_struct() -> Result<()> { setup_program_test!( Wallets("wallet"), Abigen(Script( name = "MyScript", project = "e2e/sway/scripts/script_struct" )), LoadScript( name = "script_instance", script = "MyScript", wallet = "wallet" ) ); let my_struct = MyStruct { number: 42, boolean: true, }; let response = script_instance.main(my_struct).call().await?; assert_eq!(response.value, 42); Ok(()) } #[tokio::test] async fn test_script_enum() -> Result<()> { setup_program_test!( Wallets("wallet"), Abigen(Script( name = "MyScript", project = "e2e/sway/scripts/script_enum" )), LoadScript( name = "script_instance", script = "MyScript", wallet = "wallet" ) ); let my_enum = MyEnum::Two; let response = script_instance.main(my_enum).call().await?; assert_eq!(response.value, 2); Ok(()) } #[tokio::test] async fn test_script_array() -> Result<()> { setup_program_test!( Wallets("wallet"), Abigen(Script( name = "MyScript", project = "e2e/sway/scripts/script_array" )), LoadScript( name = "script_instance", script = "MyScript", wallet = "wallet" ) ); let my_array: [u64; 4] = [1, 2, 3, 4]; let response = script_instance.main(my_array).call().await?; assert_eq!(response.value, 10); Ok(()) } #[tokio::test] async fn can_configure_decoder_on_script_call() -> Result<()> { setup_program_test!( Wallets("wallet"), Abigen(Script( name = "MyScript", project = "e2e/sway/scripts/script_needs_custom_decoder" )), LoadScript( name = "script_instance", script = "MyScript", wallet = "wallet" ) ); { // Will fail if max_tokens too low script_instance .main(false) .with_decoder_config(DecoderConfig { max_tokens: 101, ..Default::default() }) .call() .await .expect_err( "Should fail because return type has more tokens than what is allowed by default", ); } { // When the token limit is bumped should pass let response = script_instance .main(false) .with_decoder_config(DecoderConfig { max_tokens: 1002, ..Default::default() }) .call() .await? .value .unwrap(); assert_eq!(response, [0u8; 1000]); } Ok(()) } #[tokio::test] async fn test_script_submit_and_response() -> Result<()> { setup_program_test!( Wallets("wallet"), Abigen(Script( name = "MyScript", project = "e2e/sway/scripts/script_struct" )), LoadScript( name = "script_instance", script = "MyScript", wallet = "wallet" ) ); let my_struct = MyStruct { number: 42, boolean: true, }; // ANCHOR: submit_response_script let submitted_tx = script_instance.main(my_struct).submit().await?; tokio::time::sleep(Duration::from_millis(500)).await; let value = submitted_tx.response().await?.value; // ANCHOR_END: submit_response_script assert_eq!(value, 42); Ok(()) } #[tokio::test] async fn test_script_transaction_builder() -> Result<()> { setup_program_test!( Wallets("wallet"), Abigen(Script( name = "MyScript", project = "e2e/sway/scripts/basic_script" )), LoadScript( name = "script_instance", script = "MyScript", wallet = "wallet" ) ); let provider = wallet.provider(); // ANCHOR: script_call_tb let script_call_handler = script_instance.main(1, 2); let mut tb = script_call_handler.transaction_builder().await?; // customize the builder... wallet.adjust_for_fee(&mut tb, 0).await?; wallet.add_witnesses(&mut tb)?; let tx = tb.build(provider).await?; let tx_id = provider.send_transaction(tx).await?; tokio::time::sleep(Duration::from_millis(500)).await; let tx_status = provider.tx_status(&tx_id).await?; let response = script_call_handler.get_response(tx_status)?; assert_eq!(response.value, "hello"); // ANCHOR_END: script_call_tb Ok(()) } #[tokio::test] async fn script_encoder_config_is_applied() { abigen!(Script( name = "MyScript", abi = "e2e/sway/scripts/basic_script/out/release/basic_script-abi.json" )); let wallet = launch_provider_and_get_wallet().await.expect(""); let bin_path = "sway/scripts/basic_script/out/release/basic_script.bin"; let script_instance_without_encoder_config = MyScript::new(wallet.clone(), bin_path); { let _encoding_ok = script_instance_without_encoder_config .main(1, 2) .call() .await .expect("should not fail as it uses the default encoder config"); } { let encoder_config = EncoderConfig { max_tokens: 1, ..Default::default() }; let script_instance_with_encoder_config = MyScript::new(wallet.clone(), bin_path).with_encoder_config(encoder_config); // uses 2 tokens when 1 is the limit let encoding_error = script_instance_with_encoder_config .main(1, 2) .call() .await .expect_err("should error"); assert!(encoding_error.to_string().contains( "cannot encode script call arguments: codec: token limit `1` reached while encoding" )); let encoding_error = script_instance_with_encoder_config .main(1, 2) .simulate(Execution::realistic()) .await .expect_err("should error"); assert!(encoding_error.to_string().contains( "cannot encode script call arguments: codec: token limit `1` reached while encoding" )); } } #[tokio::test] async fn simulations_can_be_made_without_coins() -> Result<()> { setup_program_test!( Wallets("wallet"), Abigen(Script( name = "MyScript", project = "e2e/sway/scripts/basic_script" )), LoadScript( name = "script_instance", script = "MyScript", wallet = "wallet" ) ); let provider = wallet.provider().clone(); let no_funds_wallet = Wallet::random(&mut thread_rng(), provider); let script_instance = script_instance.with_account(no_funds_wallet); let value = script_instance .main(1000, 2000) .simulate(Execution::state_read_only()) .await? .value; assert_eq!(value.as_ref(), "hello"); Ok(()) } #[tokio::test] async fn can_be_run_in_blobs_builder() -> Result<()> { abigen!(Script( abi = "e2e/sway/scripts/script_blobs/out/release/script_blobs-abi.json", name = "MyScript" )); let binary_path = "./sway/scripts/script_blobs/out/release/script_blobs.bin"; let wallet = launch_provider_and_get_wallet().await?; let provider = wallet.provider().clone(); // ANCHOR: preload_low_level let regular = Executable::load_from(binary_path)?; let configurables = MyScriptConfigurables::default().with_SECRET_NUMBER(10001)?; let loader = regular .convert_to_loader()? .with_configurables(configurables); // The Blob must be uploaded manually, otherwise the script code will revert. loader.upload_blob(wallet.clone()).await?; let encoder = fuels::core::codec::ABIEncoder::default(); let token = MyStruct { field_a: MyEnum::B(99), field_b: Bits256([17; 32]), } .into_token(); let data = encoder.encode(&[token])?; let mut tb = ScriptTransactionBuilder::default() .with_script(loader.code()) .with_script_data(data); wallet.adjust_for_fee(&mut tb, 0).await?; wallet.add_witnesses(&mut tb)?; let tx = tb.build(&provider).await?; let response = provider.send_transaction_and_await_commit(tx).await?; response.check(None)?; // ANCHOR_END: preload_low_level Ok(()) } #[tokio::test] async fn can_be_run_in_blobs_high_level() -> Result<()> { setup_program_test!( Abigen(Script( project = "e2e/sway/scripts/script_blobs", name = "MyScript" )), Wallets("wallet"), LoadScript(name = "my_script", script = "MyScript", wallet = "wallet") ); let configurables = MyScriptConfigurables::default().with_SECRET_NUMBER(10001)?; let mut my_script = my_script.with_configurables(configurables); let arg = MyStruct { field_a: MyEnum::B(99), field_b: Bits256([17; 32]), }; let secret = my_script .convert_into_loader() .await? .main(arg) .call() .await? .value; assert_eq!(secret, 10001); Ok(()) } #[tokio::test] async fn high_level_blob_upload_sets_max_fee_tolerance() -> Result<()> { let node_config = NodeConfig { starting_gas_price: 1000000000, ..Default::default() }; let signer = PrivateKeySigner::random(&mut thread_rng()); let coins = setup_single_asset_coins(signer.address(), AssetId::zeroed(), 1, u64::MAX); let provider = setup_test_provider(coins, vec![], Some(node_config), None).await?; let wallet = Wallet::new(signer, provider.clone()); setup_program_test!( Abigen(Script( project = "e2e/sway/scripts/script_blobs", name = "MyScript" )), LoadScript(name = "my_script", script = "MyScript", wallet = "wallet") ); let loader = Executable::from_bytes(std::fs::read( "sway/scripts/script_blobs/out/release/script_blobs.bin", )?) .convert_to_loader()?; let zero_tolerance_fee = { let mut tb = BlobTransactionBuilder::default() .with_blob(loader.blob()) .with_max_fee_estimation_tolerance(0.); wallet.adjust_for_fee(&mut tb, 0).await?; wallet.add_witnesses(&mut tb)?; let tx = tb.build(&provider).await?; tx.max_fee().unwrap() }; let mut my_script = my_script; my_script.convert_into_loader().await?; let max_fee_of_sent_blob_tx = provider .get_transactions(PaginationRequest { cursor: None, results: 20, direction: PageDirection::Forward, }) .await? .results .into_iter() .find_map(|tx| { if let TransactionType::Blob(blob_transaction) = tx.transaction { blob_transaction.max_fee() } else { None } }) .unwrap(); assert_eq!( max_fee_of_sent_blob_tx, (zero_tolerance_fee as f32 * (1.0 + DEFAULT_MAX_FEE_ESTIMATION_TOLERANCE)).ceil() as u64, "the blob upload tx should have had the max fee increased by the default estimation tolerance" ); Ok(()) } #[tokio::test] async fn no_data_section_blob_run() -> Result<()> { setup_program_test!( Abigen(Script( project = "e2e/sway/scripts/empty", name = "MyScript" )), Wallets("wallet"), LoadScript(name = "my_script", script = "MyScript", wallet = "wallet") ); let mut my_script = my_script; // ANCHOR: preload_high_level my_script.convert_into_loader().await?.main().call().await?; // ANCHOR_END: preload_high_level Ok(()) } #[tokio::test] async fn loader_script_calling_loader_proxy() -> Result<()> { setup_program_test!( Abigen( Contract( name = "MyContract", project = "e2e/sway/contracts/huge_contract" ), Contract(name = "MyProxy", project = "e2e/sway/contracts/proxy"), Script(name = "MyScript", project = "e2e/sway/scripts/script_proxy"), ), Wallets("wallet"), LoadScript(name = "my_script", script = "MyScript", wallet = "wallet") ); let contract_binary = "sway/contracts/huge_contract/out/release/huge_contract.bin"; let contract = Contract::load_from(contract_binary, LoadConfiguration::default())?; let contract_id = contract .convert_to_loader(100)? .deploy_if_not_exists(&wallet, TxPolicies::default()) .await? .contract_id; let contract_binary = "sway/contracts/proxy/out/release/proxy.bin"; let proxy_id = Contract::load_from(contract_binary, LoadConfiguration::default())? .convert_to_loader(100)? .deploy_if_not_exists(&wallet, TxPolicies::default()) .await? .contract_id; let proxy = MyProxy::new(proxy_id, wallet.clone()); proxy .methods() .set_target_contract(contract_id) .call() .await?; let mut my_script = my_script; let result = my_script .convert_into_loader() .await? .main(proxy_id) .with_contract_ids(&[contract_id, proxy_id]) .call() .await?; assert!(result.value); Ok(()) } #[tokio::test] async fn loader_can_be_presented_as_a_normal_script_with_shifted_configurables() -> Result<()> { abigen!(Script( abi = "e2e/sway/scripts/script_blobs/out/release/script_blobs-abi.json", name = "MyScript" )); let binary_path = "./sway/scripts/script_blobs/out/release/script_blobs.bin"; let wallet = launch_provider_and_get_wallet().await?; let provider = wallet.provider().clone(); let regular = Executable::load_from(binary_path)?; let configurables = MyScriptConfigurables::default().with_SECRET_NUMBER(10001)?; let loader = regular.clone().convert_to_loader()?; // The Blob must be uploaded manually, otherwise the script code will revert. loader.upload_blob(wallet.clone()).await?; let encoder = fuels::core::codec::ABIEncoder::default(); let token = MyStruct { field_a: MyEnum::B(99), field_b: Bits256([17; 32]), } .into_token(); let data = encoder.encode(&[token])?; let configurables: Configurables = configurables.into(); let offset = regular .configurables_offset_in_code()? .unwrap_or_else(|| regular.data_offset_in_code().unwrap()); let shifted_configurables = configurables .with_shifted_offsets(-(offset as i64)) .unwrap() .with_shifted_offsets(loader.configurables_offset_in_code() as i64) .unwrap(); let loader_posing_as_normal_script = Executable::from_bytes(loader.code()).with_configurables(shifted_configurables); let mut tb = ScriptTransactionBuilder::default() .with_script(loader_posing_as_normal_script.code()) .with_script_data(data); wallet.adjust_for_fee(&mut tb, 0).await?; wallet.add_witnesses(&mut tb)?; let tx = tb.build(&provider).await?; let response = provider.send_transaction_and_await_commit(tx).await?; response.check(None)?; Ok(()) } #[tokio::test] async fn script_call_respects_maturity_and_expiration() -> Result<()> { abigen!(Script( name = "MyScript", abi = "e2e/sway/scripts/basic_script/out/release/basic_script-abi.json" )); let wallet = launch_provider_and_get_wallet().await.expect(""); let provider = wallet.provider().clone(); let bin_path = "sway/scripts/basic_script/out/release/basic_script.bin"; let script_instance = MyScript::new(wallet, bin_path); let maturity = 10; let expiration = 20; let call_handler = script_instance.main(1, 2).with_tx_policies( TxPolicies::default() .with_maturity(maturity) .with_expiration(expiration), ); { let err = call_handler .clone() .call() .await .expect_err("maturity not reached"); assert!(err.to_string().contains("TransactionMaturity")); } { provider.produce_blocks(15, None).await?; call_handler .clone() .call() .await .expect("should succeed. Block height between `maturity` and `expiration`"); } { provider.produce_blocks(15, None).await?; let err = call_handler.call().await.expect_err("expiration reached"); assert!(err.to_string().contains("TransactionExpiration")); } Ok(()) } #[tokio::test] async fn script_tx_input_output() -> Result<()> { let [wallet_1, wallet_2] = launch_custom_provider_and_get_wallets( WalletsConfig::new(Some(2), Some(10), Some(1000)), None, None, ) .await? .try_into() .unwrap(); abigen!(Script( name = "TxScript", abi = "e2e/sway/scripts/script_tx_input_output/out/release/script_tx_input_output-abi.json" )); let script_binary = "sway/scripts/script_tx_input_output/out/release/script_tx_input_output.bin"; // Set `wallet_1` as the custom input owner let configurables = TxScriptConfigurables::default().with_OWNER(wallet_1.address())?; let script_instance = TxScript::new(wallet_2.clone(), script_binary).with_configurables(configurables); let asset_id = AssetId::zeroed(); { let custom_inputs = wallet_1 .get_asset_inputs_for_amount(asset_id, 10, None) .await? .into_iter() .take(1) .collect(); let custom_output = vec![Output::change(wallet_1.address(), 0, asset_id)]; // Input at first position is a coin owned by wallet_1 // Output at first position is change to wallet_1 // ANCHOR: script_custom_inputs_outputs let _ = script_instance .main(0, 0) .with_inputs(custom_inputs) .with_outputs(custom_output) .add_signer(wallet_1.signer().clone()) .call() .await?; // ANCHOR_END: script_custom_inputs_outputs } { // Input at first position is not a coin owned by wallet_1 let err = script_instance.main(0, 0).call().await.unwrap_err(); assert!(err.to_string().contains("wrong owner")); let custom_input = wallet_1 .get_asset_inputs_for_amount(asset_id, 10, None) .await? .pop() .unwrap(); // Input at first position is a coin owned by wallet_1 // Output at first position is not change to wallet_1 let err = script_instance .main(0, 0) .with_inputs(vec![custom_input]) .add_signer(wallet_1.signer().clone()) .call() .await .unwrap_err(); assert!(err.to_string().contains("wrong change address")); } Ok(()) } ================================================ FILE: e2e/tests/storage.rs ================================================ use fuels::{ prelude::*, tx::StorageSlot, types::{Bits256, Bytes32}, }; #[tokio::test] async fn test_storage_initialization() -> Result<()> { abigen!(Contract( name = "MyContract", abi = "e2e/sway/contracts/storage/out/release/storage-abi.json" )); let wallet = launch_provider_and_get_wallet().await?; let key = Bytes32::from([1u8; 32]); let value = Bytes32::from([2u8; 32]); let storage_slot = StorageSlot::new(key, value); let storage_vec = vec![storage_slot.clone()]; let storage_configuration = StorageConfiguration::default().add_slot_overrides(storage_vec); let contract_id = Contract::load_from( "sway/contracts/storage/out/release/storage.bin", LoadConfiguration::default().with_storage_configuration(storage_configuration), )? .deploy_if_not_exists(&wallet, TxPolicies::default()) .await? .contract_id; let contract_instance = MyContract::new(contract_id, wallet.clone()); let result = contract_instance .methods() .get_value_b256(Bits256(key.into())) .call() .await? .value; assert_eq!(result.0, *value); Ok(()) } #[tokio::test] async fn test_init_storage_automatically() -> Result<()> { abigen!(Contract( name = "MyContract", abi = "e2e/sway/contracts/storage/out/release/storage-abi.json" )); let wallet = launch_provider_and_get_wallet().await?; let contract_id = Contract::load_from( "sway/contracts/storage/out/release/storage.bin", LoadConfiguration::default(), )? .deploy_if_not_exists(&wallet, TxPolicies::default()) .await? .contract_id; let contract_methods = MyContract::new(contract_id, wallet.clone()).methods(); { let key: Bytes32 = "eb390d9f85c8c849ff8aeb05c865ca66b37ba69a7bec8489b1c467f029b650af".parse()?; let value = contract_methods .get_value_b256(Bits256(*key)) .call() .await? .value; assert_eq!(value.0, [1u8; 32]); } { let key: Bytes32 = "419b1120ea993203d7e223dfbe76184322453d6f8de946e827a8669102ab395b".parse()?; let value = contract_methods .get_value_u64(Bits256(*key)) .call() .await? .value; assert_eq!(value, 64); } Ok(()) } #[tokio::test] async fn storage_load_error_messages() { { let json_path = "sway/contracts/storage/out/release/no_file_on_path.json"; let expected_error = format!("io: file \"{json_path}\" does not exist"); let error = StorageConfiguration::default() .add_slot_overrides_from_file(json_path) .expect_err("should have failed"); assert_eq!(error.to_string(), expected_error); } { let json_path = "sway/contracts/storage/out/release/storage.bin"; let expected_error = format!("expected \"{json_path}\" to have '.json' extension"); let error = StorageConfiguration::default() .add_slot_overrides_from_file(json_path) .expect_err("should have failed"); assert_eq!(error.to_string(), expected_error); } } ================================================ FILE: e2e/tests/types_contracts.rs ================================================ use std::str::FromStr; use fuels::{ prelude::*, types::{B512, Bits256, EvmAddress, Identity, SizedAsciiString, U256}, }; #[tokio::test] async fn test_methods_typeless_argument() -> Result<()> { setup_program_test!( Wallets("wallet"), Abigen(Contract( name = "TypesContract", project = "e2e/sway/types/contracts/empty_arguments" )), Deploy( name = "contract_instance", contract = "TypesContract", wallet = "wallet", random_salt = false, ), ); let response = contract_instance .methods() .method_with_empty_argument() .call() .await?; assert_eq!(response.value, 63); Ok(()) } #[tokio::test] async fn call_with_empty_return() -> Result<()> { setup_program_test!( Wallets("wallet"), Abigen(Contract( name = "TestContract", project = "e2e/sway/types/contracts/call_empty_return" )), Deploy( name = "contract_instance", contract = "TestContract", wallet = "wallet", random_salt = false, ), ); let _response = contract_instance.methods().store_value(42).call().await?; Ok(()) } #[tokio::test] async fn call_with_structs() -> Result<()> { // Generates the bindings from the an ABI definition inline. // The generated bindings can be accessed through `MyContract`. // ANCHOR: struct_generation abigen!(Contract( name = "MyContract", abi = "e2e/sway/types/contracts/complex_types_contract/out/release/complex_types_contract-abi.json" )); // Here we can use `CounterConfig`, a struct originally // defined in the contract. let counter_config = CounterConfig { dummy: true, initial_value: 42, }; // ANCHOR_END: struct_generation let wallet = launch_provider_and_get_wallet().await?; let contract_id = Contract::load_from( "sway/types/contracts/complex_types_contract/out/release/complex_types_contract.bin", LoadConfiguration::default(), )? .deploy_if_not_exists(&wallet, TxPolicies::default()) .await? .contract_id; let contract_methods = MyContract::new(contract_id, wallet).methods(); let response = contract_methods .initialize_counter(counter_config) .call() .await?; assert_eq!(42, response.value); let response = contract_methods.increment_counter(10).call().await?; assert_eq!(52, response.value); Ok(()) } #[tokio::test] async fn abigen_different_structs_same_arg_name() -> Result<()> { setup_program_test!( Wallets("wallet"), Abigen(Contract( name = "TypesContract", project = "e2e/sway/types/contracts/two_structs" )), Deploy( name = "contract_instance", contract = "TypesContract", wallet = "wallet", random_salt = false, ), ); let param_one = StructOne { foo: 42 }; let param_two = StructTwo { bar: 42 }; let contract_methods = contract_instance.methods(); let res_one = contract_methods.something(param_one).call().await?; assert_eq!(res_one.value, 43); let res_two = contract_methods.something_else(param_two).call().await?; assert_eq!(res_two.value, 41); Ok(()) } #[tokio::test] async fn nested_structs() -> Result<()> { setup_program_test!( Wallets("wallet"), Abigen(Contract( name = "TypesContract", project = "e2e/sway/types/contracts/nested_structs" )), Deploy( name = "contract_instance", contract = "TypesContract", wallet = "wallet", random_salt = false, ), ); let expected = AllStruct { some_struct: SomeStruct { field: 12345, field_2: true, }, }; let contract_methods = contract_instance.methods(); let actual = contract_methods.get_struct().call().await?.value; assert_eq!(actual, expected); let fuelvm_judgement = contract_methods .check_struct_integrity(expected) .call() .await? .value; assert!( fuelvm_judgement, "The FuelVM deems that we've not encoded the argument correctly. Investigate!" ); let memory_address = MemoryAddress { contract_id: ContractId::zeroed(), function_selector: 10, function_data: 0, }; let call_data = CallData { memory_address, num_coins_to_forward: 10, asset_id_of_coins_to_forward: ContractId::zeroed(), amount_of_gas_to_forward: 5, }; let actual = contract_methods .nested_struct_with_reserved_keyword_substring(call_data.clone()) .call() .await? .value; assert_eq!(actual, call_data); Ok(()) } #[tokio::test] async fn calls_with_empty_struct() -> Result<()> { setup_program_test!( Wallets("wallet"), Abigen(Contract( name = "TypesContract", project = "e2e/sway/types/contracts/complex_types_contract" )), Deploy( name = "contract_instance", contract = "TypesContract", wallet = "wallet", random_salt = false, ), ); let contract_methods = contract_instance.methods(); { let response = contract_methods.get_empty_struct().call().await?; assert_eq!(response.value, EmptyStruct {}); } { let response = contract_methods .input_empty_struct(EmptyStruct {}) .call() .await?; assert!(response.value); } Ok(()) } #[tokio::test] async fn can_use_try_into_to_construct_struct_from_bytes() -> Result<()> { abigen!(Contract( name = "MyContract", abi = "e2e/sway/types/contracts/enum_inside_struct/out/release\ /enum_inside_struct-abi.json" )); let cocktail_in_bytes: Vec = vec![ 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 3, ]; let expected = Cocktail { the_thing_you_mix_in: Shaker::Mojito(2), glass: 3, }; // as slice let actual: Cocktail = cocktail_in_bytes[..].try_into()?; assert_eq!(actual, expected); // as ref let actual: Cocktail = (&cocktail_in_bytes).try_into()?; assert_eq!(actual, expected); // as value let actual: Cocktail = cocktail_in_bytes.try_into()?; assert_eq!(actual, expected); Ok(()) } #[tokio::test] async fn test_tuples() -> Result<()> { setup_program_test!( Wallets("wallet"), Abigen(Contract( name = "TypesContract", project = "e2e/sway/types/contracts/tuples" )), Deploy( name = "contract_instance", contract = "TypesContract", wallet = "wallet", random_salt = false, ), ); let contract_methods = contract_instance.methods(); { let response = contract_methods.returns_tuple((1, 2)).call().await?; assert_eq!(response.value, (1, 2)); } { // Tuple with struct. let my_struct_tuple = ( 42, Person { name: "Jane".try_into()?, }, ); let response = contract_methods .returns_struct_in_tuple(my_struct_tuple.clone()) .call() .await?; assert_eq!(response.value, my_struct_tuple); } { // Tuple with enum. let my_enum_tuple: (u64, State) = (42, State::A); let response = contract_methods .returns_enum_in_tuple(my_enum_tuple.clone()) .call() .await?; assert_eq!(response.value, my_enum_tuple); } { // Tuple with single element let my_enum_tuple = (123u64,); let response = contract_methods .single_element_tuple(my_enum_tuple) .call() .await?; assert_eq!(response.value, my_enum_tuple); } { // tuple with b256 let id = *ContractId::zeroed(); let my_b256_u8_tuple = (Bits256(id), 10); let response = contract_methods .tuple_with_b256(my_b256_u8_tuple) .call() .await?; assert_eq!(response.value, my_b256_u8_tuple); } Ok(()) } #[tokio::test] async fn test_evm_address() -> Result<()> { setup_program_test!( Wallets("wallet"), Abigen(Contract( name = "TypesContract", project = "e2e/sway/types/contracts/evm_address" )), Deploy( name = "contract_instance", contract = "TypesContract", wallet = "wallet", random_salt = false, ), ); { // ANCHOR: evm_address_arg let b256 = Bits256::from_hex_str( "0x1616060606060606060606060606060606060606060606060606060606060606", )?; let evm_address = EvmAddress::from(b256); let call_handler = contract_instance .methods() .evm_address_as_input(evm_address); // ANCHOR_END: evm_address_arg assert!(call_handler.call().await?.value); } { let b256 = Bits256::from_hex_str( "0x0606060606060606060606060606060606060606060606060606060606060606", )?; let expected_evm_address = EvmAddress::from(b256); assert_eq!( contract_instance .methods() .evm_address_from_literal() .call() .await? .value, expected_evm_address ); } { let b256 = Bits256::from_hex_str( "0x0606060606060606060606060606060606060606060606060606060606060606", )?; let expected_evm_address = EvmAddress::from(b256); assert_eq!( contract_instance .methods() .evm_address_from_argument(b256) .call() .await? .value, expected_evm_address ); } Ok(()) } #[tokio::test] async fn test_array() -> Result<()> { setup_program_test!( Wallets("wallet"), Abigen(Contract( name = "TypesContract", project = "e2e/sway/contracts/contract_test" )), Deploy( name = "contract_instance", contract = "TypesContract", wallet = "wallet", random_salt = false, ), ); assert_eq!( contract_instance .methods() .get_array([42; 2]) .call() .await? .value, [42; 2] ); Ok(()) } #[tokio::test] async fn test_arrays_with_custom_types() -> Result<()> { setup_program_test!( Wallets("wallet"), Abigen(Contract( name = "TypesContract", project = "e2e/sway/contracts/contract_test" )), Deploy( name = "contract_instance", contract = "TypesContract", wallet = "wallet", random_salt = false, ), ); let persons = [ Person { name: "John".try_into()?, }, Person { name: "Jane".try_into()?, }, ]; let contract_methods = contract_instance.methods(); let response = contract_methods.array_of_structs(persons).call().await?; assert_eq!("John", response.value[0].name); assert_eq!("Jane", response.value[1].name); let states = [State::A, State::B]; let response = contract_methods .array_of_enums(states.clone()) .call() .await?; assert_eq!(states[0], response.value[0]); assert_eq!(states[1], response.value[1]); Ok(()) } #[tokio::test] async fn str_in_array() -> Result<()> { setup_program_test!( Wallets("wallet"), Abigen(Contract( name = "TypesContract", project = "e2e/sway/types/contracts/str_in_array" )), Deploy( name = "contract_instance", contract = "TypesContract", wallet = "wallet", random_salt = false, ), ); let input = ["foo", "bar", "baz"].map(|str| str.try_into().unwrap()); let contract_methods = contract_instance.methods(); let response = contract_methods .take_array_string_shuffle(input.clone()) .call() .await?; assert_eq!(response.value, ["baz", "foo", "bar"]); let response = contract_methods .take_array_string_return_single(input.clone()) .call() .await?; assert_eq!(response.value, ["foo"]); let response = contract_methods .take_array_string_return_single_element(input) .call() .await?; assert_eq!(response.value, "bar"); Ok(()) } #[tokio::test] async fn test_enum_inside_struct() -> Result<()> { setup_program_test!( Wallets("wallet"), Abigen(Contract( name = "TypesContract", project = "e2e/sway/types/contracts/enum_inside_struct" )), Deploy( name = "contract_instance", contract = "TypesContract", wallet = "wallet", random_salt = false, ), ); let expected = Cocktail { the_thing_you_mix_in: Shaker::Mojito(11), glass: 333, }; let contract_methods = contract_instance.methods(); let response = contract_methods .return_enum_inside_struct(11) .call() .await?; assert_eq!(response.value, expected); let enum_inside_struct = Cocktail { the_thing_you_mix_in: Shaker::Cosmopolitan(444), glass: 555, }; let response = contract_methods .take_enum_inside_struct(enum_inside_struct) .call() .await?; assert_eq!(response.value, 555); Ok(()) } #[tokio::test] async fn native_types_support() -> Result<()> { setup_program_test!( Wallets("wallet"), Abigen(Contract( name = "TypesContract", project = "e2e/sway/types/contracts/native_types" )), Deploy( name = "contract_instance", contract = "TypesContract", wallet = "wallet", random_salt = false, ), ); let user = User { weight: 10, address: Address::zeroed(), }; let contract_methods = contract_instance.methods(); let response = contract_methods.wrapped_address(user).call().await?; assert_eq!(response.value.address, Address::zeroed()); let response = contract_methods .unwrapped_address(Address::zeroed()) .call() .await?; assert_eq!( response.value, Address::from_str("0x0000000000000000000000000000000000000000000000000000000000000000")? ); Ok(()) } #[tokio::test] async fn enum_coding_w_variable_width_variants() -> Result<()> { setup_program_test!( Wallets("wallet"), Abigen(Contract( name = "TypesContract", project = "e2e/sway/types/contracts/enum_encoding" )), Deploy( name = "contract_instance", contract = "TypesContract", wallet = "wallet", random_salt = false, ), ); // If we had a regression on the issue of enum encoding width, then we'll // probably end up mangling arg_2 and onward which will fail this test. let expected = BigBundle { arg_1: EnumThatHasABigAndSmallVariant::Small(12345), arg_2: 6666, arg_3: 7777, arg_4: 8888, }; let contract_methods = contract_instance.methods(); let actual = contract_methods.get_big_bundle().call().await?.value; assert_eq!(actual, expected); let fuelvm_judgement = contract_methods .check_big_bundle_integrity(expected) .call() .await? .value; assert!( fuelvm_judgement, "The FuelVM deems that we've not encoded the bundle correctly. Investigate!" ); Ok(()) } #[tokio::test] async fn enum_coding_w_unit_enums() -> Result<()> { setup_program_test!( Wallets("wallet"), Abigen(Contract( name = "TypesContract", project = "e2e/sway/types/contracts/enum_encoding" )), Deploy( name = "contract_instance", contract = "TypesContract", wallet = "wallet", random_salt = false, ), ); // If we had a regression on the issue of unit enum encoding width, then // we'll end up mangling arg_2 let expected = UnitBundle { arg_1: UnitEnum::var2, arg_2: u64::MAX, }; let contract_methods = contract_instance.methods(); let actual = contract_methods.get_unit_bundle().call().await?.value; assert_eq!(actual, expected); let fuelvm_judgement = contract_methods .check_unit_bundle_integrity(expected) .call() .await? .value; assert!( fuelvm_judgement, "The FuelVM deems that we've not encoded the bundle correctly. Investigate!" ); Ok(()) } #[tokio::test] async fn enum_as_input() -> Result<()> { setup_program_test!( Wallets("wallet"), Abigen(Contract( name = "TypesContract", project = "e2e/sway/types/contracts/enum_as_input" )), Deploy( name = "contract_instance", contract = "TypesContract", wallet = "wallet", random_salt = false, ), ); let expected = MaxedOutVariantsEnum::Variant255(11); let contract_methods = contract_instance.methods(); let actual = contract_methods.get_max_variant().call().await?.value; assert_eq!(expected, actual); let expected = StandardEnum::Two(12345); let contract_methods = contract_instance.methods(); let actual = contract_methods.get_standard_enum().call().await?.value; assert_eq!(expected, actual); let fuelvm_judgement = contract_methods .check_standard_enum_integrity(expected) .call() .await? .value; assert!( fuelvm_judgement, "The FuelVM deems that we've not encoded the standard enum correctly. Investigate!" ); let expected = UnitEnum::Two; let actual = contract_methods.get_unit_enum().call().await?.value; assert_eq!(actual, expected); let fuelvm_judgement = contract_methods .check_unit_enum_integrity(expected) .call() .await? .value; assert!( fuelvm_judgement, "The FuelVM deems that we've not encoded the unit enum correctly. Investigate!" ); Ok(()) } #[tokio::test] async fn can_use_try_into_to_construct_enum_from_bytes() -> Result<()> { abigen!(Contract( name = "MyContract", abi = "e2e/sway/types/contracts/enum_inside_struct/out/release\ /enum_inside_struct-abi.json" )); let shaker_in_bytes: Vec = vec![0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 2]; let expected = Shaker::Mojito(2); // as slice let actual: Shaker = shaker_in_bytes[..].try_into()?; assert_eq!(actual, expected); // as ref let actual: Shaker = (&shaker_in_bytes).try_into()?; assert_eq!(actual, expected); // as value let actual: Shaker = shaker_in_bytes.try_into()?; assert_eq!(actual, expected); Ok(()) } #[tokio::test] async fn type_inside_enum() -> Result<()> { setup_program_test!( Wallets("wallet"), Abigen(Contract( name = "TypesContract", project = "e2e/sway/types/contracts/type_inside_enum" )), Deploy( name = "contract_instance", contract = "TypesContract", wallet = "wallet", random_salt = false, ), ); // String inside enum let enum_string = SomeEnum::SomeStr("asdf".try_into()?); let contract_methods = contract_instance.methods(); let response = contract_methods .str_inside_enum(enum_string.clone()) .call() .await?; assert_eq!(response.value, enum_string); // Array inside enum let enum_array = SomeEnum::SomeArr([1, 2, 3, 4]); let response = contract_methods .arr_inside_enum(enum_array.clone()) .call() .await?; assert_eq!(response.value, enum_array); // Struct inside enum let response = contract_methods .return_struct_inside_enum(11) .call() .await?; let expected = Shaker::Cosmopolitan(Recipe { ice: 22, sugar: 11 }); assert_eq!(response.value, expected); let struct_inside_enum = Shaker::Cosmopolitan(Recipe { ice: 22, sugar: 66 }); let response = contract_methods .take_struct_inside_enum(struct_inside_enum) .call() .await?; assert_eq!(response.value, 8888); // Enum inside enum let expected_enum = EnumLevel3::El2(EnumLevel2::El1(EnumLevel1::Num(42))); let response = contract_methods.get_nested_enum().call().await?; assert_eq!(response.value, expected_enum); let response = contract_methods .check_nested_enum_integrity(expected_enum) .call() .await?; assert!( response.value, "The FuelVM deems that we've not encoded the nested enum correctly. Investigate!" ); Ok(()) } #[tokio::test] async fn test_rust_option_can_be_decoded() -> Result<()> { setup_program_test!( Wallets("wallet"), Abigen(Contract( name = "TypesContract", project = "e2e/sway/types/contracts/options" )), Deploy( name = "contract_instance", contract = "TypesContract", wallet = "wallet", random_salt = false, ), ); let contract_methods = contract_instance.methods(); let expected_address = Address::from_str("0xd58573593432a30a800f97ad32f877425c223a9e427ab557aab5d5bb89156db0")?; let s = TestStruct { option: Some(expected_address), }; let e = TestEnum::EnumOption(Some(expected_address)); let expected_some_address = Some(expected_address); let response = contract_methods.get_some_address().call().await?; assert_eq!(response.value, expected_some_address); let expected_some_u64 = Some(10); let response = contract_methods.get_some_u64().call().await?; assert_eq!(response.value, expected_some_u64); let response = contract_methods.get_some_struct().call().await?; assert_eq!(response.value, Some(s.clone())); let response = contract_methods.get_some_enum().call().await?; assert_eq!(response.value, Some(e.clone())); let response = contract_methods.get_some_tuple().call().await?; assert_eq!(response.value, Some((s.clone(), e.clone()))); let expected_none = None; let response = contract_methods.get_none().call().await?; assert_eq!(response.value, expected_none); Ok(()) } #[tokio::test] async fn test_rust_option_can_be_encoded() -> Result<()> { setup_program_test!( Wallets("wallet"), Abigen(Contract( name = "TypesContract", project = "e2e/sway/types/contracts/options" )), Deploy( name = "contract_instance", contract = "TypesContract", wallet = "wallet", random_salt = false, ), ); let contract_methods = contract_instance.methods(); let expected_address = Address::from_str("0xd58573593432a30a800f97ad32f877425c223a9e427ab557aab5d5bb89156db0")?; let s = TestStruct { option: Some(expected_address), }; let e = TestEnum::EnumOption(Some(expected_address)); let expected_u64 = Some(36); let response = contract_methods .input_primitive(expected_u64) .call() .await?; assert!(response.value); let expected_struct = Some(s); let response = contract_methods .input_struct(expected_struct) .call() .await?; assert!(response.value); let expected_enum = Some(e); let response = contract_methods.input_enum(expected_enum).call().await?; assert!(response.value); let expected_none = None; let response = contract_methods.input_none(expected_none).call().await?; assert!(response.value); Ok(()) } #[tokio::test] async fn test_rust_result_can_be_decoded() -> Result<()> { setup_program_test!( Wallets("wallet"), Abigen(Contract( name = "TypesContract", project = "e2e/sway/types/contracts/results" )), Deploy( name = "contract_instance", contract = "TypesContract", wallet = "wallet", random_salt = false, ), ); let contract_methods = contract_instance.methods(); let expected_address = Address::from_str("0xd58573593432a30a800f97ad32f877425c223a9e427ab557aab5d5bb89156db0")?; let s = TestStruct { option: Some(expected_address), }; let e = TestEnum::EnumOption(Some(expected_address)); let expected_ok_address = Ok(expected_address); let response = contract_methods.get_ok_address().call().await?; assert_eq!(response.value, expected_ok_address); let expected_some_u64 = Ok(10); let response = contract_methods.get_ok_u64().call().await?; assert_eq!(response.value, expected_some_u64); let response = contract_methods.get_ok_struct().call().await?; assert_eq!(response.value, Ok(s.clone())); let response = contract_methods.get_ok_enum().call().await?; assert_eq!(response.value, Ok(e.clone())); let response = contract_methods.get_ok_tuple().call().await?; assert_eq!(response.value, Ok((s, e))); let expected_error = Err(TestError::NoAddress("error".try_into().unwrap())); let response = contract_methods.get_error().call().await?; assert_eq!(response.value, expected_error); Ok(()) } #[tokio::test] async fn test_rust_result_can_be_encoded() -> Result<()> { setup_program_test!( Wallets("wallet"), Abigen(Contract( name = "TypesContract", project = "e2e/sway/types/contracts/results" )), Deploy( name = "contract_instance", contract = "TypesContract", wallet = "wallet", random_salt = false, ), ); let contract_methods = contract_instance.methods(); let expected_address = Address::from_str("0xd58573593432a30a800f97ad32f877425c223a9e427ab557aab5d5bb89156db0")?; let expected_ok_address = Ok(expected_address); let response = contract_methods .input_ok(expected_ok_address) .call() .await?; assert!(response.value); let expected_error = Err(TestError::NoAddress("error".try_into().unwrap())); let response = contract_methods.input_error(expected_error).call().await?; assert!(response.value); Ok(()) } #[tokio::test] async fn test_identity_can_be_decoded() -> Result<()> { setup_program_test!( Wallets("wallet"), Abigen(Contract( name = "TypesContract", project = "e2e/sway/types/contracts/identity" )), Deploy( name = "contract_instance", contract = "TypesContract", wallet = "wallet", random_salt = false, ), ); let contract_methods = contract_instance.methods(); let expected_address = Address::from_str("0xd58573593432a30a800f97ad32f877425c223a9e427ab557aab5d5bb89156db0")?; let expected_contract_id = ContractId::from_str("0xd58573593432a30a800f97ad32f877425c223a9e427ab557aab5d5bb89156db0")?; let s = TestStruct { identity: Identity::Address(expected_address), }; let e = TestEnum::EnumIdentity(Identity::ContractId(expected_contract_id)); let response = contract_methods.get_identity_address().call().await?; assert_eq!(response.value, Identity::Address(expected_address)); let response = contract_methods.get_identity_contract_id().call().await?; assert_eq!(response.value, Identity::ContractId(expected_contract_id)); let response = contract_methods.get_struct_with_identity().call().await?; assert_eq!(response.value, s.clone()); let response = contract_methods.get_enum_with_identity().call().await?; assert_eq!(response.value, e.clone()); let response = contract_methods.get_identity_tuple().call().await?; assert_eq!(response.value, (s, e)); Ok(()) } #[tokio::test] async fn test_identity_can_be_encoded() -> Result<()> { setup_program_test!( Wallets("wallet"), Abigen(Contract( name = "TypesContract", project = "e2e/sway/types/contracts/identity" )), Deploy( name = "contract_instance", contract = "TypesContract", wallet = "wallet", random_salt = false, ), ); let contract_methods = contract_instance.methods(); let expected_address = Address::from_str("0xd58573593432a30a800f97ad32f877425c223a9e427ab557aab5d5bb89156db0")?; let expected_contract_id = ContractId::from_str("0xd58573593432a30a800f97ad32f877425c223a9e427ab557aab5d5bb89156db0")?; let s = TestStruct { identity: Identity::Address(expected_address), }; let e = TestEnum::EnumIdentity(Identity::ContractId(expected_contract_id)); let response = contract_methods .input_identity(Identity::Address(expected_address)) .call() .await?; assert!(response.value); let response = contract_methods .input_struct_with_identity(s) .call() .await?; assert!(response.value); let response = contract_methods.input_enum_with_identity(e).call().await?; assert!(response.value); Ok(()) } #[tokio::test] async fn test_identity_with_two_contracts() -> Result<()> { setup_program_test!( Wallets("wallet"), Abigen(Contract( name = "TypesContract", project = "e2e/sway/types/contracts/identity" )), Deploy( name = "contract_instance", contract = "TypesContract", wallet = "wallet", random_salt = false, ), Deploy( name = "contract_instance2", contract = "TypesContract", wallet = "wallet", random_salt = false, ), ); let expected_address = Address::from_str("0xd58573593432a30a800f97ad32f877425c223a9e427ab557aab5d5bb89156db0")?; { let response = contract_instance .methods() .input_identity(Identity::Address(expected_address)) .call() .await?; assert!(response.value); } { let response = contract_instance2 .methods() .input_identity(Identity::Address(expected_address)) .call() .await?; assert!(response.value); } Ok(()) } #[tokio::test] async fn generics_test() -> Result<()> { setup_program_test!( Wallets("wallet"), Abigen(Contract( name = "TypesContract", project = "e2e/sway/types/contracts/generics" )), Deploy( name = "contract_instance", contract = "TypesContract", wallet = "wallet", random_salt = false, ), ); let contract_methods = contract_instance.methods(); { // ANCHOR: generic // simple struct with a single generic param let arg1 = SimpleGeneric { single_generic_param: 123u64, }; let result = contract_methods .struct_w_generic(arg1.clone()) .call() .await? .value; assert_eq!(result, arg1); // ANCHOR_END: generic } { // struct that delegates the generic param internally let arg1 = PassTheGenericOn { one: SimpleGeneric { single_generic_param: "abc".try_into()?, }, }; let result = contract_methods .struct_delegating_generic(arg1.clone()) .call() .await? .value; assert_eq!(result, arg1); } { // struct that has the generic in an array let arg1 = StructWArrayGeneric { a: [1u32, 2u32] }; let result = contract_methods .struct_w_generic_in_array(arg1.clone()) .call() .await? .value; assert_eq!(result, arg1); } { // struct that has a generic struct in an array let inner = [ StructWTwoGenerics { a: Bits256([1u8; 32]), b: 1, }, StructWTwoGenerics { a: Bits256([2u8; 32]), b: 2, }, StructWTwoGenerics { a: Bits256([3u8; 32]), b: 3, }, ]; let arg1 = StructWArrWGenericStruct { a: inner }; let result = contract_methods .array_with_generic_struct(arg1.clone()) .call() .await? .value; assert_eq!(result, arg1); } { // struct that has the generic in a tuple let arg1 = StructWTupleGeneric { a: (1, 2) }; let result = contract_methods .struct_w_generic_in_tuple(arg1.clone()) .call() .await? .value; assert_eq!(result, arg1); } { // enum with generic in variant let arg1 = EnumWGeneric::B(10); let result = contract_methods .enum_w_generic(arg1.clone()) .call() .await? .value; assert_eq!(result, arg1); } { contract_methods .unused_generic_args(StructUnusedGeneric::new(15), EnumUnusedGeneric::One(15)) .call() .await?; let (the_struct, the_enum) = contract_methods .used_and_unused_generic_args( StructUsedAndUnusedGenericParams::new(10u8), EnumUsedAndUnusedGenericParams::Two(11u8), ) .call() .await? .value; assert_eq!(the_struct.field, 12u8); if let EnumUsedAndUnusedGenericParams::Two(val) = the_enum { assert_eq!(val, 13) } else { panic!("Expected the variant EnumUsedAndUnusedGenericParams::Two"); } } { // complex case let pass_through = PassTheGenericOn { one: SimpleGeneric { single_generic_param: "ab".try_into()?, }, }; let w_arr_generic = StructWArrayGeneric { a: [pass_through.clone(), pass_through], }; let arg1 = MegaExample { a: ([Bits256([0; 32]), Bits256([0; 32])], "ab".try_into()?), b: vec![( [EnumWGeneric::B(StructWTupleGeneric { a: (w_arr_generic.clone(), w_arr_generic), })], 10u32, )], }; contract_methods.complex_test(arg1.clone()).call().await?; } Ok(()) } #[tokio::test] async fn contract_vectors() -> Result<()> { setup_program_test!( Wallets("wallet"), Abigen(Contract( name = "TypesContract", project = "e2e/sway/types/contracts/vectors" )), Deploy( name = "contract_instance", contract = "TypesContract", wallet = "wallet", random_salt = false, ), ); let methods = contract_instance.methods(); { // vec of u32s let arg = vec![0, 1, 2]; methods.u32_vec(arg).call().await?; } { // vec of vecs of u32s let arg = vec![vec![0, 1, 2], vec![0, 1, 2]]; methods.vec_in_vec(arg.clone()).call().await?; } { // vec of structs // ANCHOR: passing_in_vec let arg = vec![SomeStruct { a: 0 }, SomeStruct { a: 1 }]; methods.struct_in_vec(arg.clone()).call().await?; // ANCHOR_END: passing_in_vec } { // vec in struct let arg = SomeStruct { a: vec![0, 1, 2] }; methods.vec_in_struct(arg.clone()).call().await?; } { // array in vec let arg = vec![[0u64, 1u64], [0u64, 1u64]]; methods.array_in_vec(arg.clone()).call().await?; } { // vec in array let arg = [vec![0, 1, 2], vec![0, 1, 2]]; methods.vec_in_array(arg.clone()).call().await?; } { // vec in enum let arg = SomeEnum::a(vec![0, 1, 2]); methods.vec_in_enum(arg.clone()).call().await?; } { // enum in vec let arg = vec![SomeEnum::a(0), SomeEnum::a(1)]; methods.enum_in_vec(arg.clone()).call().await?; } { // tuple in vec let arg = vec![(0, 0), (1, 1)]; methods.tuple_in_vec(arg.clone()).call().await?; } { // vec in tuple let arg = (vec![0, 1, 2], vec![0, 1, 2]); methods.vec_in_tuple(arg.clone()).call().await?; } { // vec in a vec in a struct in a vec let arg = vec![ SomeStruct { a: vec![vec![0, 1, 2], vec![3, 4, 5]], }, SomeStruct { a: vec![vec![6, 7, 8], vec![9, 10, 11]], }, ]; methods .vec_in_a_vec_in_a_struct_in_a_vec(arg.clone()) .call() .await?; } Ok(()) } #[tokio::test] async fn test_b256() -> Result<()> { setup_program_test!( Wallets("wallet"), Abigen(Contract( name = "TypesContract", project = "e2e/sway/types/contracts/b256" )), Deploy( name = "contract_instance", contract = "TypesContract", wallet = "wallet", random_salt = false, ), ); assert_eq!( Bits256([2; 32]), contract_instance .methods() .b256_as_output() .call() .await? .value ); { // ANCHOR: 256_arg let b256 = Bits256([1; 32]); let call_handler = contract_instance.methods().b256_as_input(b256); // ANCHOR_END: 256_arg assert!(call_handler.call().await?.value); } Ok(()) } #[tokio::test] async fn test_b512() -> Result<()> { setup_program_test!( Wallets("wallet"), Abigen(Contract( name = "TypesContract", project = "e2e/sway/types/contracts/b512" )), Deploy( name = "contract_instance", contract = "TypesContract", wallet = "wallet", random_salt = false, ), ); let contract_methods = contract_instance.methods(); // ANCHOR: b512_example let hi_bits = Bits256::from_hex_str( "0xbd0c9b8792876713afa8bff383eebf31c43437823ed761cc3600d0016de5110c", )?; let lo_bits = Bits256::from_hex_str( "0x44ac566bd156b4fc71a4a4cb2655d3dd360c695edb17dc3b64d611e122fea23d", )?; let b512 = B512::from((hi_bits, lo_bits)); // ANCHOR_END: b512_example assert_eq!(b512, contract_methods.b512_as_output().call().await?.value); { let lo_bits2 = Bits256::from_hex_str( "0x54ac566bd156b4fc71a4a4cb2655d3dd360c695edb17dc3b64d611e122fea23d", )?; let b512 = B512::from((hi_bits, lo_bits2)); assert!(contract_methods.b512_as_input(b512).call().await?.value); } Ok(()) } fn u128_from(parts: (u64, u64)) -> u128 { let bytes: [u8; 16] = [parts.0.to_be_bytes(), parts.1.to_be_bytes()] .concat() .try_into() .unwrap(); u128::from_be_bytes(bytes) } #[tokio::test] async fn test_u128() -> Result<()> { setup_program_test!( Wallets("wallet"), Abigen(Contract( name = "TypesContract", project = "e2e/sway/types/contracts/u128" )), Deploy( name = "contract_instance", contract = "TypesContract", wallet = "wallet", random_salt = false, ), ); let contract_methods = contract_instance.methods(); { let arg = u128_from((1, 2)); let actual = contract_methods.u128_sum_and_ret(arg).call().await?.value; let expected = arg + u128_from((3, 4)); assert_eq!(expected, actual); } { let actual = contract_methods.u128_in_enum_output().call().await?.value; let expected = SomeEnum::B(u128_from((4, 4))); assert_eq!(expected, actual); } { let input = SomeEnum::B(u128_from((3, 3))); contract_methods.u128_in_enum_input(input).call().await?; } Ok(()) } fn u256_from(parts: (u64, u64, u64, u64)) -> U256 { let bytes: [u8; 32] = [ parts.0.to_be_bytes(), parts.1.to_be_bytes(), parts.2.to_be_bytes(), parts.3.to_be_bytes(), ] .concat() .try_into() .unwrap(); U256::from(bytes) } #[tokio::test] async fn test_u256() -> Result<()> { setup_program_test!( Wallets("wallet"), Abigen(Contract( name = "TypesContract", project = "e2e/sway/types/contracts/u256" )), Deploy( name = "contract_instance", contract = "TypesContract", wallet = "wallet", random_salt = false, ), ); let contract_methods = contract_instance.methods(); { let arg = u256_from((1, 2, 3, 4)); let actual = contract_methods.u256_sum_and_ret(arg).call().await?.value; let expected = arg + u256_from((3, 4, 5, 6)); assert_eq!(expected, actual); } { let actual = contract_methods.u256_in_enum_output().call().await?.value; let expected = SomeEnum::B(u256_from((1, 2, 3, 4))); assert_eq!(expected, actual); } { let input = SomeEnum::B(u256_from((2, 3, 4, 5))); contract_methods.u256_in_enum_input(input).call().await?; } Ok(()) } #[tokio::test] async fn test_base_type_in_vec_output() -> Result<()> { setup_program_test!( Wallets("wallet"), Abigen(Contract( name = "VectorOutputContract", project = "e2e/sway/types/contracts/vector_output" )), Deploy( name = "contract_instance", contract = "VectorOutputContract", wallet = "wallet", random_salt = false, ), ); let contract_methods = contract_instance.methods(); // ANCHOR: returning_vec let response = contract_methods.u8_in_vec(10).call().await?; assert_eq!(response.value, (0..10).collect::>()); // ANCHOR_END: returning_vec let response = contract_methods.u16_in_vec(11).call().await?; assert_eq!(response.value, (0..11).collect::>()); let response = contract_methods.u32_in_vec(12).call().await?; assert_eq!(response.value, (0..12).collect::>()); let response = contract_methods.u64_in_vec(13).call().await?; assert_eq!(response.value, (0..13).collect::>()); let response = contract_methods.bool_in_vec().call().await?; assert_eq!(response.value, [true, false, true, false].to_vec()); let response = contract_methods.b256_in_vec(13).call().await?; assert_eq!(response.value, vec![Bits256([2; 32]); 13]); Ok(()) } #[tokio::test] async fn test_composite_types_in_vec_output() -> Result<()> { setup_program_test!( Wallets("wallet"), Abigen(Contract( name = "VectorOutputContract", project = "e2e/sway/types/contracts/vector_output" )), Deploy( name = "contract_instance", contract = "VectorOutputContract", wallet = "wallet", random_salt = false, ), ); let contract_methods = contract_instance.methods(); { let expected: Vec<[u64; 4]> = vec![[1, 1, 1, 1], [2, 2, 2, 2], [3, 3, 3, 3], [4, 4, 4, 4]]; let response = contract_methods.array_in_vec().call().await?.value; assert_eq!(response, expected); } { let expected: Vec = vec![ Pasta::Tortelini(Bimbam { bim: 1111, bam: 2222_u32, }), Pasta::Rigatoni(1987), Pasta::Spaghetti(true), ]; let response = contract_methods.enum_in_vec().call().await?.value; assert_eq!(response, expected); } { let expected: Vec = vec![ Bimbam { bim: 1111, bam: 2222_u32, }, Bimbam { bim: 3333, bam: 4444_u32, }, Bimbam { bim: 5555, bam: 6666_u32, }, ]; let response = contract_methods.struct_in_vec().call().await?.value; assert_eq!(response, expected); } { let expected: Vec<(u64, u32)> = vec![(1111, 2222_u32), (3333, 4444_u32), (5555, 6666_u32)]; let response = contract_methods.tuple_in_vec().call().await?.value; assert_eq!(response, expected); } { let expected: Vec> = vec!["hell".try_into()?, "ello".try_into()?, "lloh".try_into()?]; let response = contract_methods.str_in_vec().call().await?.value; assert_eq!(response, expected); } Ok(()) } #[tokio::test] async fn test_bytes_output() -> Result<()> { setup_program_test!( Wallets("wallet"), Abigen(Contract( name = "BytesOutputContract", project = "e2e/sway/types/contracts/bytes" )), Deploy( name = "contract_instance", contract = "BytesOutputContract", wallet = "wallet", random_salt = false, ), ); let contract_methods = contract_instance.methods(); let response = contract_methods.return_bytes(10).call().await?; assert_eq!(response.value, (0..10).collect::>()); Ok(()) } #[tokio::test] async fn test_bytes_as_input() -> Result<()> { setup_program_test!( Wallets("wallet"), Abigen(Contract( name = "BytesInputContract", project = "e2e/sway/types/contracts/bytes" )), Deploy( name = "contract_instance", contract = "BytesInputContract", wallet = "wallet", random_salt = false, ), ); let contract_methods = contract_instance.methods(); { // ANCHOR: bytes_arg let bytes = Bytes(vec![40, 41, 42]); contract_methods.accept_bytes(bytes).call().await?; // ANCHOR_END: bytes_arg } { let bytes = Bytes(vec![40, 41, 42]); let wrapper = Wrapper { inner: vec![bytes.clone(), bytes.clone()], inner_enum: SomeEnum::Second(bytes), }; contract_methods.accept_nested_bytes(wrapper).call().await?; } Ok(()) } #[tokio::test] async fn contract_raw_slice() -> Result<()> { setup_program_test!( Wallets("wallet"), Abigen(Contract( name = "RawSliceContract", project = "e2e/sway/types/contracts/raw_slice" )), Deploy( name = "contract_instance", contract = "RawSliceContract", wallet = "wallet", random_salt = false, ), ); let contract_methods = contract_instance.methods(); { for length in 0u8..=10 { let response = contract_methods.return_raw_slice(length).call().await?; assert_eq!(response.value, (0u8..length).collect::>()); } } { contract_methods .accept_raw_slice(RawSlice(vec![40, 41, 42])) .call() .await?; } { let raw_slice = RawSlice(vec![40, 41, 42]); let wrapper = Wrapper { inner: vec![raw_slice.clone(), raw_slice.clone()], inner_enum: SomeEnum::Second(raw_slice), }; contract_methods .accept_nested_raw_slice(wrapper) .call() .await?; } Ok(()) } #[tokio::test] async fn contract_string_slice() -> Result<()> { setup_program_test!( Wallets("wallet"), Abigen(Contract( name = "StringSliceContract", project = "e2e/sway/types/contracts/string_slice" )), Deploy( name = "contract_instance", contract = "StringSliceContract", wallet = "wallet", random_salt = false, ), ); let contract_methods = contract_instance.methods(); let response = contract_methods .handles_str("contract-input".try_into()?) .call() .await?; assert_eq!(response.value, "contract-return"); Ok(()) } #[tokio::test] async fn contract_std_lib_string() -> Result<()> { setup_program_test!( Wallets("wallet"), Abigen(Contract( name = "StdLibString", project = "e2e/sway/types/contracts/std_lib_string" )), Deploy( name = "contract_instance", contract = "StdLibString", wallet = "wallet", random_salt = false, ), ); let contract_methods = contract_instance.methods(); { let resp = contract_methods.return_dynamic_string().call().await?.value; assert_eq!(resp, "Hello World"); } { let _resp = contract_methods .accepts_dynamic_string(String::from("Hello World")) .call() .await?; } { // confirm encoding/decoding a string wasn't faulty and led to too high gas consumption let _resp = contract_methods .echoes_dynamic_string(String::from("Hello Fuel")) .with_tx_policies(TxPolicies::default().with_script_gas_limit(3600)) .call() .await?; } Ok(()) } #[tokio::test] async fn test_heap_type_in_enums() -> Result<()> { setup_program_test!( Wallets("wallet"), Abigen(Contract( name = "HeapTypeInEnum", project = "e2e/sway/types/contracts/heap_type_in_enums" )), Deploy( name = "contract_instance", contract = "HeapTypeInEnum", wallet = "wallet", random_salt = false, ), ); let contract_methods = contract_instance.methods(); { let resp = contract_methods.returns_bytes_result(true).call().await?; let expected = Ok(Bytes(vec![1, 1, 1, 1])); assert_eq!(resp.value, expected); } { let resp = contract_methods.returns_bytes_result(false).call().await?; let expected = Err(TestError::Something([255u8, 255u8, 255u8, 255u8, 255u8])); assert_eq!(resp.value, expected); } { let resp = contract_methods.returns_vec_result(true).call().await?; let expected = Ok(vec![2, 2, 2, 2, 2]); assert_eq!(resp.value, expected); } { let resp = contract_methods.returns_vec_result(false).call().await?; let expected = Err(TestError::Else(7777)); assert_eq!(resp.value, expected); } { let resp = contract_methods.returns_string_result(true).call().await?; let expected = Ok("Hello World".to_string()); assert_eq!(resp.value, expected); } { let resp = contract_methods.returns_string_result(false).call().await?; let expected = Err(TestError::Else(3333)); assert_eq!(resp.value, expected); } { let resp = contract_methods.returns_str_result(true).call().await?; let expected = Ok("Hello World".try_into()?); assert_eq!(resp.value, expected); } { let resp = contract_methods.returns_string_result(false).call().await?; let expected = Err(TestError::Else(3333)); assert_eq!(resp.value, expected); } { let resp = contract_methods.returns_bytes_option(true).call().await?; let expected = Some(Bytes(vec![1, 1, 1, 1])); assert_eq!(resp.value, expected); } { let resp = contract_methods.returns_bytes_option(false).call().await?; assert!(resp.value.is_none()); } { let resp = contract_methods.returns_vec_option(true).call().await?; let expected = Some(vec![2, 2, 2, 2, 2]); assert_eq!(resp.value, expected); } { let resp = contract_methods.returns_vec_option(false).call().await?; assert!(resp.value.is_none()); } { let resp = contract_methods.returns_string_option(true).call().await?; let expected = Some("Hello World".to_string()); assert_eq!(resp.value, expected); } { let resp = contract_methods.returns_string_option(false).call().await?; assert!(resp.value.is_none()); } { let resp = contract_methods.returns_str_option(true).call().await?; let expected = Some("Hello World".try_into()?); assert_eq!(resp.value, expected); } { let resp = contract_methods.returns_string_option(false).call().await?; assert!(resp.value.is_none()); } Ok(()) } #[tokio::test] async fn nested_heap_types() -> Result<()> { setup_program_test!( Wallets("wallet"), Abigen(Contract( name = "HeapTypeInEnum", project = "e2e/sway/types/contracts/heap_types" )), Deploy( name = "contract_instance", contract = "HeapTypeInEnum", wallet = "wallet", random_salt = false, ), ); let arr = [2u8, 4, 8]; let struct_generics = StructGenerics { one: Bytes(arr.to_vec()), two: String::from("fuel"), three: RawSlice(arr.to_vec()), }; let enum_vec = [struct_generics.clone(), struct_generics].to_vec(); let expected = EnumGeneric::One(enum_vec); let result = contract_instance .methods() .nested_heap_types() .call() .await?; assert_eq!(result.value, expected); Ok(()) } ================================================ FILE: e2e/tests/types_predicates.rs ================================================ use std::{default::Default, path::Path}; use fuels::{ accounts::{Account, predicate::Predicate, signers::private_key::PrivateKeySigner}, prelude::*, types::{AssetId, Bits256, U256, coin::Coin, message::Message}, }; async fn assert_predicate_spendable(data: Vec, project_path: impl AsRef) -> Result<()> { let binary_path = project_binary(project_path); let mut predicate: Predicate = Predicate::load_from(&binary_path)?.with_data(data); let num_coins = 4; let num_messages = 8; let amount = 16; let (provider, predicate_balance, receiver, receiver_balance, asset_id) = setup_predicate_test(predicate.address(), num_coins, num_messages, amount).await?; predicate.set_provider(provider.clone()); let amount_to_send = 136; let fee = predicate .transfer( receiver.address(), amount_to_send, asset_id, TxPolicies::default(), ) .await? .tx_status .total_fee; // The predicate has spent the funds assert_address_balance( &predicate.address(), &provider, &asset_id, predicate_balance - amount_to_send - fee, ) .await; // Funds were transferred assert_address_balance( &receiver.address(), &provider, &asset_id, receiver_balance + amount_to_send, ) .await; Ok(()) } fn project_binary(project_root: impl AsRef) -> String { let project_root = project_root.as_ref(); let project_name = project_root .file_name() .expect("Couldn't extract project name") .to_str() .unwrap(); project_root .join(format!("out/release/{project_name}.bin")) .display() .to_string() } async fn assert_address_balance( address: &Address, provider: &Provider, asset_id: &AssetId, amount: u64, ) { let balance = provider .get_asset_balance(address, asset_id) .await .expect("Could not retrieve balance"); assert_eq!(balance, amount as u128); } fn get_test_coins_and_messages( address: Address, num_coins: u64, num_messages: u64, amount: u64, ) -> (Vec, Vec, AssetId) { let asset_id = AssetId::zeroed(); let coins = setup_single_asset_coins(address, asset_id, num_coins, amount); let messages = (0..num_messages) .map(|i| setup_single_message(Address::default(), address, amount, i.into(), vec![])) .collect(); (coins, messages, asset_id) } // Setup function used to assign coins and messages to a predicate address // and create a `receiver` wallet async fn setup_predicate_test( predicate_address: Address, num_coins: u64, num_messages: u64, amount: u64, ) -> Result<(Provider, u64, Wallet, u64, AssetId)> { let receiver_num_coins = 1; let receiver_amount = 1; let receiver_balance = receiver_num_coins * receiver_amount; let predicate_balance = (num_coins + num_messages) * amount; let receiver_signer = PrivateKeySigner::random(&mut rand::thread_rng()); let (mut coins, messages, asset_id) = get_test_coins_and_messages(predicate_address, num_coins, num_messages, amount); coins.extend(setup_single_asset_coins( receiver_signer.address(), asset_id, receiver_num_coins, receiver_amount, )); let node_config = NodeConfig { starting_gas_price: 0, ..Default::default() }; let provider = setup_test_provider(coins, messages, Some(node_config), None).await?; let receiver = Wallet::new(receiver_signer, provider.clone()); Ok(( provider, predicate_balance, receiver, receiver_balance, asset_id, )) } #[tokio::test] async fn spend_predicate_coins_messages_single_u64() -> Result<()> { abigen!(Predicate( name = "MyPredicate", abi = "e2e/sway/types/predicates/u64/out/release/u64-abi.json" )); let data = MyPredicateEncoder::default().encode_data(32768)?; assert_predicate_spendable(data, "sway/types/predicates/u64").await?; Ok(()) } #[tokio::test] async fn spend_predicate_coins_messages_address() -> Result<()> { abigen!(Predicate( name = "MyPredicate", abi = "e2e/sway/types/predicates/address/out/release/address-abi.json" )); let addr: Address = "0xef86afa9696cf0dc6385e2c407a6e159a1103cefb7e2ae0636fb33d3cb2a9e4a".parse()?; let data = MyPredicateEncoder::default().encode_data(addr)?; assert_predicate_spendable(data, "sway/types/predicates/address").await?; Ok(()) } #[tokio::test] async fn spend_predicate_coins_messages_enums() -> Result<()> { abigen!(Predicate( name = "MyPredicate", abi = "e2e/sway/types/predicates/enums/out/release/enums-abi.json" )); let data = MyPredicateEncoder::default().encode_data(TestEnum::A(32), AnotherTestEnum::B(32))?; assert_predicate_spendable(data, "sway/types/predicates/enums").await?; Ok(()) } #[tokio::test] async fn spend_predicate_coins_messages_structs() -> Result<()> { abigen!(Predicate( name = "MyPredicate", abi = "e2e/sway/types/predicates/structs/out/release/structs-abi.json" )); let data = MyPredicateEncoder::default().encode_data( TestStruct { value: 192 }, AnotherTestStruct { value: 64, number: 128, }, )?; assert_predicate_spendable(data, "sway/types/predicates/structs").await?; Ok(()) } #[tokio::test] async fn spend_predicate_coins_messages_tuple() -> Result<()> { abigen!(Predicate( name = "MyPredicate", abi = "e2e/sway/types/predicates/predicate_tuples/out/release/predicate_tuples-abi.json" )); let data = MyPredicateEncoder::default() .encode_data((16, TestStruct { value: 32 }, TestEnum::Value(64)), 128)?; assert_predicate_spendable(data, "sway/types/predicates/predicate_tuples").await?; Ok(()) } #[tokio::test] async fn spend_predicate_coins_messages_vector() -> Result<()> { abigen!(Predicate( name = "MyPredicate", abi = "e2e/sway/types/predicates/predicate_vector/out/release/predicate_vector-abi.json" )); let data = MyPredicateEncoder::default().encode_data(18, 24, vec![2, 4, 42])?; assert_predicate_spendable(data, "sway/types/predicates/predicate_vector").await?; Ok(()) } #[tokio::test] async fn spend_predicate_coins_messages_vectors() -> Result<()> { abigen!(Predicate( name = "MyPredicate", abi = "e2e/sway/types/predicates/predicate_vectors/out/release/predicate_vectors-abi.json" )); let u32_vec = vec![0, 4, 3]; let vec_in_vec = vec![vec![0, 2, 2], vec![0, 1, 2]]; let struct_in_vec = vec![SomeStruct { a: 8 }, SomeStruct { a: 1 }]; let vec_in_struct = SomeStruct { a: vec![0, 16, 2] }; let array_in_vec = vec![[0u64, 1u64], [32u64, 1u64]]; let vec_in_enum = SomeEnum::A(vec![0, 1, 128]); let enum_in_vec = vec![SomeEnum::A(0), SomeEnum::A(16)]; let b256_in_vec = vec![Bits256([2; 32]), Bits256([2; 32])]; let tuple_in_vec = vec![(0, 0), (128, 1)]; let vec_in_tuple = (vec![0, 64, 2], vec![0, 1, 2]); let vec_in_a_vec_in_a_struct_in_a_vec = vec![ SomeStruct { a: vec![vec![0, 1, 2], vec![3, 4, 5]], }, SomeStruct { a: vec![vec![6, 7, 8], vec![9, 32, 11]], }, ]; let vec_in_array = [vec![0, 64, 2], vec![0, 1, 2]]; let data = MyPredicateEncoder::default().encode_data( u32_vec, vec_in_vec, struct_in_vec, vec_in_struct, array_in_vec, vec_in_array, vec_in_enum, enum_in_vec, b256_in_vec, tuple_in_vec, vec_in_tuple, vec_in_a_vec_in_a_struct_in_a_vec, )?; assert_predicate_spendable(data, "sway/types/predicates/predicate_vectors").await?; Ok(()) } #[tokio::test] async fn spend_predicate_coins_messages_generics() -> Result<()> { abigen!(Predicate( name = "MyPredicate", abi = "e2e/sway/types/predicates/predicate_generics/out/release/predicate_generics-abi.json" )); let data = MyPredicateEncoder::default().encode_data( GenericStruct { value: 64u8 }, GenericEnum::Generic(GenericStruct { value: 64u16 }), )?; assert_predicate_spendable(data, "sway/types/predicates/predicate_generics").await?; Ok(()) } #[tokio::test] async fn spend_predicate_coins_messages_bytes_hash() -> Result<()> { abigen!(Predicate( name = "MyPredicate", abi = "e2e/sway/types/predicates/predicate_bytes_hash/out/release/predicate_bytes_hash-abi.json" )); let bytes = Bytes::from_hex_str( "0x75a448b91bb82a255757e61ba3eb7afe282c09842485268d4d72a027ec0cffc80500000000", )?; let bits256 = Bits256::from_hex_str( "0x173d69ea3d0aa050d01ff7cc60ccd4579b567c465cd115c6876c2da4a332fb99", )?; let data = MyPredicateEncoder::default().encode_data(bytes, bits256)?; assert_predicate_spendable(data, "sway/types/predicates/predicate_bytes_hash").await?; Ok(()) } #[tokio::test] async fn spend_predicate_coins_messages_bytes() -> Result<()> { abigen!(Predicate( name = "MyPredicate", abi = "e2e/sway/types/predicates/predicate_bytes/out/release/predicate_bytes-abi.json" )); let bytes = Bytes(vec![40, 41, 42]); let wrapper = Wrapper { inner: vec![bytes.clone(), bytes.clone()], inner_enum: SomeEnum::Second(bytes), }; let data = MyPredicateEncoder::default().encode_data(wrapper)?; assert_predicate_spendable(data, "sway/types/predicates/predicate_bytes").await?; Ok(()) } #[tokio::test] async fn spend_predicate_coins_messages_raw_slice() -> Result<()> { abigen!(Predicate( name = "MyPredicate", abi = "e2e/sway/types/predicates/predicate_raw_slice/out/release/predicate_raw_slice-abi.json" )); let raw_slice = RawSlice(vec![40, 41, 42]); let wrapper = Wrapper { inner: vec![raw_slice.clone(), raw_slice.clone()], inner_enum: SomeEnum::Second(raw_slice), }; let data = MyPredicateEncoder::default().encode_data(wrapper)?; assert_predicate_spendable(data, "sway/types/predicates/predicate_raw_slice").await?; Ok(()) } fn u128_from(parts: (u64, u64)) -> u128 { let bytes: [u8; 16] = [parts.0.to_be_bytes(), parts.1.to_be_bytes()] .concat() .try_into() .unwrap(); u128::from_be_bytes(bytes) } #[tokio::test] async fn predicate_handles_u128() -> Result<()> { abigen!(Predicate( name = "MyPredicate", abi = "e2e/sway/types/predicates/predicate_u128/out/release/predicate_u128-abi.json" )); let data = MyPredicateEncoder::default().encode_data(u128_from((8, 2)))?; assert_predicate_spendable(data, "sway/types/predicates/predicate_u128").await?; Ok(()) } #[tokio::test] async fn predicate_handles_b256() -> Result<()> { abigen!(Predicate( name = "MyPredicate", abi = "e2e/sway/types/predicates/predicate_b256/out/release/predicate_b256-abi.json" )); let data = MyPredicateEncoder::default().encode_data(Bits256([1; 32]))?; assert_predicate_spendable(data, "sway/types/predicates/predicate_b256").await?; Ok(()) } fn u256_from(parts: (u64, u64, u64, u64)) -> U256 { let bytes: [u8; 32] = [ parts.0.to_be_bytes(), parts.1.to_be_bytes(), parts.2.to_be_bytes(), parts.3.to_be_bytes(), ] .concat() .try_into() .unwrap(); U256::from(bytes) } #[tokio::test] async fn predicate_handles_u256() -> Result<()> { abigen!(Predicate( name = "MyPredicate", abi = "e2e/sway/types/predicates/predicate_u256/out/release/predicate_u256-abi.json" )); let data = MyPredicateEncoder::default().encode_data(u256_from((10, 11, 12, 13)))?; assert_predicate_spendable(data, "sway/types/predicates/predicate_u256").await?; Ok(()) } #[tokio::test] async fn predicate_handles_std_string() -> Result<()> { abigen!(Predicate( name = "MyPredicate", abi = "e2e/sway/types/predicates/predicate_std_lib_string/out/release/predicate_std_lib_string-abi.json" )); let data = MyPredicateEncoder::default().encode_data(10, 11, String::from("Hello World"))?; assert_predicate_spendable(data, "sway/types/predicates/predicate_std_lib_string").await?; Ok(()) } #[tokio::test] async fn predicate_string_slice() -> Result<()> { abigen!(Predicate( name = "MyPredicate", abi = "e2e/sway/types/predicates/predicate_string_slice/out/release/predicate_string_slice-abi.json" )); let data = MyPredicateEncoder::default().encode_data("predicate-input".try_into()?)?; assert_predicate_spendable(data, "sway/types/predicates/predicate_string_slice").await?; Ok(()) } ================================================ FILE: e2e/tests/types_scripts.rs ================================================ use fuels::{ prelude::*, types::{Bits256, U256}, }; #[tokio::test] async fn script_b256() -> Result<()> { setup_program_test!( Wallets("wallet"), Abigen(Script( name = "MyScript", project = "e2e/sway/types/scripts/script_b256" )), LoadScript( name = "script_instance", script = "MyScript", wallet = "wallet" ) ); let b256 = Bits256([1; 32]); let response = script_instance.main(b256).call().await?; assert_eq!(response.value, Bits256([2; 32])); Ok(()) } #[tokio::test] async fn main_function_generic_arguments() -> Result<()> { setup_program_test!( Wallets("wallet"), Abigen(Script( name = "MyScript", project = "e2e/sway/types/scripts/script_generics" )), LoadScript( name = "script_instance", script = "MyScript", wallet = "wallet" ) ); let bim = GenericBimbam { val: 90 }; let bam_comp = GenericBimbam { val: 4342 }; let bam = GenericSnack { twix: bam_comp, mars: 1000, }; let result = script_instance .main(bim.clone(), bam.clone()) .call() .await?; let expected = ( GenericSnack { twix: GenericBimbam { val: bam.mars as u64, }, mars: 2 * bim.val as u32, }, GenericBimbam { val: 255_u8 }, ); assert_eq!(result.value, expected); Ok(()) } #[tokio::test] async fn main_function_option_result() -> Result<()> { setup_program_test!( Wallets("wallet"), Abigen(Script( name = "MyScript", project = "e2e/sway/types/scripts/options_results" )), LoadScript( name = "script_instance", script = "MyScript", wallet = "wallet" ) ); { let result = script_instance.main(Some(42), None).call().await?; assert_eq!(result.value, Ok(Some(true))); } { let result = script_instance.main(Some(987), None).call().await?; assert_eq!(result.value, Ok(None)); } { let expected_error = Err(TestError::ZimZam("error".try_into().unwrap())); let result = script_instance.main(None, Some(987)).call().await?; assert_eq!(result.value, expected_error); } Ok(()) } #[tokio::test] async fn main_function_tuple_types() -> Result<()> { setup_program_test!( Wallets("wallet"), Abigen(Script( name = "MyScript", project = "e2e/sway/types/scripts/script_tuples" )), LoadScript( name = "script_instance", script = "MyScript", wallet = "wallet" ) ); let bim = Bim { bim: 90 }; let bam = Bam { bam: "itest".try_into()?, }; let boum = Boum { boum: true }; let result = script_instance .main( (bim, bam, boum), Bam { bam: "twice".try_into()?, }, ) .call() .await?; let expected = ( ( Boum { boum: true }, Bim { bim: 193817 }, Bam { bam: "hello".try_into()?, }, ), 42242, ); assert_eq!(result.value, expected); Ok(()) } #[tokio::test] async fn main_function_vector_arguments() -> Result<()> { setup_program_test!( Wallets("wallet"), Abigen(Script( name = "MyScript", project = "e2e/sway/types/scripts/script_vectors" )), LoadScript( name = "script_instance", script = "MyScript", wallet = "wallet" ) ); let u32_vec = vec![0, 1, 2]; let vec_in_vec = vec![vec![0, 1, 2], vec![0, 1, 2]]; let struct_in_vec = vec![SomeStruct { a: 0 }, SomeStruct { a: 1 }]; let vec_in_struct = SomeStruct { a: vec![0, 1, 2] }; let array_in_vec = vec![[0u64, 1u64], [0u64, 1u64]]; let vec_in_array = [vec![0, 1, 2], vec![0, 1, 2]]; let vec_in_enum = SomeEnum::a(vec![0, 1, 2]); let enum_in_vec = vec![SomeEnum::a(0), SomeEnum::a(1)]; let b256_in_vec = vec![Bits256([2; 32]), Bits256([2; 32])]; let tuple_in_vec = vec![(0, 0), (1, 1)]; let vec_in_tuple = (vec![0, 1, 2], vec![0, 1, 2]); let vec_in_a_vec_in_a_struct_in_a_vec = vec![ SomeStruct { a: vec![vec![0, 1, 2], vec![3, 4, 5]], }, SomeStruct { a: vec![vec![6, 7, 8], vec![9, 10, 11]], }, ]; let result = script_instance .main( u32_vec, vec_in_vec, struct_in_vec, vec_in_struct, array_in_vec, vec_in_array, vec_in_enum, enum_in_vec, b256_in_vec, tuple_in_vec, vec_in_tuple, vec_in_a_vec_in_a_struct_in_a_vec, ) .call() .await?; assert!(result.value); Ok(()) } #[tokio::test] async fn script_raw_slice() -> Result<()> { setup_program_test!( Wallets("wallet"), Abigen(Script( name = "BimBamScript", project = "e2e/sway/types/scripts/script_raw_slice", )), LoadScript( name = "script_instance", script = "BimBamScript", wallet = "wallet" ) ); let raw_slice = RawSlice(vec![40, 41, 42]); let wrapper = Wrapper { inner: vec![raw_slice.clone(), raw_slice.clone()], inner_enum: SomeEnum::Second(raw_slice), }; let rtn = script_instance.main(6, wrapper).call().await?.value; assert_eq!(rtn, RawSlice(vec![0, 1, 2, 3, 4, 5])); Ok(()) } #[tokio::test] async fn main_function_bytes_arguments() -> Result<()> { setup_program_test!( Wallets("wallet"), Abigen(Script( name = "BimBamScript", project = "e2e/sway/types/scripts/script_bytes", )), LoadScript( name = "script_instance", script = "BimBamScript", wallet = "wallet" ) ); let bytes = Bytes(vec![40, 41, 42]); let wrapper = Wrapper { inner: vec![bytes.clone(), bytes.clone()], inner_enum: SomeEnum::Second(bytes), }; script_instance.main(10, wrapper).call().await?; Ok(()) } fn u128_from(parts: (u64, u64)) -> u128 { let bytes: [u8; 16] = [parts.0.to_be_bytes(), parts.1.to_be_bytes()] .concat() .try_into() .unwrap(); u128::from_be_bytes(bytes) } #[tokio::test] async fn script_handles_u128() -> Result<()> { setup_program_test!( Wallets("wallet"), Abigen(Script( name = "MyScript", project = "e2e/sway/types/scripts/script_u128", )), LoadScript( name = "script_instance", script = "MyScript", wallet = "wallet" ) ); let arg = u128_from((10, 20)); let actual = script_instance.main(arg).call().await?.value; let expected = arg + u128_from((8, 2)); assert_eq!(expected, actual); Ok(()) } fn u256_from(parts: (u64, u64, u64, u64)) -> U256 { let bytes: [u8; 32] = [ parts.0.to_be_bytes(), parts.1.to_be_bytes(), parts.2.to_be_bytes(), parts.3.to_be_bytes(), ] .concat() .try_into() .unwrap(); U256::from(bytes) } #[tokio::test] async fn script_handles_u256() -> Result<()> { setup_program_test!( Wallets("wallet"), Abigen(Script( name = "MyScript", project = "e2e/sway/types/scripts/script_u256", )), LoadScript( name = "script_instance", script = "MyScript", wallet = "wallet" ) ); let arg = u256_from((10, 20, 30, 40)); let actual = script_instance.main(arg).call().await?.value; let expected = arg + u256_from((6, 7, 8, 9)); assert_eq!(expected, actual); Ok(()) } #[tokio::test] async fn script_std_string() -> Result<()> { setup_program_test!( Wallets("wallet"), Abigen(Script( name = "MyScript", project = "e2e/sway/types/scripts/script_std_lib_string", )), LoadScript( name = "script_instance", script = "MyScript", wallet = "wallet" ) ); let response = script_instance .main("script-input".to_string()) .call() .await?; assert_eq!(response.value, "script-return".to_string()); Ok(()) } #[tokio::test] // TODO: Uncomment this test when we find the reason why it fails #[ignore] async fn script_string_slice() -> Result<()> { setup_program_test!( Wallets("wallet"), Abigen(Script( name = "MyScript", project = "e2e/sway/types/scripts/script_string_slice", )), LoadScript( name = "script_instance", script = "MyScript", wallet = "wallet" ) ); let response = script_instance .main("script-input".try_into()?) .call() .await?; assert_eq!(response.value, "script-return"); Ok(()) } #[tokio::test] async fn nested_heap_types() -> Result<()> { setup_program_test!( Wallets("wallet"), Abigen(Script( name = "MyScript", project = "e2e/sway/types/scripts/script_heap_types", )), LoadScript( name = "script_instance", script = "MyScript", wallet = "wallet" ) ); let arr = [2u8, 4, 8]; let struct_generics = StructGenerics { one: Bytes(arr.to_vec()), two: String::from("fuel"), three: arr.to_vec(), }; let enum_vec = [struct_generics.clone(), struct_generics].to_vec(); let expected = EnumGeneric::One(enum_vec); let result = script_instance.main().call().await?; assert_eq!(result.value, expected); Ok(()) } ================================================ FILE: e2e/tests/wallets.rs ================================================ use fuels::{ accounts::signers::private_key::PrivateKeySigner, prelude::*, types::{coin_type::CoinType, input::Input, output::Output}, }; use rand::{Rng, thread_rng}; async fn assert_address_balance( address: &Address, provider: &Provider, asset_id: &AssetId, amount: u128, ) { let balance = provider .get_asset_balance(address, asset_id) .await .expect("Could not retrieve balance"); assert_eq!(balance, amount); } #[tokio::test] async fn test_wallet_balance_api_multi_asset() -> Result<()> { let signer = PrivateKeySigner::random(&mut rand::thread_rng()); let number_of_assets = 7; let coins_per_asset = 21; let amount_per_coin = 11; let (coins, asset_ids) = setup_multiple_assets_coins( signer.address(), number_of_assets, coins_per_asset, amount_per_coin, ); let provider = setup_test_provider(coins.clone(), vec![], None, None).await?; let wallet = Wallet::new(signer, provider.clone()); let balances = wallet.get_balances().await?; assert_eq!(balances.len() as u64, number_of_assets); for asset_id in asset_ids { let balance = wallet.get_asset_balance(&asset_id).await?; assert_eq!(balance, (coins_per_asset * amount_per_coin) as u128); let expected_key = asset_id.to_string(); assert!(balances.contains_key(&expected_key)); assert_eq!( *balances.get(&expected_key).unwrap(), (coins_per_asset * amount_per_coin) as u128 ); } Ok(()) } #[tokio::test] async fn test_wallet_balance_api_single_asset() -> Result<()> { let signer = PrivateKeySigner::random(&mut rand::thread_rng()); let number_of_coins = 21; let amount_per_coin = 11; let coins = setup_single_asset_coins( signer.address(), AssetId::zeroed(), number_of_coins, amount_per_coin, ); let provider = setup_test_provider(coins.clone(), vec![], None, None).await?; let wallet = Wallet::new(signer, provider.clone()); for coin in coins { let balance = wallet.get_asset_balance(&coin.asset_id).await?; assert_eq!(balance, (number_of_coins * amount_per_coin) as u128); } let balances = wallet.get_balances().await?; let expected_key = AssetId::zeroed().to_string(); assert_eq!(balances.len(), 1); // only the base asset assert!(balances.contains_key(&expected_key)); assert_eq!( *balances.get(&expected_key).unwrap(), (number_of_coins * amount_per_coin) as u128 ); Ok(()) } fn base_asset_wallet_config(num_wallets: u64) -> WalletsConfig { let asset_configs = vec![AssetConfig { id: AssetId::zeroed(), num_coins: 20, coin_amount: 20, }]; WalletsConfig::new_multiple_assets(num_wallets, asset_configs) } #[tokio::test] async fn adjust_fee_empty_transaction() -> Result<()> { let wallet = launch_provider_and_get_wallet().await?; let mut tb = ScriptTransactionBuilder::prepare_transfer(vec![], vec![], TxPolicies::default()); assert!(tb.inputs().is_empty()); assert!(tb.outputs().is_empty()); wallet.add_witnesses(&mut tb)?; wallet.adjust_for_fee(&mut tb, 0).await?; assert!(!tb.inputs().is_empty(), "inputs should be added"); assert_eq!(tb.outputs().len(), 1, "output should be added"); let tx = tb.build(wallet.provider()).await?; let total_amount_inputs: u64 = tx.inputs().iter().map(|i| i.amount().unwrap()).sum(); assert!( total_amount_inputs > tx.max_fee().unwrap(), "amount should cover tx" ); let expected_outputs = vec![Output::change(wallet.address(), 0, AssetId::zeroed())]; assert_eq!(tx.outputs(), &expected_outputs); Ok(()) } #[tokio::test] async fn adjust_for_fee_with_message_data_input() -> Result<()> { let wallet_signer = PrivateKeySigner::random(&mut rand::thread_rng()); let receiver_signer = PrivateKeySigner::random(&mut rand::thread_rng()); let messages = setup_single_message( Address::default(), wallet_signer.address(), 100, 0.into(), vec![1, 2, 3], // has data ); let asset_id = AssetId::zeroed(); let coins = setup_single_asset_coins(wallet_signer.address(), asset_id, 1, 50); let provider = setup_test_provider(coins, vec![messages], None, None).await?; let wallet = Wallet::new(wallet_signer, provider.clone()); let receiver = Wallet::new(receiver_signer, provider.clone()); let amount_to_send = 14; let message = wallet.get_messages().await?.pop().unwrap(); let input = Input::resource_signed(CoinType::Message(message)); let outputs = wallet.get_asset_outputs_for_amount(receiver.address(), asset_id, amount_to_send); { // message with data as only input - without adjust for fee let mut tb = ScriptTransactionBuilder::prepare_transfer( vec![input.clone()], outputs.clone(), TxPolicies::default(), ); wallet.add_witnesses(&mut tb)?; let tx = tb.build(wallet.provider()).await?; let err = provider .send_transaction_and_await_commit(tx) .await .unwrap_err(); assert!(err.to_string().contains("Validity(NoSpendableInput)")); } { // message with data as only input - with adjust for fee let mut tb = ScriptTransactionBuilder::prepare_transfer( vec![input.clone()], outputs.clone(), TxPolicies::default(), ); wallet.adjust_for_fee(&mut tb, 0).await.unwrap(); wallet.add_witnesses(&mut tb)?; let tx = tb.build(wallet.provider()).await?; assert_eq!(receiver.get_asset_balance(&asset_id).await?, 0); provider .send_transaction_and_await_commit(tx) .await .unwrap(); assert_eq!( receiver.get_asset_balance(&asset_id).await?, amount_to_send as u128 ); } Ok(()) } #[tokio::test] async fn adjust_fee_resources_to_transfer_with_base_asset() -> Result<()> { let wallet = launch_provider_and_get_wallet().await?; let base_amount = 30; let base_asset_id = AssetId::zeroed(); let inputs = wallet .get_asset_inputs_for_amount(base_asset_id, base_amount.into(), None) .await?; let outputs = wallet.get_asset_outputs_for_amount(Address::zeroed(), base_asset_id, base_amount); let mut tb = ScriptTransactionBuilder::prepare_transfer(inputs, outputs, TxPolicies::default()); wallet.adjust_for_fee(&mut tb, base_amount.into()).await?; wallet.add_witnesses(&mut tb)?; let tx = tb.build(wallet.provider()).await?; let total_amount_inputs: u64 = tx.inputs().iter().map(|i| i.amount().unwrap()).sum(); assert!(total_amount_inputs > tx.max_fee().unwrap()); // can cover tx let expected_outputs = vec![ Output::coin(Address::zeroed(), base_amount, base_asset_id), Output::change(wallet.address(), 0, base_asset_id), ]; assert_eq!(tx.outputs(), &expected_outputs); Ok(()) } #[tokio::test] async fn test_transfer() -> Result<()> { let wallet_1_signer = PrivateKeySigner::random(&mut rand::thread_rng()); let wallet_2_signer = PrivateKeySigner::random(&mut rand::thread_rng()); let amount = 100; let num_coins = 1; let base_asset_id = AssetId::zeroed(); let mut coins_1 = setup_single_asset_coins(wallet_1_signer.address(), base_asset_id, num_coins, amount); let coins_2 = setup_single_asset_coins(wallet_2_signer.address(), base_asset_id, num_coins, amount); coins_1.extend(coins_2); let provider = setup_test_provider(coins_1, vec![], None, None).await?; let wallet_1 = Wallet::new(wallet_1_signer, provider.clone()); let wallet_2 = Wallet::new(wallet_2_signer, provider.clone()).lock(); let _ = wallet_1 .transfer( wallet_2.address(), amount / 2, Default::default(), TxPolicies::default(), ) .await .unwrap(); let wallet_2_coins = wallet_2.get_coins(base_asset_id).await.unwrap(); let wallet_2_balance = wallet_2.get_asset_balance(&base_asset_id).await?; assert_eq!(wallet_2_coins.len(), 2); assert_eq!(wallet_2_balance, (amount + amount / 2) as u128); Ok(()) } #[tokio::test] async fn send_transfer_transactions() -> Result<()> { let amount = 5; let (wallet_1, wallet_2) = setup_transfer_test(amount).await?; // Configure transaction policies let tip = 2; let script_gas_limit = 500_000; let maturity = 0; let tx_policies = TxPolicies::default() .with_tip(tip) .with_maturity(maturity) .with_script_gas_limit(script_gas_limit); // Transfer 1 from wallet 1 to wallet 2. let amount_to_send = 1; let base_asset_id = AssetId::zeroed(); let tx_id = wallet_1 .transfer( wallet_2.address(), amount_to_send, base_asset_id, tx_policies, ) .await? .tx_id; // Assert that the transaction was properly configured. let res = wallet_1 .try_provider()? .get_transaction_by_id(&tx_id) .await? .unwrap(); let script: ScriptTransaction = match res.transaction { TransactionType::Script(tx) => tx, _ => panic!("Received unexpected tx type!"), }; // Transfer scripts uses set `script_gas_limit` despite not having script code assert_eq!(script.gas_limit(), script_gas_limit); assert_eq!(script.maturity().unwrap(), maturity); let wallet_1_spendable_resources = wallet_1 .get_spendable_resources(base_asset_id, 1, None) .await?; let wallet_2_spendable_resources = wallet_2 .get_spendable_resources(base_asset_id, 1, None) .await?; let wallet_1_all_coins = wallet_1.get_coins(base_asset_id).await?; let wallet_2_all_coins = wallet_2.get_coins(base_asset_id).await?; // wallet_1 has now only one spent coin assert_eq!(wallet_1_spendable_resources.len(), 1); assert_eq!(wallet_1_all_coins.len(), 1); // Check that wallet two now has a coin. assert_eq!(wallet_2_all_coins.len(), 1); assert_eq!(wallet_2_spendable_resources.len(), 1); Ok(()) } #[tokio::test] async fn transfer_coins_with_change() -> Result<()> { const AMOUNT: u64 = 5; let (wallet_1, wallet_2) = setup_transfer_test(AMOUNT).await?; // Transfer 2 from wallet 1 to wallet 2. const SEND_AMOUNT: u64 = 2; let fee = wallet_1 .transfer( wallet_2.address(), SEND_AMOUNT, AssetId::zeroed(), TxPolicies::default(), ) .await? .tx_status .total_fee; let base_asset_id = AssetId::zeroed(); let wallet_1_final_coins = wallet_1 .get_spendable_resources(base_asset_id, 1, None) .await?; // Assert that we've sent 2 from wallet 1, resulting in an amount of 3 in wallet 1. let resulting_amount = wallet_1_final_coins.first().unwrap(); assert_eq!(resulting_amount.amount(), AMOUNT - SEND_AMOUNT - fee); let wallet_2_final_coins = wallet_2.get_coins(base_asset_id).await?; assert_eq!(wallet_2_final_coins.len(), 1); let total_amount: u64 = wallet_2_final_coins.iter().map(|c| c.amount).sum(); assert_eq!(total_amount, SEND_AMOUNT); Ok(()) } #[tokio::test] async fn test_wallet_get_coins() -> Result<()> { const AMOUNT: u64 = 1000; const NUM_COINS: u64 = 3; let addr = Address::zeroed(); let coins = setup_single_asset_coins(addr, AssetId::zeroed(), NUM_COINS, AMOUNT); let provider = setup_test_provider(coins, vec![], None, None).await?; let wallet = Wallet::new_locked(addr, provider.clone()); let consensus_parameters = provider.consensus_parameters().await?; let wallet_initial_coins = wallet .get_coins(*consensus_parameters.base_asset_id()) .await?; let total_amount: u64 = wallet_initial_coins.iter().map(|c| c.amount).sum(); assert_eq!(wallet_initial_coins.len(), NUM_COINS as usize); assert_eq!(total_amount, AMOUNT * NUM_COINS); Ok(()) } async fn setup_transfer_test(amount: u64) -> Result<(Wallet, Wallet)> { let wallet_1_signer = PrivateKeySigner::random(&mut rand::thread_rng()); let coins = setup_single_asset_coins(wallet_1_signer.address(), AssetId::zeroed(), 1, amount); let provider = setup_test_provider(coins, vec![], None, None).await?; let wallet_1 = Wallet::new(wallet_1_signer, provider.clone()); let wallet_2 = Wallet::random(&mut thread_rng(), provider.clone()); Ok((wallet_1, wallet_2)) } #[tokio::test] async fn transfer_more_than_owned() -> Result<()> { const AMOUNT: u64 = 1000000; let (wallet_1, wallet_2) = setup_transfer_test(AMOUNT).await?; // Transferring more than balance should fail. let response = wallet_1 .transfer( wallet_2.address(), AMOUNT * 2, Default::default(), TxPolicies::default(), ) .await; assert!(response.is_err()); let wallet_2_coins = wallet_2.get_coins(AssetId::zeroed()).await?; assert_eq!(wallet_2_coins.len(), 0); Ok(()) } #[tokio::test] async fn transfer_coins_of_non_base_asset() -> Result<()> { const AMOUNT: u64 = 10000; let wallet_1_signer = PrivateKeySigner::random(&mut rand::thread_rng()); let asset_id: AssetId = AssetId::from([1; 32usize]); let mut coins = setup_single_asset_coins(wallet_1_signer.address(), asset_id, 1, AMOUNT); // setup base asset coins to pay tx fees let base_coins = setup_single_asset_coins(wallet_1_signer.address(), AssetId::zeroed(), 1, AMOUNT); coins.extend(base_coins); let provider = setup_test_provider(coins, vec![], None, None).await?; let wallet_1 = Wallet::new(wallet_1_signer, provider.clone()); let wallet_2 = Wallet::random(&mut thread_rng(), provider.clone()); const SEND_AMOUNT: u64 = 200; let _ = wallet_1 .transfer( wallet_2.address(), SEND_AMOUNT, asset_id, TxPolicies::default(), ) .await?; let wallet_1_balance = wallet_1.get_asset_balance(&asset_id).await?; assert_eq!(wallet_1_balance, (AMOUNT - SEND_AMOUNT) as u128); let wallet_2_final_coins = wallet_2.get_coins(asset_id).await?; assert_eq!(wallet_2_final_coins.len(), 1); let total_amount: u64 = wallet_2_final_coins.iter().map(|c| c.amount).sum(); assert_eq!(total_amount, SEND_AMOUNT); Ok(()) } #[tokio::test] async fn test_transfer_with_multiple_signatures() -> Result<()> { let wallet_config = base_asset_wallet_config(5); let wallets = launch_custom_provider_and_get_wallets(wallet_config, None, None).await?; let provider = wallets[0].try_provider()?; let receiver = Wallet::random(&mut thread_rng(), provider.clone()); let amount_to_transfer = 20u64; let mut inputs = vec![]; let consensus_parameters = provider.consensus_parameters().await?; for wallet in &wallets { inputs.extend( wallet .get_asset_inputs_for_amount( *consensus_parameters.base_asset_id(), amount_to_transfer.into(), None, ) .await?, ); } let amount_to_receive = amount_to_transfer * wallets.len() as u64; // all change goes to the first wallet let outputs = wallets[0].get_asset_outputs_for_amount( receiver.address(), *consensus_parameters.base_asset_id(), amount_to_receive, ); let mut tb = ScriptTransactionBuilder::prepare_transfer(inputs, outputs, TxPolicies::default()); for wallet in wallets.iter() { wallet.add_witnesses(&mut tb)? } let tx = tb.build(provider).await?; provider.send_transaction_and_await_commit(tx).await?; assert_eq!( receiver .get_asset_balance(consensus_parameters.base_asset_id()) .await?, amount_to_receive as u128, ); Ok(()) } #[tokio::test] async fn wallet_transfer_respects_maturity_and_expiration() -> Result<()> { let wallet = launch_provider_and_get_wallet().await?; let asset_id = AssetId::zeroed(); let wallet_balance = wallet.get_asset_balance(&asset_id).await?; let provider = wallet.provider(); let receiver: Address = thread_rng().r#gen(); let maturity = 10; let expiration = 20; let tx_policies = TxPolicies::default() .with_maturity(maturity) .with_expiration(expiration); let amount_to_send = 10; { let err = wallet .transfer(receiver, amount_to_send, asset_id, tx_policies) .await .expect_err("maturity not reached"); assert!(err.to_string().contains("TransactionMaturity")); } let transaction_fee = { provider.produce_blocks(15, None).await?; wallet .transfer(receiver, amount_to_send, asset_id, tx_policies) .await .expect("should succeed. Block height between `maturity` and `expiration`") .tx_status .total_fee }; { provider.produce_blocks(15, None).await?; let err = wallet .transfer(receiver, amount_to_send, asset_id, tx_policies) .await .expect_err("expiration reached"); assert!(err.to_string().contains("TransactionExpiration")); } // Wallet has spent the funds assert_address_balance( &wallet.address(), provider, &asset_id, wallet_balance - amount_to_send as u128 - transaction_fee as u128, ) .await; // Funds were transferred assert_address_balance(&receiver, provider, &asset_id, amount_to_send as u128).await; Ok(()) } ================================================ FILE: examples/codec/Cargo.toml ================================================ [package] name = "fuels-example-codec" version = { workspace = true } authors = { workspace = true } edition = { workspace = true } homepage = { workspace = true } license = { workspace = true } publish = false repository = { workspace = true } description = "Fuel Rust SDK codec examples." [dev-dependencies] fuels = { workspace = true, features = ["default"] } tokio = { workspace = true, features = ["full"] } ================================================ FILE: examples/codec/src/lib.rs ================================================ #[cfg(test)] mod tests { use fuels::{ core::codec::{DecoderConfig, EncoderConfig}, types::errors::Result, }; #[test] fn encoding_a_type() -> Result<()> { //ANCHOR: encoding_example use fuels::{ core::{codec::ABIEncoder, traits::Tokenizable}, macros::Tokenizable, }; #[derive(Tokenizable)] struct MyStruct { field: u64, } let instance = MyStruct { field: 101 }; let _encoded: Vec = ABIEncoder::default().encode(&[instance.into_token()])?; //ANCHOR_END: encoding_example Ok(()) } #[test] fn encoding_via_macro() -> Result<()> { //ANCHOR: encoding_example_w_macro use fuels::{core::codec::calldata, macros::Tokenizable}; #[derive(Tokenizable)] struct MyStruct { field: u64, } let _: Vec = calldata!(MyStruct { field: 101 }, MyStruct { field: 102 })?; //ANCHOR_END: encoding_example_w_macro Ok(()) } #[test] fn decoding_example() -> Result<()> { // ANCHOR: decoding_example use fuels::{ core::{ codec::ABIDecoder, traits::{Parameterize, Tokenizable}, }, macros::{Parameterize, Tokenizable}, types::Token, }; #[derive(Parameterize, Tokenizable)] struct MyStruct { field: u64, } let bytes: &[u8] = &[0, 0, 0, 0, 0, 0, 0, 101]; let token: Token = ABIDecoder::default().decode(&MyStruct::param_type(), bytes)?; let _: MyStruct = MyStruct::from_token(token)?; // ANCHOR_END: decoding_example Ok(()) } #[test] fn decoding_example_try_into() -> Result<()> { // ANCHOR: decoding_example_try_into use fuels::macros::{Parameterize, Tokenizable, TryFrom}; #[derive(Parameterize, Tokenizable, TryFrom)] struct MyStruct { field: u64, } let bytes: &[u8] = &[0, 0, 0, 0, 0, 0, 0, 101]; let _: MyStruct = bytes.try_into()?; // ANCHOR_END: decoding_example_try_into Ok(()) } #[test] fn configuring_the_decoder() -> Result<()> { // ANCHOR: configuring_the_decoder use fuels::core::codec::ABIDecoder; ABIDecoder::new(DecoderConfig { max_depth: 5, max_tokens: 100, }); // ANCHOR_END: configuring_the_decoder Ok(()) } #[test] fn configuring_the_encoder() -> Result<()> { // ANCHOR: configuring_the_encoder use fuels::core::codec::ABIEncoder; ABIEncoder::new(EncoderConfig { max_depth: 5, max_tokens: 100, }); // ANCHOR_END: configuring_the_encoder Ok(()) } } ================================================ FILE: examples/contracts/Cargo.toml ================================================ [package] name = "fuels-example-contracts" version = { workspace = true } authors = { workspace = true } edition = { workspace = true } homepage = { workspace = true } license = { workspace = true } publish = false repository = { workspace = true } description = "Fuel Rust SDK contract examples." [dependencies] fuels = { workspace = true, features = ["default"] } rand = { workspace = true, features = ["default"] } tokio = { workspace = true, features = ["full"] } tempfile = { workspace = true } [features] fuel-core-lib = ["fuels/fuel-core-lib"] rocksdb = ["fuels/rocksdb"] ================================================ FILE: examples/contracts/src/lib.rs ================================================ #[cfg(test)] mod tests { use std::{collections::HashSet, time::Duration}; use fuels::{ accounts::signers::{fake::FakeSigner, private_key::PrivateKeySigner}, core::codec::{ABIFormatter, DecoderConfig, EncoderConfig, encode_fn_selector}, prelude::{LoadConfiguration, NodeConfig, StorageConfiguration}, programs::debug::ScriptType, test_helpers::{ChainConfig, StateConfig}, tx::ContractIdExt, types::{ SubAssetId, errors::{Result, transaction::Reason}, }, }; use rand::{Rng, thread_rng}; #[tokio::test] async fn instantiate_client() -> Result<()> { // ANCHOR: instantiate_client use fuels::prelude::{FuelService, Provider}; // Run the fuel node. let server = FuelService::start( NodeConfig::default(), ChainConfig::default(), StateConfig::default(), ) .await?; // Create a client that will talk to the node created above. let client = Provider::from(server.bound_address()).await?; assert!(client.healthy().await?); // ANCHOR_END: instantiate_client Ok(()) } #[tokio::test] async fn deploy_contract() -> Result<()> { use fuels::prelude::*; // ANCHOR: deploy_contract // This helper will launch a local node and provide a test wallet linked to it let wallet = launch_provider_and_get_wallet().await?; // This will load and deploy your contract binary to the chain so that its ID can // be used to initialize the instance let contract_id = Contract::load_from( "../../e2e/sway/contracts/contract_test/out/release/contract_test.bin", LoadConfiguration::default(), )? .deploy(&wallet, TxPolicies::default()) .await? .contract_id; println!("Contract deployed @ {contract_id}"); // ANCHOR_END: deploy_contract Ok(()) } #[tokio::test] async fn setup_program_test_example() -> Result<()> { use fuels::prelude::*; // ANCHOR: deploy_contract_setup_macro_short setup_program_test!( Wallets("wallet"), Abigen(Contract( name = "TestContract", project = "e2e/sway/contracts/contract_test" )), Deploy( name = "contract_instance", contract = "TestContract", wallet = "wallet" ), ); let response = contract_instance .methods() .initialize_counter(42) .call() .await?; assert_eq!(42, response.value); // ANCHOR_END: deploy_contract_setup_macro_short Ok(()) } #[tokio::test] async fn contract_call_cost_estimation() -> Result<()> { use fuels::prelude::*; abigen!(Contract( name = "MyContract", abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json" )); let wallet = launch_provider_and_get_wallet().await?; let contract_id = Contract::load_from( "../../e2e/sway/contracts/contract_test/out/release/contract_test.bin", LoadConfiguration::default(), )? .deploy(&wallet, TxPolicies::default()) .await? .contract_id; // ANCHOR: contract_call_cost_estimation let contract_instance = MyContract::new(contract_id, wallet); let tolerance = Some(0.0); let block_horizon = Some(1); let transaction_cost = contract_instance .methods() .initialize_counter(42) // Build the ABI call .estimate_transaction_cost(tolerance, block_horizon) // Get estimated transaction cost .await?; // ANCHOR_END: contract_call_cost_estimation let expected_script_gas = 2340; let expected_total_gas = 8592; assert_eq!(transaction_cost.script_gas, expected_script_gas); assert_eq!(transaction_cost.total_gas, expected_total_gas); Ok(()) } #[tokio::test] async fn deploy_with_parameters() -> std::result::Result<(), Box> { use fuels::{prelude::*, tx::StorageSlot, types::Bytes32}; use rand::prelude::{Rng, SeedableRng, StdRng}; let wallet = launch_provider_and_get_wallet().await?; let contract_id_1 = Contract::load_from( "../../e2e/sway/contracts/contract_test/out/release/contract_test.bin", LoadConfiguration::default(), )? .deploy(&wallet, TxPolicies::default()) .await? .contract_id; // ANCHOR: deploy_with_parameters // Optional: Add `Salt` let rng = &mut StdRng::seed_from_u64(2322u64); let salt: [u8; 32] = rng.r#gen(); // Optional: Configure storage let key = Bytes32::from([1u8; 32]); let value = Bytes32::from([2u8; 32]); let storage_slot = StorageSlot::new(key, value); let storage_configuration = StorageConfiguration::default().add_slot_overrides([storage_slot]); let configuration = LoadConfiguration::default() .with_storage_configuration(storage_configuration) .with_salt(salt); // Optional: Configure deployment parameters let tx_policies = TxPolicies::default() .with_tip(1) .with_script_gas_limit(1_000_000) .with_maturity(0) .with_expiration(10_000); let contract_id_2 = Contract::load_from( "../../e2e/sway/contracts/contract_test/out/release/contract_test.bin", configuration, )? .deploy(&wallet, tx_policies) .await? .contract_id; println!("Contract deployed @ {contract_id_2}"); // ANCHOR_END: deploy_with_parameters assert_ne!(contract_id_1, contract_id_2); // ANCHOR: use_deployed_contract // This will generate your contract's methods onto `MyContract`. // This means an instance of `MyContract` will have access to all // your contract's methods that are running on-chain! // ANCHOR: abigen_example abigen!(Contract( name = "MyContract", abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json" )); // ANCHOR_END: abigen_example // This is an instance of your contract which you can use to make calls to your functions let contract_instance = MyContract::new(contract_id_2, wallet); let response = contract_instance .methods() .initialize_counter(42) // Build the ABI call .call() // Perform the network call .await?; assert_eq!(42, response.value); let response = contract_instance .methods() .increment_counter(10) .call() .await?; assert_eq!(52, response.value); // ANCHOR_END: use_deployed_contract // ANCHOR: submit_response_contract let response = contract_instance .methods() .initialize_counter(42) .submit() .await?; tokio::time::sleep(Duration::from_millis(500)).await; let value = response.response().await?.value; // ANCHOR_END: submit_response_contract assert_eq!(42, value); Ok(()) } #[tokio::test] async fn deploy_with_multiple_wallets() -> Result<()> { use fuels::prelude::*; abigen!(Contract( name = "MyContract", abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json" )); let wallets = launch_custom_provider_and_get_wallets(WalletsConfig::default(), None, None).await?; let contract_id_1 = Contract::load_from( "../../e2e/sway/contracts/contract_test/out/release/contract_test.bin", LoadConfiguration::default(), )? .deploy(&wallets[0], TxPolicies::default()) .await? .contract_id; let contract_instance_1 = MyContract::new(contract_id_1, wallets[0].clone()); let response = contract_instance_1 .methods() .initialize_counter(42) .call() .await?; assert_eq!(42, response.value); let contract_id_2 = Contract::load_from( "../../e2e/sway/contracts/contract_test/out/release/contract_test.bin", LoadConfiguration::default().with_salt([1; 32]), )? .deploy(&wallets[1], TxPolicies::default()) .await? .contract_id; let contract_instance_2 = MyContract::new(contract_id_2, wallets[1].clone()); let response = contract_instance_2 .methods() .initialize_counter(42) // Build the ABI call .call() .await?; assert_eq!(42, response.value); Ok(()) } #[tokio::test] #[allow(unused_variables)] async fn contract_tx_and_call_params() -> Result<()> { use fuels::prelude::*; abigen!(Contract( name = "MyContract", abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json" )); let wallet = launch_provider_and_get_wallet().await?; let contract_id = Contract::load_from( "../../e2e/sway/contracts/contract_test/out/release/contract_test.bin", LoadConfiguration::default(), )? .deploy(&wallet, TxPolicies::default()) .await? .contract_id; // ANCHOR: tx_policies let contract_methods = MyContract::new(contract_id, wallet.clone()).methods(); let tx_policies = TxPolicies::default() .with_tip(1) .with_script_gas_limit(1_000_000) .with_maturity(0) .with_expiration(10_000); let response = contract_methods .initialize_counter(42) // Our contract method .with_tx_policies(tx_policies) // Chain the tx policies .call() // Perform the contract call .await?; // This is an async call, `.await` it. // ANCHOR_END: tx_policies // ANCHOR: tx_policies_default let response = contract_methods .initialize_counter(42) .with_tx_policies(TxPolicies::default()) .call() .await?; // ANCHOR_END: tx_policies_default // ANCHOR: call_parameters let contract_methods = MyContract::new(contract_id, wallet.clone()).methods(); let tx_policies = TxPolicies::default(); // Forward 1_000_000 coin amount of base asset_id // this is a big number for checking that amount can be a u64 let call_params = CallParameters::default().with_amount(1_000_000); let response = contract_methods .get_msg_amount() // Our contract method. .with_tx_policies(tx_policies) // Chain the tx policies. .call_params(call_params)? // Chain the call parameters. .call() // Perform the contract call. .await?; // ANCHOR_END: call_parameters // ANCHOR: call_parameters_default let response = contract_methods .initialize_counter(42) .call_params(CallParameters::default())? .call() .await?; // ANCHOR_END: call_parameters_default Ok(()) } #[tokio::test] #[allow(unused_variables)] #[cfg(any(not(feature = "fuel-core-lib"), feature = "rocksdb"))] async fn token_ops_tests() -> Result<()> { use fuels::{prelude::*, types::SubAssetId}; abigen!(Contract( name = "MyContract", abi = "e2e/sway/contracts/token_ops/out/release/token_ops-abi.json" )); let temp_dir = tempfile::tempdir().expect("failed to make tempdir"); let temp_dir_name = temp_dir .path() .file_name() .expect("failed to get file name") .to_string_lossy() .to_string(); let temp_database_path = temp_dir.path().join("db"); let node_config = NodeConfig { starting_gas_price: 1100, database_type: DbType::RocksDb(Some(temp_database_path)), historical_execution: true, ..NodeConfig::default() }; let chain_config = ChainConfig { chain_name: temp_dir_name, ..ChainConfig::default() }; let wallets = launch_custom_provider_and_get_wallets( WalletsConfig::default(), Some(node_config), Some(chain_config), ) .await?; let wallet = wallets.first().expect("is there"); let contract_id = Contract::load_from( "../../e2e/sway/contracts/token_ops/out/release/token_ops.bin", LoadConfiguration::default(), )? .deploy_if_not_exists(wallet, TxPolicies::default()) .await? .contract_id; let contract_methods = MyContract::new(contract_id, wallet.clone()).methods(); // ANCHOR: simulate // you would mint 100 coins if the transaction wasn't simulated let counter = contract_methods .mint_coins(100) .simulate(Execution::realistic()) .await?; // ANCHOR_END: simulate { // ANCHOR: simulate_read_state // you don't need any funds to read state let balance = contract_methods .get_balance(contract_id, AssetId::zeroed()) .simulate(Execution::state_read_only()) .await? .value; // ANCHOR_END: simulate_read_state } { let provider = wallet.provider(); provider.produce_blocks(2, None).await?; let block_height = provider.latest_block_height().await?; // ANCHOR: simulate_read_state_at_height let balance = contract_methods .get_balance(contract_id, AssetId::zeroed()) .simulate(Execution::state_read_only().at_height(block_height)) .await? .value; // ANCHOR_END: simulate_read_state_at_height } let response = contract_methods.mint_coins(1_000_000).call().await?; // ANCHOR: variable_outputs let address = wallet.address(); let asset_id = contract_id.asset_id(&SubAssetId::zeroed()); // withdraw some tokens to wallet let response = contract_methods .transfer(1_000_000, asset_id, address.into()) .with_variable_output_policy(VariableOutputPolicy::Exactly(1)) .call() .await?; // ANCHOR_END: variable_outputs Ok(()) } #[tokio::test] #[allow(unused_variables)] async fn dependency_estimation() -> Result<()> { use fuels::prelude::*; abigen!(Contract( name = "MyContract", abi = "e2e/sway/contracts/lib_contract_caller/out/release/lib_contract_caller-abi.json" )); let wallet = launch_provider_and_get_wallet().await?; let called_contract_id: ContractId = Contract::load_from( "../../e2e/sway/contracts/lib_contract/out/release/lib_contract.bin", LoadConfiguration::default(), )? .deploy(&wallet, TxPolicies::default()) .await? .contract_id; let bin_path = "../../e2e/sway/contracts/lib_contract_caller/out/release/lib_contract_caller.bin"; let caller_contract_id = Contract::load_from(bin_path, LoadConfiguration::default())? .deploy(&wallet, TxPolicies::default()) .await? .contract_id; let contract_methods = MyContract::new(caller_contract_id, wallet.clone()).methods(); // ANCHOR: dependency_estimation_fail let address = wallet.address(); let amount = 100; let response = contract_methods .mint_then_increment_from_contract(called_contract_id, amount, address.into()) .call() .await; assert!(matches!( response, Err(Error::Transaction(Reason::Failure { .. })) )); // ANCHOR_END: dependency_estimation_fail // ANCHOR: dependency_estimation_manual let response = contract_methods .mint_then_increment_from_contract(called_contract_id, amount, address.into()) .with_variable_output_policy(VariableOutputPolicy::Exactly(1)) .with_contract_ids(&[called_contract_id]) .call() .await?; // ANCHOR_END: dependency_estimation_manual let asset_id = caller_contract_id.asset_id(&SubAssetId::zeroed()); let balance = wallet.get_asset_balance(&asset_id).await?; assert_eq!(balance, amount as u128); // ANCHOR: dependency_estimation let response = contract_methods .mint_then_increment_from_contract(called_contract_id, amount, address.into()) .with_variable_output_policy(VariableOutputPolicy::EstimateMinimum) .determine_missing_contracts() .await? .call() .await?; // ANCHOR_END: dependency_estimation let balance = wallet.get_asset_balance(&asset_id).await?; assert_eq!(balance, 2 * amount as u128); Ok(()) } #[tokio::test] #[allow(unused_variables)] async fn get_contract_outputs() -> Result<()> { use fuels::prelude::*; // ANCHOR: deployed_contracts abigen!(Contract( name = "MyContract", // Replace with your contract ABI.json path abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json" )); let wallet_original = launch_provider_and_get_wallet().await?; let wallet = wallet_original.clone(); let contract_id: ContractId = "0x65b6a3d081966040bbccbb7f79ac91b48c635729c59a4c02f15ae7da999b32d3".parse()?; let connected_contract_instance = MyContract::new(contract_id, wallet); // ANCHOR_END: deployed_contracts Ok(()) } #[tokio::test] #[allow(unused_variables)] async fn call_params_gas() -> Result<()> { use fuels::prelude::*; abigen!(Contract( name = "MyContract", abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json" )); let wallet = launch_provider_and_get_wallet().await?; let contract_id = Contract::load_from( "../../e2e/sway/contracts/contract_test/out/release/contract_test.bin", LoadConfiguration::default(), )? .deploy(&wallet, TxPolicies::default()) .await? .contract_id; let contract_methods = MyContract::new(contract_id, wallet.clone()).methods(); // ANCHOR: call_params_gas // Set the transaction `gas_limit` to 1_000_000 and `gas_forwarded` to 4300 to specify that // the contract call transaction may consume up to 1_000_000 gas, while the actual call may // only use 4300 gas let tx_policies = TxPolicies::default().with_script_gas_limit(1_000_000); let call_params = CallParameters::default().with_gas_forwarded(4300); let response = contract_methods .get_msg_amount() // Our contract method. .with_tx_policies(tx_policies) // Chain the tx policies. .call_params(call_params)? // Chain the call parameters. .call() // Perform the contract call. .await?; // ANCHOR_END: call_params_gas Ok(()) } #[tokio::test] #[allow(unused_variables)] async fn multi_call_example() -> Result<()> { use fuels::prelude::*; abigen!(Contract( name = "MyContract", abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json" )); let wallet = launch_provider_and_get_wallet().await?; let contract_id = Contract::load_from( "../../e2e/sway/contracts/contract_test/out/release/contract_test.bin", LoadConfiguration::default(), )? .deploy(&wallet, TxPolicies::default()) .await? .contract_id; // ANCHOR: multi_call_prepare let contract_methods = MyContract::new(contract_id, wallet.clone()).methods(); let call_handler_1 = contract_methods.initialize_counter(42); let call_handler_2 = contract_methods.get_array([42; 2]); // ANCHOR_END: multi_call_prepare // ANCHOR: multi_call_build let multi_call_handler = CallHandler::new_multi_call(wallet.clone()) .add_call(call_handler_1) .add_call(call_handler_2) .with_tx_policies(TxPolicies::default()); // ANCHOR_END: multi_call_build let multi_call_handler_tmp = multi_call_handler.clone(); // ANCHOR: multi_call_values let (counter, array): (u64, [u64; 2]) = multi_call_handler.call().await?.value; // ANCHOR_END: multi_call_values let multi_call_handler = multi_call_handler_tmp.clone(); // ANCHOR: multi_contract_call_response let response = multi_call_handler.call::<(u64, [u64; 2])>().await?; // ANCHOR_END: multi_contract_call_response assert_eq!(counter, 42); assert_eq!(array, [42; 2]); let multi_call_handler = multi_call_handler_tmp.clone(); // ANCHOR: submit_response_multicontract let submitted_tx = multi_call_handler.submit().await?; tokio::time::sleep(Duration::from_millis(500)).await; let (counter, array): (u64, [u64; 2]) = submitted_tx.response().await?.value; // ANCHOR_END: submit_response_multicontract assert_eq!(counter, 42); assert_eq!(array, [42; 2]); Ok(()) } #[tokio::test] #[allow(unused_variables)] async fn multi_call_cost_estimation() -> Result<()> { use fuels::prelude::*; abigen!(Contract( name = "MyContract", abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json" )); let wallet = launch_provider_and_get_wallet().await?; let contract_id = Contract::load_from( "../../e2e/sway/contracts/contract_test/out/release/contract_test.bin", LoadConfiguration::default(), )? .deploy(&wallet, TxPolicies::default()) .await? .contract_id; let contract_methods = MyContract::new(contract_id, wallet.clone()).methods(); // ANCHOR: multi_call_cost_estimation let call_handler_1 = contract_methods.initialize_counter(42); let call_handler_2 = contract_methods.get_array([42; 2]); let multi_call_handler = CallHandler::new_multi_call(wallet.clone()) .add_call(call_handler_1) .add_call(call_handler_2); let tolerance = Some(0.0); let block_horizon = Some(1); let transaction_cost = multi_call_handler .estimate_transaction_cost(tolerance, block_horizon) // Get estimated transaction cost .await?; // ANCHOR_END: multi_call_cost_estimation let expected_script_gas = 3832; let expected_total_gas = 10_661; assert_eq!(transaction_cost.script_gas, expected_script_gas); assert_eq!(transaction_cost.total_gas, expected_total_gas); Ok(()) } #[tokio::test] #[allow(unused_variables)] async fn connect_wallet() -> Result<()> { use fuels::prelude::*; abigen!(Contract( name = "MyContract", abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json" )); let config = WalletsConfig::new(Some(2), Some(1), Some(DEFAULT_COIN_AMOUNT)); let mut wallets = launch_custom_provider_and_get_wallets(config, None, None).await?; let wallet_1 = wallets.pop().unwrap(); let wallet_2 = wallets.pop().unwrap(); let contract_id = Contract::load_from( "../../e2e/sway/contracts/contract_test/out/release/contract_test.bin", LoadConfiguration::default(), )? .deploy(&wallet_1, TxPolicies::default()) .await? .contract_id; // ANCHOR: connect_wallet // Create contract instance with wallet_1 let contract_instance = MyContract::new(contract_id, wallet_1.clone()); // Perform contract call with wallet_2 let response = contract_instance .with_account(wallet_2) // Connect wallet_2 .methods() // Get contract methods .get_msg_amount() // Our contract method .call() // Perform the contract call. .await?; // This is an async call, `.await` for it. // ANCHOR_END: connect_wallet Ok(()) } #[tokio::test] async fn custom_assets_example() -> Result<()> { use fuels::prelude::*; setup_program_test!( Wallets("wallet", "wallet_2"), Abigen(Contract( name = "MyContract", project = "e2e/sway/contracts/contract_test" )), Deploy( name = "contract_instance", contract = "MyContract", wallet = "wallet" ) ); let some_addr: Address = thread_rng().r#gen(); // ANCHOR: add_custom_assets let amount = 1000; let _ = contract_instance .methods() .initialize_counter(42) .add_custom_asset(AssetId::zeroed(), amount, Some(some_addr)) .call() .await?; // ANCHOR_END: add_custom_assets let custom_inputs = vec![]; let custom_outputs = vec![]; // ANCHOR: add_custom_inputs_outputs let _ = contract_instance .methods() .initialize_counter(42) .with_inputs(custom_inputs) .with_outputs(custom_outputs) .add_signer(wallet_2.signer().clone()) .call() .await?; // ANCHOR_END: add_custom_inputs_outputs Ok(()) } #[tokio::test] async fn low_level_call_example() -> Result<()> { use fuels::{core::codec::calldata, prelude::*, types::SizedAsciiString}; setup_program_test!( Wallets("wallet"), Abigen( Contract( name = "MyCallerContract", project = "e2e/sway/contracts/low_level_caller" ), Contract( name = "MyTargetContract", project = "e2e/sway/contracts/contract_test" ), ), Deploy( name = "caller_contract_instance", contract = "MyCallerContract", wallet = "wallet" ), Deploy( name = "target_contract_instance", contract = "MyTargetContract", wallet = "wallet" ), ); // ANCHOR: low_level_call let function_selector = encode_fn_selector("set_value_multiple_complex"); let call_data = calldata!( MyStruct { a: true, b: [1, 2, 3], }, SizedAsciiString::<4>::try_from("fuel")? )?; caller_contract_instance .methods() .call_low_level_call( target_contract_instance.id(), Bytes(function_selector), Bytes(call_data), ) .determine_missing_contracts() .await? .call() .await?; // ANCHOR_END: low_level_call let result_uint = target_contract_instance .methods() .read_counter() .call() .await .unwrap() .value; let result_bool = target_contract_instance .methods() .get_bool_value() .call() .await .unwrap() .value; let result_str = target_contract_instance .methods() .get_str_value() .call() .await .unwrap() .value; assert_eq!(result_uint, 2); assert!(result_bool); assert_eq!(result_str, "fuel"); Ok(()) } #[tokio::test] async fn configure_the_return_value_decoder() -> Result<()> { use fuels::prelude::*; setup_program_test!( Wallets("wallet"), Abigen(Contract( name = "MyContract", project = "e2e/sway/contracts/contract_test" )), Deploy( name = "contract_instance", contract = "MyContract", wallet = "wallet" ) ); // ANCHOR: contract_decoder_config let _ = contract_instance .methods() .initialize_counter(42) .with_decoder_config(DecoderConfig { max_depth: 10, max_tokens: 2_000, }) .call() .await?; // ANCHOR_END: contract_decoder_config Ok(()) } #[tokio::test] async fn storage_slots_override() -> Result<()> { { // ANCHOR: storage_slots_override use fuels::{programs::contract::Contract, tx::StorageSlot}; let slot_override = StorageSlot::new([1; 32].into(), [2; 32].into()); let storage_config = StorageConfiguration::default().add_slot_overrides([slot_override]); let load_config = LoadConfiguration::default().with_storage_configuration(storage_config); let _: Result<_> = Contract::load_from("...", load_config); // ANCHOR_END: storage_slots_override } { // ANCHOR: storage_slots_disable_autoload use fuels::programs::contract::Contract; let storage_config = StorageConfiguration::default().with_autoload(false); let load_config = LoadConfiguration::default().with_storage_configuration(storage_config); let _: Result<_> = Contract::load_from("...", load_config); // ANCHOR_END: storage_slots_disable_autoload } Ok(()) } #[tokio::test] async fn contract_custom_call() -> Result<()> { use fuels::prelude::*; setup_program_test!( Wallets("wallet"), Abigen(Contract( name = "TestContract", project = "e2e/sway/contracts/contract_test" )), Deploy( name = "contract_instance", contract = "TestContract", wallet = "wallet" ), ); let provider = wallet.provider(); let counter = 42; // ANCHOR: contract_call_tb let call_handler = contract_instance.methods().initialize_counter(counter); let mut tb = call_handler.transaction_builder().await?; // customize the builder... wallet.adjust_for_fee(&mut tb, 0).await?; wallet.add_witnesses(&mut tb)?; let tx = tb.build(provider).await?; let tx_id = provider.send_transaction(tx).await?; tokio::time::sleep(Duration::from_millis(500)).await; let tx_status = provider.tx_status(&tx_id).await?; let response = call_handler.get_response(tx_status)?; assert_eq!(counter, response.value); // ANCHOR_END: contract_call_tb Ok(()) } #[tokio::test] async fn configure_encoder_config() -> Result<()> { use fuels::prelude::*; setup_program_test!( Wallets("wallet"), Abigen(Contract( name = "MyContract", project = "e2e/sway/contracts/contract_test" )), Deploy( name = "contract_instance", contract = "MyContract", wallet = "wallet" ) ); // ANCHOR: contract_encoder_config let _ = contract_instance .with_encoder_config(EncoderConfig { max_depth: 10, max_tokens: 2_000, }) .methods() .initialize_counter(42) .call() .await?; // ANCHOR_END: contract_encoder_config Ok(()) } #[tokio::test] async fn contract_call_impersonation() -> Result<()> { use fuels::prelude::*; abigen!(Contract( name = "MyContract", abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json" )); // ANCHOR: utxo_validation_off let node_config = NodeConfig { utxo_validation: false, ..Default::default() }; // ANCHOR_END: utxo_validation_off let signer = PrivateKeySigner::random(&mut thread_rng()); let coins = setup_single_asset_coins( signer.address(), AssetId::zeroed(), DEFAULT_NUM_COINS, DEFAULT_COIN_AMOUNT, ); // ANCHOR: utxo_validation_off_node_start let provider = setup_test_provider(coins, vec![], Some(node_config), None).await?; // ANCHOR_END: utxo_validation_off_node_start let wallet = Wallet::new(signer, provider.clone()); let contract_id = Contract::load_from( "../../e2e/sway/contracts/contract_test/out/release/contract_test.bin", LoadConfiguration::default(), )? .deploy(&wallet, TxPolicies::default()) .await? .contract_id; let some_address = wallet.address(); // ANCHOR: contract_call_impersonation // create impersonator for an address let fake_signer = FakeSigner::new(some_address); let impersonator = Wallet::new(fake_signer, provider.clone()); let contract_instance = MyContract::new(contract_id, impersonator.clone()); let response = contract_instance .methods() .initialize_counter(42) .call() .await?; assert_eq!(42, response.value); // ANCHOR_END: contract_call_impersonation Ok(()) } #[tokio::test] #[allow(unused_variables)] async fn deploying_via_loader() -> Result<()> { use fuels::prelude::*; setup_program_test!( Abigen(Contract( name = "MyContract", project = "e2e/sway/contracts/huge_contract" )), Wallets("main_wallet") ); let contract_binary = "../../e2e/sway/contracts/huge_contract/out/release/huge_contract.bin"; let provider: Provider = main_wallet.provider().clone(); let random_salt = || Salt::new(rand::thread_rng().r#gen()); // ANCHOR: show_contract_is_too_big let contract = Contract::load_from( contract_binary, LoadConfiguration::default().with_salt(random_salt()), )?; let max_allowed = provider .consensus_parameters() .await? .contract_params() .contract_max_size(); assert!(contract.code().len() as u64 > max_allowed); // ANCHOR_END: show_contract_is_too_big let wallet = main_wallet.clone(); // ANCHOR: manual_blob_upload_then_deploy let max_words_per_blob = 10_000; let blobs = Contract::load_from( contract_binary, LoadConfiguration::default().with_salt(random_salt()), )? .convert_to_loader(max_words_per_blob)? .blobs() .to_vec(); let mut all_blob_ids = vec![]; let mut already_uploaded_blobs = HashSet::new(); for blob in blobs { let blob_id = blob.id(); all_blob_ids.push(blob_id); // uploading the same blob twice is not allowed if already_uploaded_blobs.contains(&blob_id) { continue; } let mut tb = BlobTransactionBuilder::default().with_blob(blob); wallet.adjust_for_fee(&mut tb, 0).await?; wallet.add_witnesses(&mut tb)?; let tx = tb.build(&provider).await?; provider .send_transaction_and_await_commit(tx) .await? .check(None)?; already_uploaded_blobs.insert(blob_id); } let contract_id = Contract::loader_from_blob_ids(all_blob_ids, random_salt(), vec![])? .deploy(&wallet, TxPolicies::default()) .await? .contract_id; // ANCHOR_END: manual_blob_upload_then_deploy // ANCHOR: deploy_via_loader let max_words_per_blob = 10_000; let contract_id = Contract::load_from( contract_binary, LoadConfiguration::default().with_salt(random_salt()), )? .convert_to_loader(max_words_per_blob)? .deploy(&wallet, TxPolicies::default()) .await? .contract_id; // ANCHOR_END: deploy_via_loader // ANCHOR: auto_convert_to_loader let max_words_per_blob = 10_000; let contract_id = Contract::load_from( contract_binary, LoadConfiguration::default().with_salt(random_salt()), )? .smart_deploy(&wallet, TxPolicies::default(), max_words_per_blob) .await? .contract_id; // ANCHOR_END: auto_convert_to_loader // ANCHOR: upload_blobs_then_deploy let contract_id = Contract::load_from( contract_binary, LoadConfiguration::default().with_salt(random_salt()), )? .convert_to_loader(max_words_per_blob)? .upload_blobs(&wallet, TxPolicies::default()) .await? .deploy(&wallet, TxPolicies::default()) .await? .contract_id; // ANCHOR_END: upload_blobs_then_deploy let wallet = main_wallet.clone(); // ANCHOR: use_loader let contract_instance = MyContract::new(contract_id, wallet); let response = contract_instance.methods().something().call().await?.value; assert_eq!(response, 1001); // ANCHOR_END: use_loader // ANCHOR: show_max_tx_size provider .consensus_parameters() .await? .tx_params() .max_size(); // ANCHOR_END: show_max_tx_size // ANCHOR: show_max_tx_gas provider .consensus_parameters() .await? .tx_params() .max_gas_per_tx(); // ANCHOR_END: show_max_tx_gas let wallet = main_wallet; // ANCHOR: manual_blobs_then_deploy let chunk_size = 100_000; assert!( chunk_size % 8 == 0, "all chunks, except the last, must be word-aligned" ); let blobs = contract .code() .chunks(chunk_size) .map(|chunk| Blob::new(chunk.to_vec())) .collect(); let contract_id = Contract::loader_from_blobs(blobs, random_salt(), vec![])? .deploy(&wallet, TxPolicies::default()) .await? .contract_id; // ANCHOR_END: manual_blobs_then_deploy // ANCHOR: estimate_max_blob_size let max_blob_size = BlobTransactionBuilder::default() .estimate_max_blob_size(&provider) .await?; // ANCHOR_END: estimate_max_blob_size Ok(()) } #[tokio::test] #[allow(unused_variables)] async fn decoding_script_transactions() -> Result<()> { use fuels::prelude::*; setup_program_test!( Abigen(Contract( name = "MyContract", project = "e2e/sway/contracts/contract_test" )), Wallets("wallet"), Deploy( name = "contract_instance", contract = "MyContract", wallet = "wallet" ) ); let tx_id = contract_instance .methods() .initialize_counter(42) .call() .await? .tx_id .unwrap(); let provider: &Provider = wallet.provider(); // ANCHOR: decoding_script_transactions let TransactionType::Script(tx) = provider .get_transaction_by_id(&tx_id) .await? .unwrap() .transaction else { panic!("Transaction is not a script transaction"); }; let ScriptType::ContractCall(calls) = ScriptType::detect(tx.script(), tx.script_data())? else { panic!("Script is not a contract call"); }; let json_abi = std::fs::read_to_string( "../../e2e/sway/contracts/contract_test/out/release/contract_test-abi.json", )?; let abi_formatter = ABIFormatter::from_json_abi(json_abi)?; let call = &calls[0]; let fn_selector = call.decode_fn_selector()?; let decoded_args = abi_formatter.decode_fn_args(&fn_selector, call.encoded_args.as_slice())?; eprintln!( "The script called: {fn_selector}({})", decoded_args.join(", ") ); // ANCHOR_END: decoding_script_transactions Ok(()) } } ================================================ FILE: examples/cookbook/Cargo.toml ================================================ [package] name = "fuels-example-cookbook" version = { workspace = true } authors = { workspace = true } edition = { workspace = true } homepage = { workspace = true } license = { workspace = true } publish = false repository = { workspace = true } description = "Fuel Rust SDK cookbook examples." [dependencies] fuels = { workspace = true, features = ["default"] } rand = { workspace = true } tokio = { workspace = true, features = ["full"] } [features] rocksdb = ["fuels/rocksdb"] fuel-core-lib = ["fuels/fuel-core-lib"] ================================================ FILE: examples/cookbook/src/lib.rs ================================================ #[cfg(test)] mod tests { use std::{str::FromStr, time::Duration}; use fuels::{ accounts::{ ViewOnlyAccount, predicate::Predicate, signers::private_key::PrivateKeySigner, wallet::Wallet, }, prelude::Result, test_helpers::{setup_single_asset_coins, setup_test_provider}, tx::ContractIdExt, types::{ Address, AssetId, SubAssetId, transaction::TxPolicies, transaction_builders::{ BuildableTransaction, ScriptTransactionBuilder, TransactionBuilder, }, tx_status::TxStatus, }, }; use rand::thread_rng; #[tokio::test] async fn liquidity() -> Result<()> { use fuels::{ prelude::*, test_helpers::{AssetConfig, WalletsConfig}, }; // ANCHOR: liquidity_abigen abigen!(Contract( name = "MyContract", abi = "e2e/sway/contracts/liquidity_pool/out/release/liquidity_pool-abi.json" )); // ANCHOR_END: liquidity_abigen // ANCHOR: liquidity_wallet let base_asset_id: AssetId = "0x9ae5b658754e096e4d681c548daf46354495a437cc61492599e33fc64dcdc30c".parse()?; let asset_ids = [AssetId::zeroed(), base_asset_id]; let asset_configs = asset_ids .map(|id| AssetConfig { id, num_coins: 1, coin_amount: 1_000_000, }) .into(); let wallet_config = WalletsConfig::new_multiple_assets(1, asset_configs); let wallets = launch_custom_provider_and_get_wallets(wallet_config, None, None).await?; let wallet = &wallets[0]; // ANCHOR_END: liquidity_wallet // ANCHOR: liquidity_deploy let contract_id = Contract::load_from( "../../e2e/sway/contracts/liquidity_pool/out/release/liquidity_pool.bin", LoadConfiguration::default(), )? .deploy(wallet, TxPolicies::default()) .await? .contract_id; let contract_methods = MyContract::new(contract_id, wallet.clone()).methods(); // ANCHOR_END: liquidity_deploy // ANCHOR: liquidity_deposit let deposit_amount = 1_000_000; let call_params = CallParameters::default() .with_amount(deposit_amount) .with_asset_id(base_asset_id); contract_methods .deposit(wallet.address().into()) .call_params(call_params)? .with_variable_output_policy(VariableOutputPolicy::Exactly(1)) .call() .await?; // ANCHOR_END: liquidity_deposit // ANCHOR: liquidity_withdraw let lp_asset_id = contract_id.asset_id(&SubAssetId::zeroed()); let lp_token_balance = wallet.get_asset_balance(&lp_asset_id).await?; let call_params = CallParameters::default() .with_amount(lp_token_balance.try_into().unwrap()) .with_asset_id(lp_asset_id); contract_methods .withdraw(wallet.address().into()) .call_params(call_params)? .with_variable_output_policy(VariableOutputPolicy::Exactly(1)) .call() .await?; let base_balance = wallet.get_asset_balance(&base_asset_id).await?; assert_eq!(base_balance, deposit_amount as u128); // ANCHOR_END: liquidity_withdraw Ok(()) } #[tokio::test] async fn custom_chain() -> Result<()> { // ANCHOR: custom_chain_import use fuels::{ prelude::*, tx::{ConsensusParameters, FeeParameters, TxParameters}, }; // ANCHOR_END: custom_chain_import // ANCHOR: custom_chain_consensus let tx_params = TxParameters::default() .with_max_gas_per_tx(1_000) .with_max_inputs(2); let fee_params = FeeParameters::default().with_gas_price_factor(10); let mut consensus_parameters = ConsensusParameters::default(); consensus_parameters.set_tx_params(tx_params); consensus_parameters.set_fee_params(fee_params); let chain_config = ChainConfig { consensus_parameters, ..ChainConfig::default() }; // ANCHOR_END: custom_chain_consensus // ANCHOR: custom_chain_coins let signer = PrivateKeySigner::random(&mut thread_rng()); let coins = setup_single_asset_coins( signer.address(), Default::default(), DEFAULT_NUM_COINS, DEFAULT_COIN_AMOUNT, ); // ANCHOR_END: custom_chain_coins // ANCHOR: custom_chain_provider let node_config = NodeConfig::default(); let _provider = setup_test_provider(coins, vec![], Some(node_config), Some(chain_config)).await?; // ANCHOR_END: custom_chain_provider Ok(()) } #[tokio::test] async fn transfer_multiple() -> Result<()> { use std::str::FromStr; use fuels::prelude::*; // ANCHOR: transfer_multiple_setup let wallet_1_signer = PrivateKeySigner::random(&mut thread_rng()); const NUM_ASSETS: u64 = 5; const AMOUNT: u64 = 100_000; const NUM_COINS: u64 = 1; let (coins, _) = setup_multiple_assets_coins(wallet_1_signer.address(), NUM_ASSETS, NUM_COINS, AMOUNT); let provider = setup_test_provider(coins, vec![], None, None).await?; let wallet_1 = Wallet::new(wallet_1_signer, provider.clone()); let wallet_2 = Wallet::random(&mut thread_rng(), provider.clone()); // ANCHOR_END: transfer_multiple_setup // ANCHOR: transfer_multiple_input let balances = wallet_1.get_balances().await?; let consensus_parameters = provider.consensus_parameters().await?; let mut inputs = vec![]; let mut outputs = vec![]; for (id_string, amount) in balances { let id = AssetId::from_str(&id_string)?; let input = wallet_1 .get_asset_inputs_for_amount(id, amount, None) .await?; inputs.extend(input); // we don't transfer the full base asset so we can cover fees let output = if id == *consensus_parameters.base_asset_id() { wallet_1.get_asset_outputs_for_amount(wallet_2.address(), id, (amount / 2) as u64) } else { wallet_1.get_asset_outputs_for_amount(wallet_2.address(), id, amount as u64) }; outputs.extend(output); } // ANCHOR_END: transfer_multiple_input // ANCHOR: transfer_multiple_transaction let mut tb = ScriptTransactionBuilder::prepare_transfer(inputs, outputs, TxPolicies::default()); wallet_1.add_witnesses(&mut tb)?; let tx = tb.build(&provider).await?; provider.send_transaction_and_await_commit(tx).await?; let balances = wallet_2.get_balances().await?; assert_eq!(balances.len(), NUM_ASSETS as usize); for (id, balance) in balances { if id == *consensus_parameters.base_asset_id().to_string() { assert_eq!(balance, (AMOUNT / 2) as u128); } else { assert_eq!(balance, AMOUNT as u128); } } // ANCHOR_END: transfer_multiple_transaction Ok(()) } #[tokio::test] #[cfg(any(not(feature = "fuel-core-lib"), feature = "rocksdb"))] async fn create_or_use_rocksdb() -> Result<()> { use std::path::PathBuf; use fuels::prelude::*; // ANCHOR: create_or_use_rocksdb let provider_config = NodeConfig { database_type: DbType::RocksDb(Some(PathBuf::from("/tmp/.spider/db"))), ..NodeConfig::default() }; // ANCHOR_END: create_or_use_rocksdb launch_custom_provider_and_get_wallets(Default::default(), Some(provider_config), None) .await?; Ok(()) } #[tokio::test] async fn custom_transaction() -> Result<()> { let hot_wallet_signer = PrivateKeySigner::random(&mut thread_rng()); let code_path = "../../e2e/sway/predicates/swap/out/release/swap.bin"; let mut predicate = Predicate::load_from(code_path)?; let num_coins = 5; let amount = 1000; let bridged_asset_id = AssetId::from([1u8; 32]); let base_coins = setup_single_asset_coins( hot_wallet_signer.address(), AssetId::zeroed(), num_coins, amount, ); let other_coins = setup_single_asset_coins(predicate.address(), bridged_asset_id, num_coins, amount); let provider = setup_test_provider( base_coins.into_iter().chain(other_coins).collect(), vec![], None, None, ) .await?; provider.produce_blocks(100, None).await?; let hot_wallet = Wallet::new(hot_wallet_signer, provider.clone()); let cold_wallet = Wallet::random(&mut thread_rng(), provider.clone()); predicate.set_provider(provider.clone()); // ANCHOR: custom_tx_receiver let ask_amount = 100; let locked_amount = 500; let bridged_asset_id = AssetId::from([1u8; 32]); let receiver = Address::from_str("09c0b2d1a486c439a87bcba6b46a7a1a23f3897cc83a94521a96da5c23bc58db")?; // ANCHOR_END: custom_tx_receiver // ANCHOR: custom_tx let tb = ScriptTransactionBuilder::default(); // ANCHOR_END: custom_tx // ANCHOR: custom_tx_io_base let consensus_parameters = provider.consensus_parameters().await?; let base_inputs = hot_wallet .get_asset_inputs_for_amount(*consensus_parameters.base_asset_id(), ask_amount, None) .await?; let base_outputs = hot_wallet.get_asset_outputs_for_amount( receiver, *consensus_parameters.base_asset_id(), ask_amount as u64, ); // ANCHOR_END: custom_tx_io_base // ANCHOR: custom_tx_io_other let other_asset_inputs = predicate .get_asset_inputs_for_amount(bridged_asset_id, locked_amount, None) .await?; let other_asset_outputs = predicate.get_asset_outputs_for_amount(cold_wallet.address(), bridged_asset_id, 500); // ANCHOR_END: custom_tx_io_other // ANCHOR: custom_tx_io let inputs = base_inputs .into_iter() .chain(other_asset_inputs.into_iter()) .collect(); let outputs = base_outputs .into_iter() .chain(other_asset_outputs.into_iter()) .collect(); let mut tb = tb.with_inputs(inputs).with_outputs(outputs); // ANCHOR_END: custom_tx_io // ANCHOR: custom_tx_add_signer tb.add_signer(hot_wallet.signer().clone())?; // ANCHOR_END: custom_tx_add_signer // ANCHOR: custom_tx_adjust hot_wallet.adjust_for_fee(&mut tb, 100).await?; // ANCHOR_END: custom_tx_adjust // ANCHOR: custom_tx_policies let tx_policies = TxPolicies::default().with_maturity(64).with_expiration(128); let tb = tb.with_tx_policies(tx_policies); // ANCHOR_END: custom_tx_policies // ANCHOR: custom_tx_build let tx = tb.build(&provider).await?; let tx_id = provider.send_transaction(tx).await?; // ANCHOR_END: custom_tx_build tokio::time::sleep(Duration::from_millis(500)).await; // ANCHOR: custom_tx_verify let status = provider.tx_status(&tx_id).await?; assert!(matches!(status, TxStatus::Success { .. })); let balance: u128 = cold_wallet.get_asset_balance(&bridged_asset_id).await?; assert_eq!(balance, locked_amount); // ANCHOR_END: custom_tx_verify Ok(()) } } ================================================ FILE: examples/debugging/Cargo.toml ================================================ [package] name = "fuels-example-debugging" version = { workspace = true } authors = { workspace = true } edition = { workspace = true } homepage = { workspace = true } license = { workspace = true } publish = false repository = { workspace = true } description = "Fuel Rust SDK debugging examples." [dev-dependencies] fuel-abi-types = { workspace = true } fuels = { workspace = true, features = ["default"] } rand = { workspace = true } serde_json = { workspace = true } tokio = { workspace = true, features = ["full"] } ================================================ FILE: examples/debugging/src/lib.rs ================================================ #[cfg(test)] mod tests { use std::collections::HashMap; use fuel_abi_types::abi::unified_program::UnifiedProgramABI; use fuels::{ core::codec::ABIDecoder, macros::abigen, types::{SizedAsciiString, errors::Result, param_types::ParamType}, }; #[test] fn encode_fn_selector() { use fuels::core::codec::encode_fn_selector; // ANCHOR: example_fn_selector // fn some_fn_name(arg1: Vec, arg2: u8) let fn_name = "some_fn_name"; let selector = encode_fn_selector(fn_name); assert_eq!( selector, [ 0, 0, 0, 0, 0, 0, 0, 12, 115, 111, 109, 101, 95, 102, 110, 95, 110, 97, 109, 101 ] ); // ANCHOR_END: example_fn_selector } #[test] fn decoded_debug_matches_rust_debug() -> Result<()> { abigen!(Contract( name = "MyContract", abi = "e2e/sway/types/contracts/generics/out/release/generics-abi.json" )); let json_abi_file = "../../e2e/sway/types/contracts/generics/out/release/generics-abi.json"; let abi_file_contents = std::fs::read_to_string(json_abi_file)?; let parsed_abi = UnifiedProgramABI::from_json_abi(&abi_file_contents)?; let type_lookup = parsed_abi .types .into_iter() .map(|decl| (decl.type_id, decl)) .collect::>(); let get_first_fn_argument = |fn_name: &str| { parsed_abi .functions .iter() .find(|abi_fun| abi_fun.name == fn_name) .expect("should be there") .inputs .first() .expect("should be there") }; let decoder = ABIDecoder::default(); { // simple struct with a single generic parameter let type_application = get_first_fn_argument("struct_w_generic"); let param_type = ParamType::try_from_type_application(type_application, &type_lookup)?; let expected_struct = SimpleGeneric { single_generic_param: 123u64, }; assert_eq!( format!("{expected_struct:?}"), decoder.decode_as_debug_str(¶m_type, [0, 0, 0, 0, 0, 0, 0, 123].as_slice())? ); } { // struct that delegates the generic param internally let type_application = get_first_fn_argument("struct_delegating_generic"); let param_type = ParamType::try_from_type_application(type_application, &type_lookup)?; let expected_struct = PassTheGenericOn { one: SimpleGeneric { single_generic_param: SizedAsciiString::<3>::try_from("abc")?, }, }; assert_eq!( format!("{expected_struct:?}"), decoder.decode_as_debug_str(¶m_type, [97, 98, 99].as_slice())? ); } { // enum with generic in variant let type_application = get_first_fn_argument("enum_w_generic"); let param_type = ParamType::try_from_type_application(type_application, &type_lookup)?; let expected_enum = EnumWGeneric::B(10u64); assert_eq!( format!("{expected_enum:?}"), decoder.decode_as_debug_str( ¶m_type, [0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 10].as_slice() )? ); } { // logged type let logged_type = parsed_abi .logged_types .as_ref() .expect("has logs") .first() .expect("has log"); let param_type = ParamType::try_from_type_application(&logged_type.application, &type_lookup)?; let expected_u8 = 1; assert_eq!( format!("{expected_u8}"), decoder.decode_as_debug_str(¶m_type, [1].as_slice())? ); } Ok(()) } } ================================================ FILE: examples/macros/Cargo.toml ================================================ [package] name = "fuels-example-macros" version = { workspace = true } authors = { workspace = true } edition = { workspace = true } homepage = { workspace = true } license = { workspace = true } publish = false repository = { workspace = true } description = "Fuel Rust SDK macro examples." [dev-dependencies] fuels = { workspace = true, features = ["default"] } tokio = { workspace = true, features = ["full"] } ================================================ FILE: examples/macros/src/lib.rs ================================================ extern crate alloc; #[cfg(test)] mod tests { use fuels::prelude::*; #[test] fn example_of_abigen_usage() { // ANCHOR: multiple_abigen_program_types abigen!( Contract( name = "ContractA", abi = "e2e/sway/bindings/sharing_types/contract_a/out/release/contract_a-abi.json" ), Contract( name = "ContractB", abi = "e2e/sway/bindings/sharing_types/contract_b/out/release/contract_b-abi.json" ), Script( name = "MyScript", abi = "e2e/sway/scripts/arguments/out/release/arguments-abi.json" ), Predicate( name = "MyPredicateEncoder", abi = "e2e/sway/predicates/basic_predicate/out/release/basic_predicate-abi.json" ), ); // ANCHOR_END: multiple_abigen_program_types } #[test] fn macro_deriving() { // ANCHOR: deriving_traits use fuels::macros::{Parameterize, Tokenizable}; #[derive(Parameterize, Tokenizable)] #[allow(dead_code)] struct MyStruct { field_a: u8, } #[derive(Parameterize, Tokenizable)] #[allow(dead_code)] enum SomeEnum { A(MyStruct), B(Vec), } // ANCHOR_END: deriving_traits } #[test] fn macro_deriving_extra() { { use fuels::{ core as fuels_core_elsewhere, macros::{Parameterize, Tokenizable}, types as fuels_types_elsewhere, }; // ANCHOR: deriving_traits_paths #[derive(Parameterize, Tokenizable)] #[FuelsCorePath = "fuels_core_elsewhere"] #[FuelsTypesPath = "fuels_types_elsewhere"] #[allow(dead_code)] pub struct SomeStruct { field_a: u64, } // ANCHOR_END: deriving_traits_paths } { // ANCHOR: deriving_traits_nostd use fuels::macros::{Parameterize, Tokenizable}; #[derive(Parameterize, Tokenizable)] #[NoStd] #[allow(dead_code)] pub struct SomeStruct { field_a: u64, } // ANCHOR_END: deriving_traits_nostd } } } ================================================ FILE: examples/predicates/Cargo.toml ================================================ [package] name = "fuels-example-predicates" version = { workspace = true } authors = { workspace = true } edition = { workspace = true } homepage = { workspace = true } license = { workspace = true } publish = false repository = { workspace = true } description = "Fuel Rust SDK predicate examples." [dev-dependencies] fuels = { workspace = true, features = ["default"] } rand = { workspace = true } tokio = { workspace = true, features = ["full"] } ================================================ FILE: examples/predicates/src/lib.rs ================================================ #[cfg(test)] mod tests { use fuels::{ accounts::{Account, predicate::Predicate, signers::private_key::PrivateKeySigner}, crypto::Message, prelude::*, types::B512, }; use rand::thread_rng; #[tokio::test] async fn predicate_example() -> Result<()> { // ANCHOR: predicate_signers let wallet_signer = PrivateKeySigner::new( "0x862512a2363db2b3a375c0d4bbbd27172180d89f23f2e259bac850ab02619301".parse()?, ); let wallet2_signer = PrivateKeySigner::new( "0x37fa81c84ccd547c30c176b118d5cb892bdb113e8e80141f266519422ef9eefd".parse()?, ); let wallet3_signer = PrivateKeySigner::new( "0x976e5c3fa620092c718d852ca703b6da9e3075b9f2ecb8ed42d9f746bf26aafb".parse()?, ); let receiver_signer = PrivateKeySigner::random(&mut thread_rng()); // ANCHOR_END: predicate_signers // ANCHOR: predicate_coins let asset_id = AssetId::zeroed(); let num_coins = 32; let amount = 64; let initial_balance = amount * num_coins; let all_coins = [ &wallet_signer, &wallet2_signer, &wallet3_signer, &receiver_signer, ] .iter() .flat_map(|signer| setup_single_asset_coins(signer.address(), asset_id, num_coins, amount)) .collect::>(); let provider = setup_test_provider(all_coins, vec![], None, None).await?; let wallet = Wallet::new(wallet_signer, provider.clone()); let wallet2 = Wallet::new(wallet2_signer, provider.clone()); let wallet3 = Wallet::new(wallet3_signer, provider.clone()); let receiver = Wallet::new(receiver_signer, provider.clone()); // ANCHOR_END: predicate_coins let data_to_sign = Message::new([0; 32]); let signature1: B512 = wallet .signer() .sign(data_to_sign) .await? .as_ref() .try_into()?; let signature2: B512 = wallet2 .signer() .sign(data_to_sign) .await? .as_ref() .try_into()?; let signature3: B512 = wallet3 .signer() .sign(data_to_sign) .await? .as_ref() .try_into()?; let signatures = [signature1, signature2, signature3]; // ANCHOR: predicate_load abigen!(Predicate( name = "MyPredicate", abi = "e2e/sway/predicates/signatures/out/release/signatures-abi.json" )); let predicate_data = MyPredicateEncoder::default().encode_data(signatures)?; let code_path = "../../e2e/sway/predicates/signatures/out/release/signatures.bin"; let predicate: Predicate = Predicate::load_from(code_path)? .with_provider(provider) .with_data(predicate_data); // ANCHOR_END: predicate_load // ANCHOR: predicate_receive let amount_to_predicate = 500; wallet .transfer( predicate.address(), amount_to_predicate, asset_id, TxPolicies::default(), ) .await?; let predicate_balance = predicate.get_asset_balance(&asset_id).await?; assert_eq!(predicate_balance, amount_to_predicate as u128); // ANCHOR_END: predicate_receive // ANCHOR: predicate_spend let amount_to_receiver = 300; predicate .transfer( receiver.address(), amount_to_receiver, asset_id, TxPolicies::default(), ) .await?; let receiver_balance_after = receiver.get_asset_balance(&asset_id).await?; assert_eq!( (initial_balance + amount_to_receiver) as u128, receiver_balance_after ); // ANCHOR_END: predicate_spend Ok(()) } #[tokio::test] async fn predicate_data_example() -> Result<()> { // ANCHOR: predicate_data_setup let asset_id = AssetId::zeroed(); let wallets_config = WalletsConfig::new_multiple_assets( 2, vec![AssetConfig { id: asset_id, num_coins: 1, coin_amount: 1_000, }], ); let wallets = &launch_custom_provider_and_get_wallets(wallets_config, None, None).await?; let first_wallet = &wallets[0]; let second_wallet = &wallets[1]; abigen!(Predicate( name = "MyPredicate", abi = "e2e/sway/predicates/basic_predicate/out/release/basic_predicate-abi.json" )); // ANCHOR_END: predicate_data_setup // ANCHOR: with_predicate_data let predicate_data = MyPredicateEncoder::default().encode_data(4096, 4096)?; let code_path = "../../e2e/sway/predicates/basic_predicate/out/release/basic_predicate.bin"; let predicate: Predicate = Predicate::load_from(code_path)? .with_provider(first_wallet.provider().clone()) .with_data(predicate_data); // ANCHOR_END: with_predicate_data // ANCHOR: predicate_data_lock_amount // First wallet transfers amount to predicate. first_wallet .transfer(predicate.address(), 500, asset_id, TxPolicies::default()) .await?; // Check predicate balance. let balance = predicate.get_asset_balance(&AssetId::zeroed()).await?; assert_eq!(balance, 500); // ANCHOR_END: predicate_data_lock_amount // ANCHOR: predicate_data_unlock let amount_to_unlock = 300; predicate .transfer( second_wallet.address(), amount_to_unlock, asset_id, TxPolicies::default(), ) .await?; // Second wallet balance is updated. let balance = second_wallet.get_asset_balance(&AssetId::zeroed()).await?; assert_eq!(balance, 1300); // ANCHOR_END: predicate_data_unlock Ok(()) } } ================================================ FILE: examples/providers/Cargo.toml ================================================ [package] name = "fuels-example-providers" version = { workspace = true } authors = { workspace = true } edition = { workspace = true } homepage = { workspace = true } license = { workspace = true } publish = false repository = { workspace = true } description = "Fuel Rust SDK provider examples." [dev-dependencies] fuels = { workspace = true, features = ["default"] } rand = { workspace = true } tokio = { workspace = true, features = ["full"] } ================================================ FILE: examples/providers/src/lib.rs ================================================ #[cfg(test)] mod tests { use std::time::Duration; use fuels::{accounts::signers::private_key::PrivateKeySigner, prelude::Result}; #[ignore = "testnet currently not compatible with the sdk"] #[tokio::test] async fn connect_to_fuel_node() -> Result<()> { // ANCHOR: connect_to_testnet use std::str::FromStr; use fuels::{crypto::SecretKey, prelude::*}; // Create a provider pointing to the testnet. let provider = Provider::connect("testnet.fuel.network").await.unwrap(); // Setup a private key let secret = SecretKey::from_str( "a1447cd75accc6b71a976fd3401a1f6ce318d27ba660b0315ee6ac347bf39568", )?; // Create the wallet let wallet = Wallet::new(PrivateKeySigner::new(secret), provider); // Get the wallet address. Used later with the faucet dbg!(wallet.address().to_string()); // ANCHOR_END: connect_to_testnet let provider = setup_test_provider(vec![], vec![], None, None).await?; let port = provider.url().split(':').next_back().unwrap(); // ANCHOR: local_node_address let _provider = Provider::connect(format!("127.0.0.1:{port}")).await?; // ANCHOR_END: local_node_address Ok(()) } #[tokio::test] async fn query_the_blockchain() -> Result<()> { // ANCHOR: setup_test_blockchain use fuels::prelude::*; // Set up our test blockchain. // Create a random signer // ANCHOR: setup_single_asset let wallet_signer = PrivateKeySigner::random(&mut rand::thread_rng()); // How many coins in our wallet. let number_of_coins = 1; // The amount/value in each coin in our wallet. let amount_per_coin = 3; let coins = setup_single_asset_coins( wallet_signer.address(), AssetId::zeroed(), number_of_coins, amount_per_coin, ); // ANCHOR_END: setup_single_asset // ANCHOR: configure_retry let retry_config = RetryConfig::new(3, Backoff::Fixed(Duration::from_secs(2)))?; let provider = setup_test_provider(coins.clone(), vec![], None, None) .await? .with_retry_config(retry_config); // ANCHOR_END: configure_retry // ANCHOR_END: setup_test_blockchain // ANCHOR: get_coins let consensus_parameters = provider.consensus_parameters().await?; let coins = provider .get_coins( &wallet_signer.address(), *consensus_parameters.base_asset_id(), ) .await?; assert_eq!(coins.len(), 1); // ANCHOR_END: get_coins // ANCHOR: get_spendable_resources let filter = ResourceFilter { from: wallet_signer.address(), amount: 1, ..Default::default() }; let spendable_resources = provider.get_spendable_resources(filter).await?; assert_eq!(spendable_resources.len(), 1); // ANCHOR_END: get_spendable_resources // ANCHOR: get_balances let _balances = provider.get_balances(&wallet_signer.address()).await?; // ANCHOR_END: get_balances Ok(()) } } ================================================ FILE: examples/rust_bindings/Cargo.toml ================================================ [package] name = "fuels-example-rust-bindings" version = { workspace = true } authors = { workspace = true } edition = { workspace = true } homepage = { workspace = true } license = { workspace = true } publish = false repository = { workspace = true } description = "Fuel Rust SDK examples for Rust-native bindings" [dev-dependencies] fuels = { workspace = true, features = ["default"] } fuels-code-gen = { workspace = true } fuels-macros = { workspace = true } proc-macro2 = { workspace = true } rand = { workspace = true } tokio = { workspace = true, features = ["full"] } ================================================ FILE: examples/rust_bindings/src/abi.json ================================================ { "programType": "contract", "specVersion": "1", "encodingVersion": "1", "concreteTypes": [ { "concreteTypeId": "1506e6f44c1d6291cdf46395a8e573276a4fa79e8ace3fc891e092ef32d1b0a0", "type": "u64" } ], "functions": [ { "inputs": [ { "name": "value", "concreteTypeId": "1506e6f44c1d6291cdf46395a8e573276a4fa79e8ace3fc891e092ef32d1b0a0" } ], "name": "initialize_counter", "output": "1506e6f44c1d6291cdf46395a8e573276a4fa79e8ace3fc891e092ef32d1b0a0" }, { "inputs": [ { "name": "value", "concreteTypeId": "1506e6f44c1d6291cdf46395a8e573276a4fa79e8ace3fc891e092ef32d1b0a0" } ], "name": "increment_counter", "output": "1506e6f44c1d6291cdf46395a8e573276a4fa79e8ace3fc891e092ef32d1b0a0" } ], "metadataTypes": [] } ================================================ FILE: examples/rust_bindings/src/lib.rs ================================================ #[cfg(test)] mod tests { use fuels::prelude::Result; #[tokio::test] #[allow(unused_variables)] async fn transform_json_to_bindings() -> Result<()> { use fuels::test_helpers::launch_provider_and_get_wallet; let wallet = launch_provider_and_get_wallet().await?; { // ANCHOR: use_abigen use fuels::prelude::*; // Replace with your own JSON abi path (relative to the root of your crate) abigen!(Contract( name = "MyContractName", abi = "examples/rust_bindings/src/abi.json" )); // ANCHOR_END: use_abigen } { // ANCHOR: abigen_with_string use fuels::prelude::*; abigen!(Contract( name = "MyContract", abi = r#" { "programType": "contract", "specVersion": "1", "encodingVersion": "1", "concreteTypes": [ { "concreteTypeId": "1506e6f44c1d6291cdf46395a8e573276a4fa79e8ace3fc891e092ef32d1b0a0", "type": "u64" } ], "functions": [ { "inputs": [ { "name": "value", "concreteTypeId": "1506e6f44c1d6291cdf46395a8e573276a4fa79e8ace3fc891e092ef32d1b0a0" } ], "name": "initialize_counter", "output": "1506e6f44c1d6291cdf46395a8e573276a4fa79e8ace3fc891e092ef32d1b0a0" }, { "inputs": [ { "name": "value", "concreteTypeId": "1506e6f44c1d6291cdf46395a8e573276a4fa79e8ace3fc891e092ef32d1b0a0" } ], "name": "increment_counter", "output": "1506e6f44c1d6291cdf46395a8e573276a4fa79e8ace3fc891e092ef32d1b0a0" } ], "metadataTypes": [] } "# )); // ANCHOR_END: abigen_with_string } Ok(()) } } ================================================ FILE: examples/rust_bindings/src/rust_bindings_formatted.rs ================================================ pub mod abigen_bindings { pub mod my_contract_mod { #[derive(Debug, Clone)] pub struct MyContract { contract_id: ::fuels::types::ContractId, account: A, log_decoder: ::fuels::core::codec::LogDecoder, encoder_config: ::fuels::core::codec::EncoderConfig, } impl MyContract { pub fn new(contract_id: ::fuels::types::ContractId, account: A) -> Self { let log_decoder = ::fuels::core::codec::LogDecoder::new( ::fuels::core::codec::log_formatters_lookup(vec![], contract_id.clone().into()), ); let encoder_config = ::fuels::core::codec::EncoderConfig::default(); Self { contract_id, account, log_decoder, encoder_config, } } pub fn contract_id(&self) -> &::fuels::types::ContractId { self.contract_id } pub fn account(&self) -> A { self.account.clone() } pub fn with_account(self, account: U) -> MyContract { MyContract { contract_id: self.contract_id, account, log_decoder: self.log_decoder, encoder_config: self.encoder_config, } } pub fn with_encoder_config( mut self, encoder_config: ::fuels::core::codec::EncoderConfig, ) -> MyContract { self.encoder_config = encoder_config; self } pub async fn get_balances( &self, ) -> ::fuels::types::errors::Result< ::std::collections::HashMap<::fuels::types::AssetId, u64>, > { ::fuels::accounts::ViewOnlyAccount::try_provider(&self.account)? .get_contract_balances(&self.contract_id) .await .map_err(::std::convert::Into::into) } pub fn methods(&self) -> MyContractMethods { MyContractMethods { contract_id: self.contract_id.clone(), account: self.account.clone(), log_decoder: self.log_decoder.clone(), encoder_config: self.encoder_config.clone(), } } } pub struct MyContractMethods { contract_id: ::fuels::types::ContractId, account: A, log_decoder: ::fuels::core::codec::LogDecoder, encoder_config: ::fuels::core::codec::EncoderConfig, } impl MyContractMethods { #[doc = " This method will read the counter from storage, increment it"] #[doc = " and write the incremented value to storage"] pub fn increment_counter( &self, value: ::core::primitive::u64, ) -> ::fuels::programs::calls::CallHandler< A, ::fuels::programs::calls::ContractCall, ::core::primitive::u64, > { ::fuels::programs::calls::CallHandler::new_contract_call( self.contract_id.clone(), self.account.clone(), ::fuels::core::codec::encode_fn_selector("increment_counter"), &[::fuels::core::traits::Tokenizable::into_token(value)], self.log_decoder.clone(), false, self.encoder_config.clone(), ) } pub fn initialize_counter( &self, value: ::core::primitive::u64, ) -> ::fuels::programs::calls::CallHandler< A, ::fuels::programs::calls::ContractCall, ::core::primitive::u64, > { ::fuels::programs::calls::CallHandler::new_contract_call( self.contract_id.clone(), self.account.clone(), ::fuels::core::codec::encode_fn_selector("initialize_counter"), &[::fuels::core::traits::Tokenizable::into_token(value)], self.log_decoder.clone(), false, self.encoder_config.clone(), ) } } impl ::fuels::programs::calls::ContractDependency for MyContract { fn id(&self) -> ::fuels::types::ContractId { self.contract_id.clone() } fn log_decoder(&self) -> ::fuels::core::codec::LogDecoder { self.log_decoder.clone() } } #[derive(Clone, Debug, Default)] pub struct MyContractConfigurables { offsets_with_data: ::std::vec::Vec<(u64, ::std::vec::Vec)>, encoder: ::fuels::core::codec::ABIEncoder, } impl MyContractConfigurables { pub fn new(encoder_config: ::fuels::core::codec::EncoderConfig) -> Self { Self { encoder: ::fuels::core::codec::ABIEncoder::new(encoder_config), ..::std::default::Default::default() } } } impl From for ::fuels::core::Configurables { fn from(config: MyContractConfigurables) -> Self { ::fuels::core::Configurables::new(config.offsets_with_data) } } } } pub use abigen_bindings::my_contract_mod::MyContract; pub use abigen_bindings::my_contract_mod::MyContractConfigurables; pub use abigen_bindings::my_contract_mod::MyContractMethods; ================================================ FILE: examples/types/Cargo.toml ================================================ [package] name = "fuels-example-types" version = { workspace = true } authors = { workspace = true } edition = { workspace = true } homepage = { workspace = true } license = { workspace = true } publish = false repository = { workspace = true } description = "Fuel Rust SDK types examples." [dev-dependencies] fuels = { workspace = true, features = ["default"] } rand = { workspace = true } tokio = { workspace = true, features = ["full"] } ================================================ FILE: examples/types/src/lib.rs ================================================ #[cfg(test)] mod tests { use fuels::{ prelude::Result, types::{Bits256, EvmAddress, Identity}, }; #[tokio::test] async fn bytes32() -> Result<()> { // ANCHOR: bytes32 use std::str::FromStr; use fuels::types::Bytes32; // Zeroed Bytes32 let b256 = Bytes32::zeroed(); // Grab the inner `[u8; 32]` from // `Bytes32` by dereferencing (i.e. `*`) it. assert_eq!([0u8; 32], *b256); // From a `[u8; 32]`. // ANCHOR: array_to_bytes32 let my_slice = [1u8; 32]; let b256 = Bytes32::new(my_slice); // ANCHOR_END: array_to_bytes32 assert_eq!([1u8; 32], *b256); // From a hex string. // ANCHOR: hex_string_to_bytes32 let hex_str = "0x0000000000000000000000000000000000000000000000000000000000000000"; let b256 = Bytes32::from_str(hex_str)?; // ANCHOR_END: hex_string_to_bytes32 assert_eq!([0u8; 32], *b256); // ANCHOR_END: bytes32 // ANCHOR: bytes32_format let b256_string = b256.to_string(); let b256_hex_string = format!("{b256:#x}"); // ANCHOR_END: bytes32_format assert_eq!(hex_str[2..], b256_string); assert_eq!(hex_str, b256_hex_string); // ANCHOR: bytes32_to_str let _str_from_bytes32: &str = b256.to_string().as_str(); // ANCHOR_END: bytes32_to_str Ok(()) } #[tokio::test] async fn address() -> Result<()> { // ANCHOR: address use std::str::FromStr; use fuels::types::Address; // Zeroed Bytes32 let address = Address::zeroed(); // Grab the inner `[u8; 32]` from // `Bytes32` by dereferencing (i.e. `*`) it. assert_eq!([0u8; 32], *address); // From a `[u8; 32]`. // ANCHOR: array_to_address let my_slice = [1u8; 32]; let address = Address::new(my_slice); // ANCHOR_END: array_to_address assert_eq!([1u8; 32], *address); // From a string. // ANCHOR: hex_string_to_address let hex_str = "0x0000000000000000000000000000000000000000000000000000000000000000"; let address = Address::from_str(hex_str)?; // ANCHOR_END: hex_string_to_address assert_eq!([0u8; 32], *address); // ANCHOR_END: address // ANCHOR: address_to_identity let _identity_from_address = Identity::Address(address); // ANCHOR_END: address_to_identity // ANCHOR: address_to_str let _str_from_address: &str = address.to_string().as_str(); // ANCHOR_END: address_to_str // ANCHOR: address_to_bits256 let bits_256 = Bits256(address.into()); // ANCHOR_END: address_to_bits256 // ANCHOR: b256_to_evm_address let _evm_address = EvmAddress::from(bits_256); // ANCHOR_END: b256_to_evm_address Ok(()) } #[tokio::test] async fn asset_id() -> Result<()> { // ANCHOR: asset_id use std::str::FromStr; use fuels::types::AssetId; // Zeroed Bytes32 let asset_id = AssetId::zeroed(); // Grab the inner `[u8; 32]` from // `Bytes32` by dereferencing (i.e. `*`) it. assert_eq!([0u8; 32], *asset_id); // From a `[u8; 32]`. // ANCHOR: array_to_asset_id let my_slice = [1u8; 32]; let asset_id = AssetId::new(my_slice); // ANCHOR_END: array_to_asset_id assert_eq!([1u8; 32], *asset_id); // From a string. // ANCHOR: string_to_asset_id let hex_str = "0x0000000000000000000000000000000000000000000000000000000000000000"; let asset_id = AssetId::from_str(hex_str)?; // ANCHOR_END: string_to_asset_id assert_eq!([0u8; 32], *asset_id); // ANCHOR_END: asset_id Ok(()) } #[tokio::test] async fn contract_id() -> Result<()> { // ANCHOR: contract_id use std::str::FromStr; use fuels::types::ContractId; // Zeroed Bytes32 let contract_id = ContractId::zeroed(); // Grab the inner `[u8; 32]` from // `Bytes32` by dereferencing (i.e. `*`) it. assert_eq!([0u8; 32], *contract_id); // From a `[u8; 32]`. // ANCHOR: array_to_contract_id let my_slice = [1u8; 32]; let contract_id = ContractId::new(my_slice); // ANCHOR_END: array_to_contract_id assert_eq!([1u8; 32], *contract_id); // From a string. // ANCHOR: string_to_contract_id let hex_str = "0x0000000000000000000000000000000000000000000000000000000000000000"; let contract_id = ContractId::from_str(hex_str)?; // ANCHOR_END: string_to_contract_id assert_eq!([0u8; 32], *contract_id); // ANCHOR_END: contract_id // ANCHOR: contract_id_to_identity let _identity_from_contract_id = Identity::ContractId(contract_id); // ANCHOR_END: contract_id_to_identity // ANCHOR: contract_id_to_str let _str_from_contract_id: &str = contract_id.to_string().as_str(); // ANCHOR_END: contract_id_to_str Ok(()) } #[tokio::test] async fn type_conversion() -> Result<()> { // ANCHOR: type_conversion use fuels::types::{AssetId, ContractId}; let contract_id = ContractId::new([1u8; 32]); let asset_id: AssetId = AssetId::new(*contract_id); assert_eq!([1u8; 32], *asset_id); // ANCHOR_END: type_conversion // ANCHOR: asset_id_to_str let _str_from_asset_id: &str = asset_id.to_string().as_str(); // ANCHOR_END: asset_id_to_str // ANCHOR: contract_id_to_bits256 let _contract_id_to_bits_256 = Bits256(contract_id.into()); // ANCHOR_END: contract_id_to_bits256 // ANCHOR: asset_id_to_bits256 let _asset_id_to_bits_256 = Bits256(asset_id.into()); // ANCHOR_END: asset_id_to_bits256 Ok(()) } #[tokio::test] async fn unused_generics() -> Result<()> { use fuels::prelude::*; abigen!(Contract( name = "MyContract", abi = "e2e/sway/types/contracts/generics/out/release/generics-abi.json" )); // ANCHOR: unused_generics_struct assert_eq!( >::new(15), StructUnusedGeneric { field: 15, _unused_generic_0: std::marker::PhantomData, _unused_generic_1: std::marker::PhantomData } ); // ANCHOR_END: unused_generics_struct let my_enum = >::One(15); // ANCHOR: unused_generics_enum match my_enum { EnumUnusedGeneric::One(_value) => {} EnumUnusedGeneric::IgnoreMe(..) => panic!("Will never receive this variant"), } // ANCHOR_END: unused_generics_enum Ok(()) } } ================================================ FILE: examples/wallets/Cargo.toml ================================================ [package] name = "fuels-example-wallets" version = { workspace = true } authors = { workspace = true } edition = { workspace = true } homepage = { workspace = true } license = { workspace = true } publish = false repository = { workspace = true } description = "Fuel Rust SDK wallet examples." [dev-dependencies] fuels = { workspace = true, features = ["default", "accounts-keystore"] } rand = { workspace = true } tokio = { workspace = true, features = ["full"] } ================================================ FILE: examples/wallets/src/lib.rs ================================================ #[cfg(test)] mod tests { use fuels::{ accounts::{ keystore::Keystore, signers::{derivation::DEFAULT_DERIVATION_PATH, private_key::PrivateKeySigner}, }, crypto::SecretKey, prelude::*, }; use rand::thread_rng; #[tokio::test] async fn create_random_wallet() -> Result<()> { // ANCHOR: create_random_wallet use fuels::prelude::*; // Use the test helper to setup a test provider. let provider = setup_test_provider(vec![], vec![], None, None).await?; // Create the wallet. let _wallet = Wallet::random(&mut thread_rng(), provider); // ANCHOR_END: create_random_wallet Ok(()) } #[tokio::test] async fn create_wallet_from_secret_key() -> std::result::Result<(), Box> { // ANCHOR: create_wallet_from_secret_key use std::str::FromStr; use fuels::{crypto::SecretKey, prelude::*}; // Use the test helper to setup a test provider. let provider = setup_test_provider(vec![], vec![], None, None).await?; // Setup the private key. let secret = SecretKey::from_str( "5f70feeff1f229e4a95e1056e8b4d80d0b24b565674860cc213bdb07127ce1b1", )?; // Create the wallet. let _wallet = Wallet::new(PrivateKeySigner::new(secret), provider); // ANCHOR_END: create_wallet_from_secret_key Ok(()) } #[tokio::test] async fn create_wallet_from_mnemonic() -> Result<()> { // ANCHOR: create_wallet_from_mnemonic use fuels::prelude::*; let phrase = "oblige salon price punch saddle immune slogan rare snap desert retire surprise"; // Use the test helper to setup a test provider. let provider = setup_test_provider(vec![], vec![], None, None).await?; // Create first account from mnemonic phrase. let key = SecretKey::new_from_mnemonic_phrase_with_path(phrase, "m/44'/1179993420'/0'/0/0")?; let signer = PrivateKeySigner::new(key); let _wallet = Wallet::new(signer, provider.clone()); // Or with the default derivation path. let key = SecretKey::new_from_mnemonic_phrase_with_path(phrase, DEFAULT_DERIVATION_PATH)?; let signer = PrivateKeySigner::new(key); let wallet = Wallet::new(signer, provider); let expected_address = "f18b6446deb8135544ba60333e5b7522685cd2cf64aa4e4c75df725149850b65"; assert_eq!(wallet.address().to_string(), expected_address); // ANCHOR_END: create_wallet_from_mnemonic Ok(()) } #[tokio::test] async fn create_and_store_mnemonic_key() -> Result<()> { // ANCHOR: create_and_store_mnemonic_key let dir = std::env::temp_dir(); let keystore = Keystore::new(&dir); let phrase = "oblige salon price punch saddle immune slogan rare snap desert retire surprise"; // Create a key from the mnemonic phrase using the default derivation path. let key = SecretKey::new_from_mnemonic_phrase_with_path(phrase, DEFAULT_DERIVATION_PATH)?; let password = "my_master_password"; // Encrypt and store the key on disk. It can be recovered using `Keystore::load_key`. let uuid = keystore.save_key(key, password, thread_rng())?; // Recover key from disk let recovered_key = keystore.load_key(&uuid, password)?; // ANCHOR_END: create_and_store_mnemonic_key assert_eq!(key, recovered_key); Ok(()) } #[tokio::test] async fn wallet_transfer() -> Result<()> { // ANCHOR: wallet_transfer use fuels::prelude::*; // Setup 2 test wallets with 1 coin each. let num_wallets = 2; let coins_per_wallet = 1; let coin_amount = 2; let wallets = launch_custom_provider_and_get_wallets( WalletsConfig::new(Some(num_wallets), Some(coins_per_wallet), Some(coin_amount)), None, None, ) .await?; // Transfer the base asset with amount 1 from wallet 1 to wallet 2. let transfer_amount = 1; let asset_id = Default::default(); let _res = wallets[0] .transfer( wallets[1].address(), transfer_amount, asset_id, TxPolicies::default(), ) .await?; let wallet_2_final_coins = wallets[1].get_coins(AssetId::zeroed()).await?; // Check that wallet 2 now has 2 coins. assert_eq!(wallet_2_final_coins.len(), 2); // ANCHOR_END: wallet_transfer Ok(()) } #[tokio::test] async fn wallet_contract_transfer() -> Result<()> { use fuels::prelude::*; use rand::Fill; let mut rng = rand::thread_rng(); let base_asset = AssetConfig { id: AssetId::zeroed(), num_coins: 1, coin_amount: 1000, }; let mut random_asset_id = AssetId::zeroed(); random_asset_id.try_fill(&mut rng).unwrap(); let random_asset = AssetConfig { id: random_asset_id, num_coins: 3, coin_amount: 100, }; let wallet_config = WalletsConfig::new_multiple_assets(1, vec![random_asset, base_asset]); let wallet = launch_custom_provider_and_get_wallets(wallet_config, None, None) .await? .pop() .unwrap(); let contract_id = Contract::load_from( "../../e2e/sway/contracts/contract_test/out/release/contract_test.bin", LoadConfiguration::default(), )? .deploy(&wallet, TxPolicies::default()) .await? .contract_id; // ANCHOR: wallet_contract_transfer // Check the current balance of the contract with id 'contract_id'. let contract_balances = wallet .try_provider()? .get_contract_balances(&contract_id) .await?; assert!(contract_balances.is_empty()); // Transfer an amount of 300 to the contract. let amount = 300; let asset_id = random_asset_id; let _res = wallet .force_transfer_to_contract(contract_id, amount, asset_id, TxPolicies::default()) .await?; // Check that the contract now has 1 coin. let contract_balances = wallet .try_provider()? .get_contract_balances(&contract_id) .await?; assert_eq!(contract_balances.len(), 1); let random_asset_balance = contract_balances.get(&random_asset_id).unwrap(); assert_eq!(*random_asset_balance, 300); // ANCHOR_END: wallet_contract_transfer Ok(()) } #[tokio::test] #[allow(unused_variables)] async fn setup_multiple_wallets() -> Result<()> { // ANCHOR: multiple_wallets_helper use fuels::prelude::*; // This helper launches a local node and provides 10 test wallets linked to it. // The initial balance defaults to 1 coin per wallet with an amount of 1_000_000_000. let wallets = launch_custom_provider_and_get_wallets(WalletsConfig::default(), None, None).await?; // ANCHOR_END: multiple_wallets_helper // ANCHOR: setup_5_wallets let num_wallets = 5; let coins_per_wallet = 3; let amount_per_coin = 100; let config = WalletsConfig::new( Some(num_wallets), Some(coins_per_wallet), Some(amount_per_coin), ); // Launches a local node and provides test wallets as specified by the config. let wallets = launch_custom_provider_and_get_wallets(config, None, None).await?; // ANCHOR_END: setup_5_wallets Ok(()) } #[tokio::test] #[allow(unused_variables)] async fn setup_wallet_multiple_assets() -> Result<()> { // ANCHOR: multiple_assets_wallet // ANCHOR: multiple_assets_coins use fuels::prelude::*; let signer = PrivateKeySigner::random(&mut thread_rng()); let num_assets = 5; let coins_per_asset = 10; let amount_per_coin = 15; let (coins, asset_ids) = setup_multiple_assets_coins( signer.address(), num_assets, coins_per_asset, amount_per_coin, ); // ANCHOR_END: multiple_assets_coins let provider = setup_test_provider(coins.clone(), vec![], None, None).await?; let wallet = Wallet::new(signer, provider); // ANCHOR_END: multiple_assets_wallet Ok(()) } #[tokio::test] #[allow(unused_variables)] async fn setup_wallet_custom_assets() -> std::result::Result<(), Box> { // ANCHOR: custom_assets_wallet use fuels::prelude::*; use rand::Fill; let mut rng = rand::thread_rng(); let signer = PrivateKeySigner::random(&mut rng); let asset_base = AssetConfig { id: AssetId::zeroed(), num_coins: 2, coin_amount: 4, }; let mut asset_id_1 = AssetId::zeroed(); asset_id_1.try_fill(&mut rng)?; let asset_1 = AssetConfig { id: asset_id_1, num_coins: 6, coin_amount: 8, }; let mut asset_id_2 = AssetId::zeroed(); asset_id_2.try_fill(&mut rng)?; let asset_2 = AssetConfig { id: asset_id_2, num_coins: 10, coin_amount: 12, }; let assets = vec![asset_base, asset_1, asset_2]; let coins = setup_custom_assets_coins(signer.address(), &assets); let provider = setup_test_provider(coins, vec![], None, None).await?; let wallet = Wallet::new(signer, provider.clone()); // ANCHOR_END: custom_assets_wallet // ANCHOR: custom_assets_wallet_short let num_wallets = 1; let wallet_config = WalletsConfig::new_multiple_assets(num_wallets, assets); let wallets = launch_custom_provider_and_get_wallets(wallet_config, None, None).await?; // ANCHOR_END: custom_assets_wallet_short // ANCHOR: wallet_to_address let wallet = Wallet::random(&mut rng, provider); let address: Address = wallet.address(); // ANCHOR_END: wallet_to_address Ok(()) } #[tokio::test] #[allow(unused_variables)] async fn get_balances() -> Result<()> { use std::collections::HashMap; use fuels::{ prelude::{DEFAULT_COIN_AMOUNT, DEFAULT_NUM_COINS, launch_provider_and_get_wallet}, types::AssetId, }; let wallet = launch_provider_and_get_wallet().await?; // ANCHOR: get_asset_balance let asset_id = AssetId::zeroed(); let balance: u128 = wallet.get_asset_balance(&asset_id).await?; // ANCHOR_END: get_asset_balance // ANCHOR: get_balances let balances: HashMap = wallet.get_balances().await?; // ANCHOR_END: get_balances // ANCHOR: get_balance_hashmap let asset_balance = balances.get(&asset_id.to_string()).unwrap(); // ANCHOR_END: get_balance_hashmap assert_eq!( *asset_balance, (DEFAULT_COIN_AMOUNT * DEFAULT_NUM_COINS) as u128 ); Ok(()) } #[tokio::test] async fn wallet_transfer_to_base_layer() -> Result<()> { // ANCHOR: wallet_withdraw_to_base use std::str::FromStr; use fuels::prelude::*; let wallets = launch_custom_provider_and_get_wallets( WalletsConfig::new(Some(1), None, None), None, None, ) .await?; let wallet = wallets.first().unwrap(); let amount = 1000; let base_layer_address = Address::from_str( "0x4710162c2e3a95a6faff05139150017c9e38e5e280432d546fae345d6ce6d8fe", )?; // Transfer an amount of 1000 to the specified base layer address. let response = wallet .withdraw_to_base_layer(base_layer_address, amount, TxPolicies::default()) .await?; let _block_height = wallet.provider().produce_blocks(1, None).await?; // Retrieve a message proof from the provider. let proof = wallet .try_provider()? .get_message_proof(&response.tx_id, &response.nonce, None, Some(2)) .await?; // Verify the amount and recipient. assert_eq!(proof.amount, amount); assert_eq!(proof.recipient, base_layer_address); // ANCHOR_END: wallet_withdraw_to_base Ok(()) } } ================================================ FILE: packages/fuels/Cargo.toml ================================================ [package] name = "fuels" version = { workspace = true } authors = { workspace = true } edition = { workspace = true } homepage = { workspace = true } readme = { workspace = true } license = { workspace = true } repository = { workspace = true } rust-version = { workspace = true } description = "Fuel Rust SDK." [package.metadata.cargo-machete] ignored = ["fuel-core"] [dependencies] fuel-core-client = { workspace = true, optional = true } fuel-crypto = { workspace = true } fuel-tx = { workspace = true } fuels-accounts = { workspace = true, default-features = false } fuels-core = { workspace = true } fuels-macros = { workspace = true } fuels-programs = { workspace = true } fuels-test-helpers = { workspace = true, optional = true } [features] default = ["std", "fuels-test-helpers?/fuels-accounts", "coin-cache"] coin-cache = ["fuels-accounts/coin-cache"] test-helpers = ["dep:fuels-test-helpers", "fuels-accounts/test-helpers"] # The crates enabled via `dep:` below are not currently wasm compatible, as # such they are only available if `std` is enabled. The `dep:` syntax was # used so that we don't get a new feature flag for every optional dependency. std = [ "dep:fuel-core-client", "fuels-programs/std", "dep:fuels-test-helpers", "fuels-accounts/std", "fuels-core/std", "fuels-test-helpers?/std", ] fuel-core-lib = ["fuels-test-helpers?/fuel-core-lib"] rocksdb = ["fuels-test-helpers?/rocksdb"] accounts-signer-aws-kms = ["fuels-accounts/signer-aws-kms"] accounts-signer-google-kms = ["fuels-accounts/signer-google-kms"] accounts-keystore = ["fuels-accounts/keystore"] fault-proving = ["fuel-core-client?/fault-proving", "fuels-test-helpers?/fault-proving", "fuels-accounts/fault-proving", "fuels-core/fault-proving", "fuels-programs/fault-proving"] ================================================ FILE: packages/fuels/src/lib.rs ================================================ //! # Fuel Rust SDK. //! //! ## Quickstart: `prelude` //! //! A prelude is provided which imports all the important data types and traits for you. Use this when you want to quickly bootstrap a new project. //! //! ```no_run //! # #[allow(unused)] //! use fuels::prelude::*; //! ``` //! //! Examples on how you can use the types imported by the prelude can be found in //! the [test suite](https://github.com/FuelLabs/fuels-rs/tree/master/packages/fuels/tests) pub mod tx { pub use fuel_tx::{ ConsensusParameters, ContractIdExt, ContractParameters, FeeParameters, GasCosts, PredicateParameters, Receipt, ScriptExecutionResult, ScriptParameters, StorageSlot, Transaction as FuelTransaction, TxId, TxParameters, TxPointer, UpgradePurpose, UploadSubsection, UtxoId, Witness, consensus_parameters, field, }; } #[cfg(feature = "std")] pub mod client { pub use fuel_core_client::client::{ FuelClient, pagination::{PageDirection, PaginationRequest}, }; } pub mod macros { pub use fuels_macros::*; } pub mod programs { pub use fuels_programs::*; } pub mod core { pub use fuels_core::{Configurable, Configurables, codec, constants, offsets, traits}; } pub mod crypto { pub use fuel_crypto::{Hasher, Message, PublicKey, SecretKey, Signature}; } pub mod accounts { pub use fuels_accounts::*; } pub mod types { pub use fuels_core::types::*; } #[cfg(feature = "std")] pub mod test_helpers { pub use fuels_test_helpers::*; } #[doc(hidden)] pub mod prelude { #[cfg(feature = "std")] pub use super::{ accounts::{ Account, ViewOnlyAccount, predicate::Predicate, provider::*, signers::*, wallet::Wallet, }, core::{ codec::{LogDecoder, LogId, LogResult}, traits::Signer, }, macros::setup_program_test, programs::{ calls::{CallHandler, CallParameters, ContractDependency, Execution}, contract::{Contract, LoadConfiguration, StorageConfiguration}, }, test_helpers::*, types::transaction_builders::*, }; pub use super::{ core::constants::*, macros::abigen, tx::Receipt, types::{ Address, AssetId, Bytes, ContractId, RawSlice, Salt, errors::{Error, Result}, transaction::*, }, }; } ================================================ FILE: packages/fuels-accounts/Cargo.toml ================================================ [package] name = "fuels-accounts" version = { workspace = true } authors = { workspace = true } edition = { workspace = true } homepage = { workspace = true } license = { workspace = true } repository = { workspace = true } rust-version = { workspace = true } description = "Fuel Rust SDK accounts." [package.metadata.cargo-machete] ignored = ["aws-config"] [dependencies] async-trait = { workspace = true, default-features = false } aws-config = { workspace = true, features = [ "behavior-version-latest", ], optional = true } aws-sdk-kms = { workspace = true, features = ["default"], optional = true } chrono = { workspace = true } cynic = { workspace = true, optional = true } eth-keystore = { workspace = true, optional = true } fuel-core-client = { workspace = true, optional = true } fuel-core-types = { workspace = true } fuel-crypto = { workspace = true, features = ["random"] } fuel-tx = { workspace = true } fuel-types = { workspace = true, features = ["random"] } fuels-core = { workspace = true, default-features = false } futures = { workspace = true} google-cloud-kms = { workspace = true, features = ["auth"], optional = true } itertools = { workspace = true } k256 = { workspace = true, features = ["ecdsa-core", "pem"] } rand = { workspace = true, default-features = false } semver = { workspace = true } tai64 = { workspace = true, features = ["serde"] } thiserror = { workspace = true, default-features = false } tokio = { workspace = true, features = ["full"], optional = true } zeroize = { workspace = true, features = ["derive"] } [dev-dependencies] fuel-tx = { workspace = true, features = ["test-helpers", "random"] } mockall = { workspace = true, default-features = false } tempfile = { workspace = true } tokio = { workspace = true, features = ["test-util"] } [features] default = ["std"] coin-cache = ["tokio/time"] std = ["fuels-core/std", "dep:tokio", "fuel-core-client/default", "dep:cynic"] test-helpers = [] keystore = ["dep:eth-keystore"] signer-aws-kms = ["dep:aws-sdk-kms", "dep:aws-config"] signer-google-kms = ["dep:google-cloud-kms"] fault-proving = ["fuel-core-types/fault-proving", "fuel-core-client?/fault-proving", "fuels-core/fault-proving"] ================================================ FILE: packages/fuels-accounts/src/account.rs ================================================ use std::collections::HashMap; use async_trait::async_trait; use fuel_core_client::client::pagination::{PaginatedResult, PaginationRequest}; use fuel_tx::{Output, TxId, TxPointer, UtxoId}; use fuels_core::types::{ Address, AssetId, Bytes32, ContractId, Nonce, coin::Coin, coin_type::CoinType, coin_type_id::CoinTypeId, errors::{Context, Result}, input::Input, message::Message, transaction::{Transaction, TxPolicies}, transaction_builders::{BuildableTransaction, ScriptTransactionBuilder, TransactionBuilder}, transaction_response::TransactionResponse, tx_response::TxResponse, tx_status::Success, }; use crate::{ accounts_utils::{ add_base_change_if_needed, available_base_assets_and_amount, calculate_missing_base_amount, extract_message_nonce, split_into_utxo_ids_and_nonces, }, provider::{Provider, ResourceFilter}, }; #[derive(Clone, Debug)] pub struct WithdrawToBaseResponse { pub tx_status: Success, pub tx_id: TxId, pub nonce: Nonce, } #[cfg_attr(not(target_arch = "wasm32"), async_trait)] pub trait ViewOnlyAccount: Send + Sync { fn address(&self) -> Address; fn try_provider(&self) -> Result<&Provider>; async fn get_transactions( &self, request: PaginationRequest, ) -> Result> { Ok(self .try_provider()? .get_transactions_by_owner(&self.address(), request) .await?) } /// Gets all unspent coins of asset `asset_id` owned by the account. async fn get_coins(&self, asset_id: AssetId) -> Result> { Ok(self .try_provider()? .get_coins(&self.address(), asset_id) .await?) } /// Get the balance of all spendable coins `asset_id` for address `address`. This is different /// from getting coins because we are just returning a number (the sum of UTXOs amount) instead /// of the UTXOs. async fn get_asset_balance(&self, asset_id: &AssetId) -> Result { self.try_provider()? .get_asset_balance(&self.address(), asset_id) .await } /// Gets all unspent messages owned by the account. async fn get_messages(&self) -> Result> { Ok(self.try_provider()?.get_messages(&self.address()).await?) } /// Get all the spendable balances of all assets for the account. This is different from getting /// the coins because we are only returning the sum of UTXOs coins amount and not the UTXOs /// coins themselves. async fn get_balances(&self) -> Result> { self.try_provider()?.get_balances(&self.address()).await } /// Get some spendable resources (coins and messages) of asset `asset_id` owned by the account /// that add up at least to amount `amount`. The returned coins (UTXOs) are actual coins that /// can be spent. The number of UXTOs is optimized to prevent dust accumulation. async fn get_spendable_resources( &self, asset_id: AssetId, amount: u128, excluded_coins: Option>, ) -> Result> { let (excluded_utxos, excluded_message_nonces) = split_into_utxo_ids_and_nonces(excluded_coins); let filter = ResourceFilter { from: self.address(), asset_id: Some(asset_id), amount, excluded_utxos, excluded_message_nonces, }; self.try_provider()?.get_spendable_resources(filter).await } /// Returns a vector containing the output coin and change output given an asset and amount fn get_asset_outputs_for_amount( &self, to: Address, asset_id: AssetId, amount: u64, ) -> Vec { vec![ Output::coin(to, amount, asset_id), // Note that the change will be computed by the node. // Here we only have to tell the node who will own the change and its asset ID. Output::change(self.address(), 0, asset_id), ] } /// Returns a vector consisting of `Input::Coin`s and `Input::Message`s for the given /// asset ID and amount. async fn get_asset_inputs_for_amount( &self, asset_id: AssetId, amount: u128, excluded_coins: Option>, ) -> Result>; /// Add base asset inputs to the transaction to cover the estimated fee /// and add a change output for the base asset if needed. /// Requires contract inputs to be at the start of the transactions inputs vec /// so that their indexes are retained async fn adjust_for_fee( &self, tb: &mut Tb, used_base_amount: u128, ) -> Result<()> { let provider = self.try_provider()?; let consensus_parameters = provider.consensus_parameters().await?; let base_asset_id = consensus_parameters.base_asset_id(); let (base_assets, base_amount) = available_base_assets_and_amount(tb, base_asset_id); let missing_base_amount = calculate_missing_base_amount(tb, base_amount, used_base_amount, provider).await?; if missing_base_amount > 0 { let new_base_inputs = self .get_asset_inputs_for_amount( *consensus_parameters.base_asset_id(), missing_base_amount, Some(base_assets), ) .await .with_context(|| { format!("failed to get base asset ({base_asset_id}) inputs with amount: `{missing_base_amount}`") })?; tb.inputs_mut().extend(new_base_inputs); }; add_base_change_if_needed(tb, self.address(), *consensus_parameters.base_asset_id()); Ok(()) } } #[cfg_attr(not(target_arch = "wasm32"), async_trait)] pub trait Account: ViewOnlyAccount { // Add signatures to the builder if the underlying account is a wallet fn add_witnesses(&self, _tb: &mut Tb) -> Result<()> { Ok(()) } /// Transfer funds from this account to another `Address`. /// Fails if amount for asset ID is larger than address's spendable coins. /// Returns the transaction ID that was sent and the list of receipts. async fn transfer( &self, to: Address, amount: u64, asset_id: AssetId, tx_policies: TxPolicies, ) -> Result { let provider = self.try_provider()?; let inputs = self .get_asset_inputs_for_amount(asset_id, amount.into(), None) .await?; let outputs = self.get_asset_outputs_for_amount(to, asset_id, amount); let mut tx_builder = ScriptTransactionBuilder::prepare_transfer(inputs, outputs, tx_policies); self.add_witnesses(&mut tx_builder)?; let consensus_parameters = provider.consensus_parameters().await?; let used_base_amount = if asset_id == *consensus_parameters.base_asset_id() { amount.into() } else { 0 }; self.adjust_for_fee(&mut tx_builder, used_base_amount) .await .context("failed to adjust inputs to cover for missing base asset")?; let tx = tx_builder.build(provider).await?; let tx_id = tx.id(consensus_parameters.chain_id()); let tx_status = provider.send_transaction_and_await_commit(tx).await?; Ok(TxResponse { tx_status: tx_status.take_success_checked(None)?, tx_id, }) } /// Unconditionally transfers `balance` of type `asset_id` to /// the contract at `to`. /// Fails if balance for `asset_id` is larger than this account's spendable balance. /// Returns the corresponding transaction ID and the list of receipts. /// /// CAUTION !!! /// /// This will transfer coins to a contract, possibly leading /// to the PERMANENT LOSS OF COINS if not used with care. async fn force_transfer_to_contract( &self, to: ContractId, balance: u64, asset_id: AssetId, tx_policies: TxPolicies, ) -> Result { let provider = self.try_provider()?; let zeroes = Bytes32::zeroed(); let mut inputs = vec![Input::contract( UtxoId::new(zeroes, 0), zeroes, zeroes, TxPointer::default(), to, )]; inputs.extend( self.get_asset_inputs_for_amount(asset_id, balance.into(), None) .await?, ); let outputs = vec![ Output::contract(0, zeroes, zeroes), Output::change(self.address(), 0, asset_id), ]; // Build transaction and sign it let mut tb = ScriptTransactionBuilder::prepare_contract_transfer( to, balance, asset_id, inputs, outputs, tx_policies, ); let consensus_parameters = provider.consensus_parameters().await?; let used_base_amount = if asset_id == *consensus_parameters.base_asset_id() { balance } else { 0 }; self.add_witnesses(&mut tb)?; self.adjust_for_fee(&mut tb, used_base_amount.into()) .await .context("failed to adjust inputs to cover for missing base asset")?; let tx = tb.build(provider).await?; let consensus_parameters = provider.consensus_parameters().await?; let tx_id = tx.id(consensus_parameters.chain_id()); let tx_status = provider.send_transaction_and_await_commit(tx).await?; Ok(TxResponse { tx_status: tx_status.take_success_checked(None)?, tx_id, }) } /// Withdraws an amount of the base asset to /// an address on the base chain. /// Returns the transaction ID, message ID and the list of receipts. async fn withdraw_to_base_layer( &self, to: Address, amount: u64, tx_policies: TxPolicies, ) -> Result { let provider = self.try_provider()?; let consensus_parameters = provider.consensus_parameters().await?; let inputs = self .get_asset_inputs_for_amount(*consensus_parameters.base_asset_id(), amount.into(), None) .await?; let mut tb = ScriptTransactionBuilder::prepare_message_to_output( to, amount, inputs, tx_policies, *consensus_parameters.base_asset_id(), ); self.add_witnesses(&mut tb)?; self.adjust_for_fee(&mut tb, amount.into()) .await .context("failed to adjust inputs to cover for missing base asset")?; let tx = tb.build(provider).await?; let tx_id = tx.id(consensus_parameters.chain_id()); let tx_status = provider.send_transaction_and_await_commit(tx).await?; let success = tx_status.take_success_checked(None)?; let nonce = extract_message_nonce(&success.receipts) .expect("MessageId could not be retrieved from tx receipts."); Ok(WithdrawToBaseResponse { tx_status: success, tx_id, nonce, }) } } #[cfg(test)] mod tests { use std::str::FromStr; use fuel_crypto::{Message, SecretKey, Signature}; use fuel_tx::{Address, ConsensusParameters, Output, Transaction as FuelTransaction}; use fuels_core::{ traits::Signer, types::{DryRun, DryRunner, transaction::Transaction}, }; use super::*; use crate::signers::private_key::PrivateKeySigner; #[derive(Default)] struct MockDryRunner { c_param: ConsensusParameters, } #[cfg_attr(not(target_arch = "wasm32"), async_trait)] impl DryRunner for MockDryRunner { async fn dry_run(&self, _: FuelTransaction) -> Result { Ok(DryRun { succeeded: true, script_gas: 0, variable_outputs: 0, }) } async fn consensus_parameters(&self) -> Result { Ok(self.c_param.clone()) } async fn estimate_gas_price(&self, _block_header: u32) -> Result { Ok(0) } async fn estimate_predicates( &self, _: &FuelTransaction, _: Option, ) -> Result { unimplemented!() } } #[tokio::test] async fn sign_tx_and_verify() -> std::result::Result<(), Box> { // ANCHOR: sign_tb let secret = SecretKey::from_str( "5f70feeff1f229e4a95e1056e8b4d80d0b24b565674860cc213bdb07127ce1b1", )?; let signer = PrivateKeySigner::new(secret); // Set up a transaction let mut tb = { let input_coin = Input::ResourceSigned { resource: CoinType::Coin(Coin { amount: 10000000, owner: signer.address(), ..Default::default() }), }; let output_coin = Output::coin( Address::from_str( "0xc7862855b418ba8f58878db434b21053a61a2025209889cc115989e8040ff077", )?, 1, Default::default(), ); let change = Output::change(signer.address(), 0, Default::default()); ScriptTransactionBuilder::prepare_transfer( vec![input_coin], vec![output_coin, change], Default::default(), ) }; // Add `Signer` to the transaction builder tb.add_signer(signer.clone())?; // ANCHOR_END: sign_tb let tx = tb.build(MockDryRunner::default()).await?; // Resolve signatures and add corresponding witness indexes // Extract the signature from the tx witnesses let bytes = <[u8; Signature::LEN]>::try_from(tx.witnesses().first().unwrap().as_ref())?; let tx_signature = Signature::from_bytes(bytes); // Sign the transaction manually let message = Message::from_bytes(*tx.id(0.into())); let signature = signer.sign(message).await?; // Check if the signatures are the same assert_eq!(signature, tx_signature); // Check if the signature is what we expect it to be assert_eq!( signature, Signature::from_str( "faa616776a1c336ef6257f7cb0cb5cd932180e2d15faba5f17481dae1cbcaf314d94617bd900216a6680bccb1ea62438e4ca93b0d5733d33788ef9d79cc24e9f" )? ); // Recover the address that signed the transaction let recovered_address = signature.recover(&message)?; assert_eq!(*signer.address(), *recovered_address.hash()); // Verify signature signature.verify(&recovered_address, &message)?; Ok(()) } } ================================================ FILE: packages/fuels-accounts/src/accounts_utils.rs ================================================ use fuel_tx::{AssetId, Output, Receipt, UtxoId}; use fuel_types::Nonce; use fuels_core::types::{ Address, coin::Coin, coin_type::CoinType, coin_type_id::CoinTypeId, errors::{Error, Result, error}, input::Input, transaction_builders::TransactionBuilder, }; use itertools::{Either, Itertools}; use crate::provider::Provider; pub fn extract_message_nonce(receipts: &[Receipt]) -> Option { receipts.iter().find_map(|m| m.nonce()).copied() } pub async fn calculate_missing_base_amount( tb: &impl TransactionBuilder, available_base_amount: u128, reserved_base_amount: u128, provider: &Provider, ) -> Result { let max_fee: u128 = tb.estimate_max_fee(provider).await?.into(); let total_used = max_fee + reserved_base_amount; let missing_amount = if total_used > available_base_amount { total_used - available_base_amount } else if !is_consuming_utxos(tb) { // A tx needs to have at least 1 spendable input // Enforce a minimum required amount on the base asset if no other inputs are present 1 } else { 0 }; Ok(missing_amount) } pub fn available_base_assets_and_amount( tb: &impl TransactionBuilder, base_asset_id: &AssetId, ) -> (Vec, u128) { let mut sum = 0u128; let iter = tb.inputs() .iter() .filter_map(|input| match input { Input::ResourceSigned { resource, .. } | Input::ResourcePredicate { resource, .. } => match resource { CoinType::Coin(Coin { amount, asset_id, .. }) if asset_id == base_asset_id => { sum += u128::from(*amount); resource.id() } CoinType::Message(message) => { if message.data.is_empty() { sum += u128::from(message.amount); resource.id() } else { None } } _ => None, }, _ => None, }) .collect_vec(); (iter, sum) } pub fn split_into_utxo_ids_and_nonces( excluded_coins: Option>, ) -> (Vec, Vec) { excluded_coins .map(|excluded_coins| { excluded_coins .iter() .partition_map(|coin_id| match coin_id { CoinTypeId::UtxoId(utxo_id) => Either::Left(*utxo_id), CoinTypeId::Nonce(nonce) => Either::Right(*nonce), }) }) .unwrap_or_default() } fn is_consuming_utxos(tb: &impl TransactionBuilder) -> bool { tb.inputs() .iter() .any(|input| !matches!(input, Input::Contract { .. })) } pub fn add_base_change_if_needed( tb: &mut impl TransactionBuilder, address: Address, base_asset_id: AssetId, ) { let is_base_change_present = tb.outputs().iter().any(|output| { matches!(output , Output::Change { asset_id , .. } if *asset_id == base_asset_id) }); if !is_base_change_present { tb.outputs_mut() .push(Output::change(address, 0, base_asset_id)); } } pub(crate) fn try_provider_error() -> Error { error!( Other, "no provider available. Make sure to use `set_provider`" ) } ================================================ FILE: packages/fuels-accounts/src/coin_cache.rs ================================================ use std::{ collections::{HashMap, HashSet}, hash::{Hash, Hasher}, }; use fuel_types::AssetId; use fuels_core::types::{Address, coin_type_id::CoinTypeId}; use tokio::time::{Duration, Instant}; type CoinCacheKey = (Address, AssetId); #[derive(Debug)] pub(crate) struct CoinsCache { ttl: Duration, items: HashMap>, } impl Default for CoinsCache { fn default() -> Self { Self::new(Duration::from_secs(30)) } } impl CoinsCache { pub fn new(ttl: Duration) -> Self { Self { ttl, items: HashMap::default(), } } pub fn insert_multiple( &mut self, coin_ids: impl IntoIterator)>, ) { for (key, ids) in coin_ids { let new_items = ids.into_iter().map(CoinCacheItem::new); let items = self.items.entry(key).or_default(); items.extend(new_items); } } pub fn get_active(&mut self, key: &CoinCacheKey) -> HashSet { self.remove_expired_entries(key); self.items .get(key) .cloned() .unwrap_or_default() .into_iter() .map(|item| item.id) .collect() } pub fn remove_items( &mut self, inputs: impl IntoIterator)>, ) { for (key, ids) in inputs { for id in ids { self.remove(&key, id); } } } fn remove(&mut self, key: &CoinCacheKey, id: CoinTypeId) { if let Some(ids) = self.items.get_mut(key) { let item = CoinCacheItem::new(id); ids.remove(&item); } } fn remove_expired_entries(&mut self, key: &CoinCacheKey) { if let Some(entry) = self.items.get_mut(key) { entry.retain(|item| item.is_valid(self.ttl)); } } } #[derive(Eq, Debug, Clone)] struct CoinCacheItem { created_at: Instant, pub id: CoinTypeId, } impl PartialEq for CoinCacheItem { fn eq(&self, other: &Self) -> bool { self.id.eq(&other.id) } } impl Hash for CoinCacheItem { fn hash(&self, state: &mut H) { self.id.hash(state); } } impl CoinCacheItem { pub fn new(id: CoinTypeId) -> Self { Self { created_at: Instant::now(), id, } } pub fn is_valid(&self, ttl: Duration) -> bool { self.created_at + ttl > Instant::now() } } #[cfg(test)] mod tests { use fuel_tx::UtxoId; use fuel_types::{Bytes32, Nonce}; use super::*; fn get_items() -> (CoinTypeId, CoinTypeId) { let utxo_id = UtxoId::new(Bytes32::from([1u8; 32]), 0); let nonce = Nonce::new([2u8; 32]); (CoinTypeId::UtxoId(utxo_id), CoinTypeId::Nonce(nonce)) } #[test] fn test_insert_and_get_active() { let mut cache = CoinsCache::new(Duration::from_secs(60)); let key: CoinCacheKey = Default::default(); let (item1, item2) = get_items(); let items = HashMap::from([(key, vec![item1.clone(), item2.clone()])]); cache.insert_multiple(items); let active_coins = cache.get_active(&key); assert_eq!(active_coins.len(), 2); assert!(active_coins.contains(&item1)); assert!(active_coins.contains(&item2)); } #[tokio::test] async fn test_insert_and_expire_items() { let mut cache = CoinsCache::new(Duration::from_secs(10)); let key = CoinCacheKey::default(); let (item1, _) = get_items(); let items = HashMap::from([(key, vec![item1.clone()])]); cache.insert_multiple(items); // Advance time by more than the cache's TTL tokio::time::pause(); tokio::time::advance(Duration::from_secs(12)).await; let (_, item2) = get_items(); let items = HashMap::from([(key, vec![item2.clone()])]); cache.insert_multiple(items); let active_coins = cache.get_active(&key); assert_eq!(active_coins.len(), 1); assert!(!active_coins.contains(&item1)); assert!(active_coins.contains(&item2)); } #[test] fn test_get_active_no_items() { let mut cache = CoinsCache::new(Duration::from_secs(60)); let key = Default::default(); let active_coins = cache.get_active(&key); assert!(active_coins.is_empty()); } #[test] fn test_remove_items() { let mut cache = CoinsCache::new(Duration::from_secs(60)); let key: CoinCacheKey = Default::default(); let (item1, item2) = get_items(); let items_to_insert = [(key, vec![item1.clone(), item2.clone()])]; cache.insert_multiple(items_to_insert.iter().cloned()); let items_to_remove = [(key, vec![item1.clone()])]; cache.remove_items(items_to_remove.iter().cloned()); let active_coins = cache.get_active(&key); assert_eq!(active_coins.len(), 1); assert!(!active_coins.contains(&item1)); assert!(active_coins.contains(&item2)); } } ================================================ FILE: packages/fuels-accounts/src/keystore.rs ================================================ use std::path::{Path, PathBuf}; use fuel_crypto::SecretKey; use fuels_core::{error, types::errors::Result}; use rand::{CryptoRng, Rng}; use zeroize::{Zeroize, ZeroizeOnDrop}; #[derive(Debug, Clone, Zeroize, ZeroizeOnDrop)] pub struct KeySaved { key: SecretKey, #[zeroize(skip)] uuid: String, } impl KeySaved { pub fn key(&self) -> &SecretKey { &self.key } pub fn uuid(&self) -> &str { &self.uuid } } /// A Keystore encapsulates operations for key management such as creation, loading, /// and saving of keys into a specified directory. pub struct Keystore { dir: PathBuf, } impl Keystore { /// Creates a new Keystore instance with the provided directory. pub fn new>(dir: P) -> Self { Self { dir: dir.as_ref().to_path_buf(), } } /// Loads and decrypts a key from the keystore using the given UUID and password. pub fn load_key(&self, uuid: &str, password: S) -> Result where S: AsRef<[u8]>, { let key_path = self.dir.join(uuid); let secret = eth_keystore::decrypt_key(key_path, password).map_err(|e| error!(Other, "{e}"))?; let secret_key = SecretKey::try_from(secret.as_slice()) .expect("Decrypted key should have a correct size"); Ok(secret_key) } /// Encrypts the provided key with the given password and saves it to the keystore. /// Returns the generated UUID for the stored key. pub fn save_key(&self, key: SecretKey, password: S, mut rng: R) -> Result where R: Rng + CryptoRng, S: AsRef<[u8]>, { // Note: `*key` is used if SecretKey implements Deref to an inner type. eth_keystore::encrypt_key(&self.dir, &mut rng, *key, password, None) .map_err(|e| error!(Other, "{e}")) } } #[cfg(test)] mod tests { use rand::thread_rng; use tempfile::tempdir; use super::*; use crate::signers::private_key::PrivateKeySigner; #[tokio::test] async fn wallet_from_mnemonic_phrase() -> Result<()> { let phrase = "oblige salon price punch saddle immune slogan rare snap desert retire surprise"; // Create first key from mnemonic phrase. let key = SecretKey::new_from_mnemonic_phrase_with_path(phrase, "m/44'/60'/0'/0/0")?; let signer = PrivateKeySigner::new(key); let expected_address = "df9d0e6c6c5f5da6e82e5e1a77974af6642bdb450a10c43f0c6910a212600185"; assert_eq!(signer.address().to_string(), expected_address); // Create a second key from the same phrase. let key = SecretKey::new_from_mnemonic_phrase_with_path(phrase, "m/44'/60'/1'/0/0")?; let signer2 = PrivateKeySigner::new(key); let expected_second_address = "261191b0164a24fd0fd51566ec5e5b0b9ba8fb2d42dc9cf7dbbd6f23d2742759"; assert_eq!(signer2.address().to_string(), expected_second_address); Ok(()) } #[tokio::test] async fn encrypt_and_store_keys_from_mnemonic() -> Result<()> { let dir = tempdir()?; let keystore = Keystore::new(dir.path()); let phrase = "oblige salon price punch saddle immune slogan rare snap desert retire surprise"; // Create a key from the mnemonic phrase. let key = SecretKey::new_from_mnemonic_phrase_with_path(phrase, "m/44'/60'/0'/0/0")?; let uuid = keystore.save_key(key, "password", thread_rng())?; let recovered_key = keystore.load_key(&uuid, "password")?; assert_eq!(key, recovered_key); // Remove the keystore file. let key_path = keystore.dir.join(&uuid); assert!(std::fs::remove_file(key_path).is_ok()); Ok(()) } } ================================================ FILE: packages/fuels-accounts/src/lib.rs ================================================ #[cfg(feature = "std")] mod account; #[cfg(feature = "std")] mod accounts_utils; #[cfg(all(feature = "std", feature = "keystore"))] pub mod keystore; #[cfg(feature = "std")] pub mod provider; #[cfg(feature = "std")] pub mod wallet; #[cfg(feature = "std")] pub use account::*; #[cfg(feature = "coin-cache")] mod coin_cache; pub mod predicate; pub mod signers; #[cfg(test)] mod test { #[test] fn sdl_is_the_same_as_from_fuel() { let file_sdl = include_str!("./schema/schema.sdl"); let core_sdl = String::from_utf8(fuel_core_client::SCHEMA_SDL.to_vec()).unwrap(); assert_eq!(file_sdl, &core_sdl); } } ================================================ FILE: packages/fuels-accounts/src/predicate.rs ================================================ use std::{fmt::Debug, fs}; #[cfg(feature = "std")] use fuels_core::types::{AssetId, coin_type_id::CoinTypeId, input::Input}; use fuels_core::{ Configurables, error, types::{Address, errors::Result}, }; #[cfg(feature = "std")] use crate::accounts_utils::try_provider_error; #[cfg(feature = "std")] use crate::{Account, ViewOnlyAccount, provider::Provider}; #[derive(Debug, Clone)] pub struct Predicate { address: Address, code: Vec, data: Vec, #[cfg(feature = "std")] provider: Option, } impl Predicate { pub fn address(&self) -> Address { self.address } pub fn code(&self) -> &[u8] { &self.code } pub fn data(&self) -> &[u8] { &self.data } pub fn calculate_address(code: &[u8]) -> Address { fuel_tx::Input::predicate_owner(code) } pub fn load_from(file_path: &str) -> Result { let code = fs::read(file_path).map_err(|e| { error!( IO, "could not read predicate binary {file_path:?}. Reason: {e}" ) })?; Ok(Self::from_code(code)) } pub fn from_code(code: Vec) -> Self { Self { address: Self::calculate_address(&code), code, data: Default::default(), #[cfg(feature = "std")] provider: None, } } pub fn with_data(mut self, data: Vec) -> Self { self.data = data; self } pub fn with_code(self, code: Vec) -> Self { let address = Self::calculate_address(&code); Self { code, address, ..self } } pub fn with_configurables(mut self, configurables: impl Into) -> Self { let configurables: Configurables = configurables.into(); configurables.update_constants_in(&mut self.code); let address = Self::calculate_address(&self.code); self.address = address; self } } #[cfg(feature = "std")] impl Predicate { pub fn provider(&self) -> Option<&Provider> { self.provider.as_ref() } pub fn set_provider(&mut self, provider: Provider) { self.provider = Some(provider); } pub fn with_provider(self, provider: Provider) -> Self { Self { provider: Some(provider), ..self } } } #[cfg(feature = "std")] #[cfg_attr(not(target_arch = "wasm32"), async_trait::async_trait)] impl ViewOnlyAccount for Predicate { fn address(&self) -> Address { self.address() } fn try_provider(&self) -> Result<&Provider> { self.provider.as_ref().ok_or_else(try_provider_error) } async fn get_asset_inputs_for_amount( &self, asset_id: AssetId, amount: u128, excluded_coins: Option>, ) -> Result> { Ok(self .get_spendable_resources(asset_id, amount, excluded_coins) .await? .into_iter() .map(|resource| { Input::resource_predicate(resource, self.code.clone(), self.data.clone()) }) .collect::>()) } } #[cfg(feature = "std")] impl Account for Predicate {} ================================================ FILE: packages/fuels-accounts/src/provider/cache.rs ================================================ use std::{sync::Arc, time::Duration}; use async_trait::async_trait; use chrono::{DateTime, Utc}; use fuel_core_client::client::types::NodeInfo; use fuel_tx::ConsensusParameters; use fuels_core::types::errors::Result; use tokio::sync::RwLock; #[cfg_attr(test, mockall::automock)] #[async_trait] pub trait CacheableRpcs { async fn consensus_parameters(&self) -> Result; async fn node_info(&self) -> Result; } trait Clock { fn now(&self) -> DateTime; } #[derive(Debug, Clone)] pub struct TtlConfig { pub consensus_parameters: Duration, } impl Default for TtlConfig { fn default() -> Self { TtlConfig { consensus_parameters: Duration::from_secs(60), } } } #[derive(Debug, Clone)] struct Dated { value: T, date: DateTime, } impl Dated { fn is_stale(&self, now: DateTime, ttl: Duration) -> bool { self.date + ttl < now } } #[derive(Debug, Clone, Copy)] pub struct SystemClock; impl Clock for SystemClock { fn now(&self) -> DateTime { Utc::now() } } #[derive(Debug, Clone)] pub struct CachedClient { client: Client, ttl_config: TtlConfig, cached_consensus_params: Arc>>>, cached_node_info: Arc>>>, clock: Clock, } impl CachedClient { pub fn new(client: Client, ttl: TtlConfig, clock: Clock) -> Self { Self { client, ttl_config: ttl, cached_consensus_params: Default::default(), cached_node_info: Default::default(), clock, } } pub fn set_ttl(&mut self, ttl: TtlConfig) { self.ttl_config = ttl } pub fn inner(&self) -> &Client { &self.client } pub fn inner_mut(&mut self) -> &mut Client { &mut self.client } } impl CachedClient where Client: CacheableRpcs, { pub async fn clear(&self) { *self.cached_consensus_params.write().await = None; } } #[async_trait] impl CacheableRpcs for CachedClient where Clk: Clock + Send + Sync, Client: CacheableRpcs + Send + Sync, { async fn consensus_parameters(&self) -> Result { { let read_lock = self.cached_consensus_params.read().await; if let Some(entry) = read_lock.as_ref() && !entry.is_stale(self.clock.now(), self.ttl_config.consensus_parameters) { return Ok(entry.value.clone()); } } let mut write_lock = self.cached_consensus_params.write().await; // because it could have been updated since we last checked if let Some(entry) = write_lock.as_ref() && !entry.is_stale(self.clock.now(), self.ttl_config.consensus_parameters) { return Ok(entry.value.clone()); } let fresh_parameters = self.client.consensus_parameters().await?; *write_lock = Some(Dated { value: fresh_parameters.clone(), date: self.clock.now(), }); Ok(fresh_parameters) } async fn node_info(&self) -> Result { // must borrow from consensus_parameters to keep the change non-breaking let ttl = self.ttl_config.consensus_parameters; { let read_lock = self.cached_node_info.read().await; if let Some(entry) = read_lock.as_ref() && !entry.is_stale(self.clock.now(), ttl) { return Ok(entry.value.clone()); } } let mut write_lock = self.cached_node_info.write().await; // because it could have been updated since we last checked if let Some(entry) = write_lock.as_ref() && !entry.is_stale(self.clock.now(), ttl) { return Ok(entry.value.clone()); } let fresh_node_info = self.client.node_info().await?; *write_lock = Some(Dated { value: fresh_node_info.clone(), date: self.clock.now(), }); Ok(fresh_node_info) } } #[cfg(test)] mod tests { use std::sync::Mutex; use fuel_core_client::client::schema::{ U64, node_info::{IndexationFlags, TxPoolStats}, }; use fuel_types::ChainId; use super::*; #[derive(Clone, Default)] struct TestClock { time: Arc>>, } impl TestClock { fn update_time(&self, time: DateTime) { *self.time.lock().unwrap() = time; } } impl Clock for TestClock { fn now(&self) -> DateTime { *self.time.lock().unwrap() } } #[tokio::test] async fn initial_call_to_consensus_params_fwd_to_api() { // given let mut api = MockCacheableRpcs::new(); api.expect_consensus_parameters() .once() .return_once(|| Ok(ConsensusParameters::default())); let sut = CachedClient::new(api, TtlConfig::default(), TestClock::default()); // when let _consensus_params = sut.consensus_parameters().await.unwrap(); // then // mock validates the call went through } #[tokio::test] async fn new_call_to_consensus_params_cached() { // given let mut api = MockCacheableRpcs::new(); api.expect_consensus_parameters() .once() .return_once(|| Ok(ConsensusParameters::default())); let sut = CachedClient::new( api, TtlConfig { consensus_parameters: Duration::from_secs(10), }, TestClock::default(), ); let consensus_parameters = sut.consensus_parameters().await.unwrap(); // when let second_call_consensus_params = sut.consensus_parameters().await.unwrap(); // then // mock validates only one call assert_eq!(consensus_parameters, second_call_consensus_params); } #[tokio::test] async fn if_ttl_expired_cache_is_updated() { // given let original_consensus_params = ConsensusParameters::default(); let changed_consensus_params = { let mut params = original_consensus_params.clone(); params.set_chain_id(ChainId::new(99)); params }; let api = { let mut api = MockCacheableRpcs::new(); let original_consensus_params = original_consensus_params.clone(); let changed_consensus_params = changed_consensus_params.clone(); api.expect_consensus_parameters() .once() .return_once(move || Ok(original_consensus_params)); api.expect_consensus_parameters() .once() .return_once(move || Ok(changed_consensus_params)); api }; let clock = TestClock::default(); let start_time = clock.now(); let sut = CachedClient::new( api, TtlConfig { consensus_parameters: Duration::from_secs(10), }, clock.clone(), ); let consensus_parameters = sut.consensus_parameters().await.unwrap(); clock.update_time(start_time + Duration::from_secs(11)); // when let second_call_consensus_params = sut.consensus_parameters().await.unwrap(); // then // mock validates two calls made assert_eq!(consensus_parameters, original_consensus_params); assert_eq!(second_call_consensus_params, changed_consensus_params); } #[tokio::test] async fn clear_cache_clears_consensus_params_cache() { // given let first_params = ConsensusParameters::default(); let second_params = { let mut params = ConsensusParameters::default(); params.set_chain_id(ChainId::new(1234)); params }; let api = { let mut api = MockCacheableRpcs::new(); let first_clone = first_params.clone(); api.expect_consensus_parameters() .times(1) .return_once(move || Ok(first_clone)); let second_clone = second_params.clone(); api.expect_consensus_parameters() .times(1) .return_once(move || Ok(second_clone)); api }; let clock = TestClock::default(); let sut = CachedClient::new(api, TtlConfig::default(), clock.clone()); let result1 = sut.consensus_parameters().await.unwrap(); // when sut.clear().await; // then let result2 = sut.consensus_parameters().await.unwrap(); assert_eq!(result1, first_params); assert_eq!(result2, second_params); } fn dummy_node_info() -> NodeInfo { NodeInfo { utxo_validation: true, vm_backtrace: false, max_tx: u64::MAX, max_gas: u64::MAX, max_size: u64::MAX, max_depth: u64::MAX, node_version: "0.0.1".to_string(), indexation: IndexationFlags { balances: true, coins_to_spend: true, asset_metadata: true, }, tx_pool_stats: TxPoolStats { tx_count: U64(1), total_gas: U64(1), total_size: U64(1), }, } } #[tokio::test] async fn initial_call_to_node_info_fwd_to_api() { // given let mut api = MockCacheableRpcs::new(); api.expect_node_info() .once() .return_once(|| Ok(dummy_node_info())); let sut = CachedClient::new(api, TtlConfig::default(), TestClock::default()); // when let _node_info = sut.node_info().await.unwrap(); // then // The mock verifies that the API call was made. } #[tokio::test] async fn new_call_to_node_info_cached() { // given let mut api = MockCacheableRpcs::new(); api.expect_node_info() .once() .return_once(|| Ok(dummy_node_info())); let sut = CachedClient::new( api, TtlConfig { consensus_parameters: Duration::from_secs(10), }, TestClock::default(), ); let first_node_info = sut.node_info().await.unwrap(); // when: second call should return the cached value let second_node_info = sut.node_info().await.unwrap(); // then: only one API call should have been made and the values are equal assert_eq!(first_node_info, second_node_info); } #[tokio::test] async fn if_ttl_expired_node_info_cache_is_updated() { // given let original_node_info = dummy_node_info(); let changed_node_info = NodeInfo { node_version: "changed".to_string(), ..dummy_node_info() }; let api = { let mut api = MockCacheableRpcs::new(); let original_clone = original_node_info.clone(); api.expect_node_info() .times(1) .return_once(move || Ok(original_clone)); let changed_clone = changed_node_info.clone(); api.expect_node_info() .times(1) .return_once(move || Ok(changed_clone)); api }; let clock = TestClock::default(); let start_time = clock.now(); let sut = CachedClient::new( api, TtlConfig { consensus_parameters: Duration::from_secs(10), }, clock.clone(), ); let first_call = sut.node_info().await.unwrap(); // Advance time past the TTL. clock.update_time(start_time + Duration::from_secs(11)); // when: a new API call should be triggered because the TTL expired let second_call = sut.node_info().await.unwrap(); // then assert_eq!(first_call, original_node_info); assert_eq!(second_call, changed_node_info); } } ================================================ FILE: packages/fuels-accounts/src/provider/retry_util.rs ================================================ use std::{fmt::Debug, future::Future, num::NonZeroU32, time::Duration}; use fuels_core::types::errors::{Result, error}; /// A set of strategies to control retry intervals between attempts. /// /// The `Backoff` enum defines different strategies for managing intervals between retry attempts. /// Each strategy allows you to customize the waiting time before a new attempt based on the /// number of attempts made. /// /// # Variants /// /// - `Linear(Duration)`: Increases the waiting time linearly with each attempt. /// - `Exponential(Duration)`: Doubles the waiting time with each attempt. /// - `Fixed(Duration)`: Uses a constant waiting time between attempts. /// /// # Examples /// /// ```rust /// use std::time::Duration; /// use fuels_accounts::provider::Backoff; /// /// let linear_backoff = Backoff::Linear(Duration::from_secs(2)); /// let exponential_backoff = Backoff::Exponential(Duration::from_secs(1)); /// let fixed_backoff = Backoff::Fixed(Duration::from_secs(5)); /// ``` //ANCHOR: backoff #[derive(Debug, Clone)] pub enum Backoff { Linear(Duration), Exponential(Duration), Fixed(Duration), } //ANCHOR_END: backoff impl Default for Backoff { fn default() -> Self { Backoff::Linear(Duration::from_millis(10)) } } impl Backoff { pub fn wait_duration(&self, attempt: u32) -> Duration { match self { Backoff::Linear(base_duration) => *base_duration * (attempt + 1), Backoff::Exponential(base_duration) => *base_duration * 2u32.pow(attempt), Backoff::Fixed(interval) => *interval, } } } /// Configuration for controlling retry behavior. /// /// The `RetryConfig` struct encapsulates the configuration parameters for controlling the retry behavior /// of asynchronous actions. It includes the maximum number of attempts and the interval strategy from /// the `Backoff` enum that determines how much time to wait between retry attempts. /// /// # Fields /// /// - `max_attempts`: The maximum number of attempts before giving up. /// - `interval`: The chosen interval strategy from the `Backoff` enum. /// /// # Examples /// /// ```rust /// use std::num::NonZeroUsize; /// use std::time::Duration; /// use fuels_accounts::provider::{Backoff, RetryConfig}; /// /// let max_attempts = 5; /// let interval_strategy = Backoff::Exponential(Duration::from_secs(1)); /// /// let retry_config = RetryConfig::new(max_attempts, interval_strategy).unwrap(); /// ``` // ANCHOR: retry_config #[derive(Clone, Debug)] pub struct RetryConfig { max_attempts: NonZeroU32, interval: Backoff, } // ANCHOR_END: retry_config impl RetryConfig { pub fn new(max_attempts: u32, interval: Backoff) -> Result { let max_attempts = NonZeroU32::new(max_attempts) .ok_or_else(|| error!(Other, "`max_attempts` must be greater than `0`"))?; Ok(RetryConfig { max_attempts, interval, }) } } impl Default for RetryConfig { fn default() -> Self { Self { max_attempts: NonZeroU32::new(1).expect("should not fail"), interval: Default::default(), } } } /// Retries an asynchronous action with customizable retry behavior. /// /// This function takes an asynchronous action represented by a closure `action`. /// The action is executed repeatedly with backoff and retry logic based on the /// provided `retry_config` and the `should_retry` condition. /// /// The `action` closure should return a `Future` that resolves to a `Result`, /// where `T` represents the success type and `K` represents the error type. /// /// # Parameters /// /// - `action`: The asynchronous action to be retried. /// - `retry_config`: A reference to the retry configuration. /// - `should_retry`: A closure that determines whether to retry based on the result. /// /// # Return /// /// Returns `Ok(T)` if the action succeeds without requiring further retries. /// Returns `Err(Error)` if the maximum number of attempts is reached and the action /// still fails. If a retryable error occurs during the attempts, the error will /// be returned if the `should_retry` condition allows further retries. pub(crate) async fn retry( mut action: impl FnMut() -> Fut, retry_config: &RetryConfig, should_retry: ShouldRetry, ) -> T where Fut: Future, ShouldRetry: Fn(&T) -> bool, { let mut last_result = None; for attempt in 0..retry_config.max_attempts.into() { let result = action().await; if should_retry(&result) { last_result = Some(result) } else { return result; } tokio::time::sleep(retry_config.interval.wait_duration(attempt)).await; } last_result.expect("should not happen") } #[cfg(test)] mod tests { mod retry_until { use std::time::{Duration, Instant}; use fuels_core::types::errors::{Result, error}; use tokio::sync::Mutex; use crate::provider::{Backoff, RetryConfig, retry_util}; #[tokio::test] async fn returns_last_received_response() -> Result<()> { // given let err_msgs = ["err1", "err2", "err3"]; let number_of_attempts = Mutex::new(0usize); let will_always_fail = || async { let msg = err_msgs[*number_of_attempts.lock().await]; *number_of_attempts.lock().await += 1; msg }; let should_retry_fn = |_res: &_| -> bool { true }; let retry_options = RetryConfig::new(3, Backoff::Linear(Duration::from_millis(10)))?; // when let response = retry_util::retry(will_always_fail, &retry_options, should_retry_fn).await; // then assert_eq!(response, "err3"); Ok(()) } #[tokio::test] async fn stops_retrying_when_predicate_is_satisfied() -> Result<()> { // given let values = Mutex::new(vec![1, 2, 3]); let will_always_fail = || async { values.lock().await.pop().unwrap() }; let should_retry_fn = |res: &i32| *res != 2; let retry_options = RetryConfig::new(3, Backoff::Linear(Duration::from_millis(10)))?; // when let response = retry_util::retry(will_always_fail, &retry_options, should_retry_fn).await; // then assert_eq!(response, 2); Ok(()) } #[tokio::test] async fn retry_respects_delay_between_attempts_fixed() -> Result<()> { // given let timestamps: Mutex> = Mutex::new(vec![]); let will_fail_and_record_timestamp = || async { timestamps.lock().await.push(Instant::now()); Result::<()>::Err(error!(Other, "error")) }; let should_retry_fn = |_res: &_| -> bool { true }; let retry_options = RetryConfig::new(3, Backoff::Fixed(Duration::from_millis(100)))?; // when let _ = retry_util::retry( will_fail_and_record_timestamp, &retry_options, should_retry_fn, ) .await; // then let timestamps_vec = timestamps.lock().await.clone(); let timestamps_spaced_out_at_least_100_mills = timestamps_vec .iter() .zip(timestamps_vec.iter().skip(1)) .all(|(current_timestamp, the_next_timestamp)| { the_next_timestamp.duration_since(*current_timestamp) >= Duration::from_millis(100) }); assert!( timestamps_spaced_out_at_least_100_mills, "retry did not wait for the specified time between attempts" ); Ok(()) } #[tokio::test] async fn retry_respects_delay_between_attempts_linear() -> Result<()> { // given let timestamps: Mutex> = Mutex::new(vec![]); let will_fail_and_record_timestamp = || async { timestamps.lock().await.push(Instant::now()); Result::<()>::Err(error!(Other, "error")) }; let should_retry_fn = |_res: &_| -> bool { true }; let retry_options = RetryConfig::new(3, Backoff::Linear(Duration::from_millis(100)))?; // when let _ = retry_util::retry( will_fail_and_record_timestamp, &retry_options, should_retry_fn, ) .await; // then let timestamps_vec = timestamps.lock().await.clone(); let timestamps_spaced_out_at_least_100_mills = timestamps_vec .iter() .zip(timestamps_vec.iter().skip(1)) .enumerate() .all(|(attempt, (current_timestamp, the_next_timestamp))| { the_next_timestamp.duration_since(*current_timestamp) >= (Duration::from_millis(100) * (attempt + 1) as u32) }); assert!( timestamps_spaced_out_at_least_100_mills, "retry did not wait for the specified time between attempts" ); Ok(()) } #[tokio::test] async fn retry_respects_delay_between_attempts_exponential() -> Result<()> { // given let timestamps: Mutex> = Mutex::new(vec![]); let will_fail_and_record_timestamp = || async { timestamps.lock().await.push(Instant::now()); Result::<()>::Err(error!(Other, "error")) }; let should_retry_fn = |_res: &_| -> bool { true }; let retry_options = RetryConfig::new(3, Backoff::Exponential(Duration::from_millis(100)))?; // when let _ = retry_util::retry( will_fail_and_record_timestamp, &retry_options, should_retry_fn, ) .await; // then let timestamps_vec = timestamps.lock().await.clone(); let timestamps_spaced_out_at_least_100_mills = timestamps_vec .iter() .zip(timestamps_vec.iter().skip(1)) .enumerate() .all(|(attempt, (current_timestamp, the_next_timestamp))| { the_next_timestamp.duration_since(*current_timestamp) >= (Duration::from_millis(100) * (2_usize.pow((attempt) as u32)) as u32) }); assert!( timestamps_spaced_out_at_least_100_mills, "retry did not wait for the specified time between attempts" ); Ok(()) } } } ================================================ FILE: packages/fuels-accounts/src/provider/retryable_client.rs ================================================ use std::{future::Future, io}; use async_trait::async_trait; use custom_queries::{ContractExistsQuery, IsUserAccountQuery, IsUserAccountVariables}; use cynic::QueryBuilder; use fuel_core_client::client::{ FuelClient, pagination::{PaginatedResult, PaginationRequest}, schema::contract::ContractByIdArgs, types::{ Balance, Blob, Block, ChainInfo, Coin, CoinType, ContractBalance, Message, MessageProof, NodeInfo, TransactionResponse, TransactionStatus, gas_price::{EstimateGasPrice, LatestGasPrice}, primitives::{BlockId, TransactionId}, }, }; use fuel_core_types::services::executor::TransactionExecutionStatus; use fuel_tx::{BlobId, ConsensusParameters, Transaction, TxId, UtxoId}; use fuel_types::{Address, AssetId, BlockHeight, ContractId, Nonce}; use fuels_core::types::errors::{Error, Result, error}; use futures::Stream; use super::{ cache::CacheableRpcs, supported_versions::{self, VersionCompatibility}, }; use crate::provider::{RetryConfig, retry_util}; #[derive(Debug, thiserror::Error)] pub(crate) enum RequestError { #[error("io error: {0}")] IO(String), } type RequestResult = std::result::Result; impl From for Error { fn from(e: RequestError) -> Self { Error::Provider(e.to_string()) } } #[derive(Debug, Clone)] pub(crate) struct RetryableClient { client: FuelClient, url: String, retry_config: RetryConfig, prepend_warning: Option, } #[async_trait] impl CacheableRpcs for RetryableClient { async fn consensus_parameters(&self) -> Result { Ok(self.chain_info().await?.consensus_parameters) } async fn node_info(&self) -> Result { Ok(self.node_info().await?) } } impl RetryableClient { pub(crate) async fn connect(url: impl AsRef, retry_config: RetryConfig) -> Result { let url = url.as_ref().to_string(); let client = FuelClient::new(&url).map_err(|e| error!(Provider, "{e}"))?; let node_info = client.node_info().await?; let warning = Self::version_compatibility_warning(&node_info)?; Ok(Self { client, retry_config, url, prepend_warning: warning, }) } fn version_compatibility_warning(node_info: &NodeInfo) -> Result> { let node_version = node_info .node_version .parse::() .map_err(|e| error!(Provider, "could not parse Fuel client version: {}", e))?; let VersionCompatibility { supported_version, is_major_supported, is_minor_supported, .. } = supported_versions::compare_node_compatibility(node_version.clone()); let msg = if !is_major_supported || !is_minor_supported { Some(format!( "warning: the fuel node version to which this provider is connected has a semver incompatible version from the one the SDK was developed against. Connected node version: {node_version}, supported version: {supported_version}", )) } else { None }; Ok(msg) } pub(crate) fn url(&self) -> &str { &self.url } pub fn client(&self) -> &FuelClient { &self.client } pub(crate) fn set_retry_config(&mut self, retry_config: RetryConfig) { self.retry_config = retry_config; } async fn wrap(&self, action: impl Fn() -> Fut) -> RequestResult where Fut: Future>, { retry_util::retry(action, &self.retry_config, |result| result.is_err()) .await .map_err(|e| { let msg = if let Some(warning) = &self.prepend_warning { format!("{warning}. {e}") } else { e.to_string() }; RequestError::IO(msg) }) } // DELEGATION START pub async fn health(&self) -> RequestResult { self.wrap(|| self.client.health()).await } pub async fn transaction(&self, id: &TxId) -> RequestResult> { self.wrap(|| self.client.transaction(id)).await } pub(crate) async fn chain_info(&self) -> RequestResult { self.wrap(|| self.client.chain_info()).await } pub async fn await_transaction_commit(&self, id: &TxId) -> RequestResult { self.wrap(|| self.client.await_transaction_commit(id)).await } pub async fn submit_and_await_commit( &self, tx: &Transaction, ) -> RequestResult { self.wrap(|| self.client.submit_and_await_commit(tx)).await } pub async fn submit_and_await_status<'a>( &'a self, tx: &'a Transaction, include_preconfirmation: bool, ) -> RequestResult> + 'a> { self.wrap(|| { self.client .submit_and_await_status_opt(tx, None, Some(include_preconfirmation)) }) .await } pub async fn subscribe_transaction_status<'a>( &'a self, id: &'a TxId, include_preconfirmation: bool, ) -> RequestResult> + 'a> { self.wrap(|| { self.client .subscribe_transaction_status_opt(id, Some(include_preconfirmation)) }) .await } pub async fn submit(&self, tx: &Transaction) -> RequestResult { self.wrap(|| self.client.submit(tx)).await } pub async fn transaction_status(&self, id: &TxId) -> RequestResult { self.wrap(|| self.client.transaction_status(id)).await } pub async fn node_info(&self) -> RequestResult { self.wrap(|| self.client.node_info()).await } pub async fn blob(&self, blob_id: BlobId) -> RequestResult> { self.wrap(|| self.client.blob(blob_id)).await } pub async fn blob_exists(&self, blob_id: BlobId) -> RequestResult { self.wrap(|| self.client.blob_exists(blob_id)).await } pub async fn latest_gas_price(&self) -> RequestResult { self.wrap(|| self.client.latest_gas_price()).await } pub async fn estimate_gas_price(&self, block_horizon: u32) -> RequestResult { self.wrap(|| self.client.estimate_gas_price(block_horizon)) .await .map(Into::into) } pub async fn estimate_predicates(&self, tx: &Transaction) -> RequestResult { self.wrap(|| async { let mut new_tx = tx.clone(); self.client.estimate_predicates(&mut new_tx).await?; Ok(new_tx) }) .await } pub async fn dry_run( &self, tx: &[Transaction], ) -> RequestResult> { self.wrap(|| self.client.dry_run(tx)).await } pub async fn dry_run_opt( &self, tx: &[Transaction], utxo_validation: Option, gas_price: Option, at_height: Option, ) -> RequestResult> { self.wrap(|| { self.client .dry_run_opt(tx, utxo_validation, gas_price, at_height) }) .await } pub async fn coins( &self, owner: &Address, asset_id: Option<&AssetId>, request: PaginationRequest, ) -> RequestResult> { self.wrap(move || self.client.coins(owner, asset_id, request.clone())) .await } pub async fn coins_to_spend( &self, owner: &Address, spend_query: Vec<(AssetId, u128, Option)>, excluded_ids: Option<(Vec, Vec)>, ) -> RequestResult>> { self.wrap(move || { self.client .coins_to_spend(owner, spend_query.clone(), excluded_ids.clone()) }) .await } pub async fn balance( &self, owner: &Address, asset_id: Option<&AssetId>, ) -> RequestResult { self.wrap(|| self.client.balance(owner, asset_id)).await } pub async fn contract_balance( &self, id: &ContractId, asset: Option<&AssetId>, ) -> RequestResult { self.wrap(|| self.client.contract_balance(id, asset)).await } pub async fn contract_balances( &self, contract: &ContractId, request: PaginationRequest, ) -> RequestResult> { self.wrap(|| self.client.contract_balances(contract, request.clone())) .await } pub async fn balances( &self, owner: &Address, request: PaginationRequest, ) -> RequestResult> { self.wrap(|| self.client.balances(owner, request.clone())) .await } pub async fn transactions( &self, request: PaginationRequest, ) -> RequestResult> { self.wrap(|| self.client.transactions(request.clone())) .await } pub async fn transactions_by_owner( &self, owner: &Address, request: PaginationRequest, ) -> RequestResult> { self.wrap(|| self.client.transactions_by_owner(owner, request.clone())) .await } pub async fn produce_blocks( &self, blocks_to_produce: u32, start_timestamp: Option, ) -> RequestResult { self.wrap(|| { self.client .produce_blocks(blocks_to_produce, start_timestamp) }) .await } pub async fn block(&self, id: &BlockId) -> RequestResult> { self.wrap(|| self.client.block(id)).await } pub async fn block_by_height(&self, height: BlockHeight) -> RequestResult> { self.wrap(|| self.client.block_by_height(height)).await } pub async fn blocks( &self, request: PaginationRequest, ) -> RequestResult> { self.wrap(|| self.client.blocks(request.clone())).await } pub async fn messages( &self, owner: Option<&Address>, request: PaginationRequest, ) -> RequestResult> { self.wrap(|| self.client.messages(owner, request.clone())) .await } /// Request a merkle proof of an output message. pub async fn message_proof( &self, transaction_id: &TxId, nonce: &Nonce, commit_block_id: Option<&BlockId>, commit_block_height: Option, ) -> RequestResult { self.wrap(|| { self.client .message_proof(transaction_id, nonce, commit_block_id, commit_block_height) }) .await } pub async fn contract_exists(&self, contract_id: &ContractId) -> RequestResult { self.wrap(|| { let query = ContractExistsQuery::build(ContractByIdArgs { id: (*contract_id).into(), }); self.client.query(query) }) .await .map(|query| { query .contract .map(|contract| ContractId::from(contract.id) == *contract_id) .unwrap_or(false) }) } // DELEGATION END pub async fn is_user_account(&self, address: [u8; 32]) -> Result { let blob_id = BlobId::from(address); let contract_id = ContractId::from(address); let transaction_id = TransactionId::from(address); let query = IsUserAccountQuery::build(IsUserAccountVariables { blob_id: blob_id.into(), contract_id: contract_id.into(), transaction_id: transaction_id.into(), }); let response = self.client.query(query).await?; let is_resource = response.blob.is_some() || response.contract.is_some() || response.transaction.is_some(); Ok(!is_resource) } } mod custom_queries { use fuel_core_client::client::schema::{ BlobId, ContractId, TransactionId, blob::BlobIdFragment, contract::{ContractByIdArgsFields, ContractIdFragment}, schema, tx::TransactionIdFragment, }; #[derive(cynic::QueryVariables, Debug, Clone)] pub struct IsUserAccountVariables { pub blob_id: BlobId, pub contract_id: ContractId, pub transaction_id: TransactionId, } #[derive(cynic::QueryFragment, Debug)] #[cynic( graphql_type = "Query", variables = "IsUserAccountVariables", schema_path = "./src/schema/schema.sdl" )] pub struct IsUserAccountQuery { #[arguments(id: $blob_id)] pub blob: Option, #[arguments(id: $contract_id)] pub contract: Option, #[arguments(id: $transaction_id)] pub transaction: Option, } #[derive(cynic::QueryFragment, Clone, Debug)] #[cynic( schema_path = "./src/schema/schema.sdl", graphql_type = "Query", variables = "ContractByIdArgs" )] pub struct ContractExistsQuery { #[arguments(id: $id)] pub contract: Option, } } ================================================ FILE: packages/fuels-accounts/src/provider/supported_fuel_core_version.rs ================================================ pub const SUPPORTED_FUEL_CORE_VERSION: semver::Version = semver::Version::new(0, 47, 1); ================================================ FILE: packages/fuels-accounts/src/provider/supported_versions.rs ================================================ use semver::Version; use crate::provider::supported_fuel_core_version::SUPPORTED_FUEL_CORE_VERSION; #[derive(Debug, PartialEq, Eq)] pub(crate) struct VersionCompatibility { pub(crate) supported_version: Version, pub(crate) is_major_supported: bool, pub(crate) is_minor_supported: bool, pub(crate) is_patch_supported: bool, } pub(crate) fn compare_node_compatibility(network_version: Version) -> VersionCompatibility { check_version_compatibility(network_version, SUPPORTED_FUEL_CORE_VERSION) } fn check_version_compatibility( actual_version: Version, expected_version: Version, ) -> VersionCompatibility { let is_major_supported = expected_version.major == actual_version.major; let is_minor_supported = expected_version.minor == actual_version.minor; let is_patch_supported = expected_version.patch == actual_version.patch; VersionCompatibility { supported_version: expected_version, is_major_supported, is_minor_supported, is_patch_supported, } } #[cfg(test)] mod tests { use super::*; #[test] fn should_validate_all_possible_version_mismatches() { let expected_version = "0.1.2".parse::().unwrap(); assert_eq!( check_version_compatibility("1.1.2".parse().unwrap(), expected_version.clone()), VersionCompatibility { is_major_supported: false, is_minor_supported: true, is_patch_supported: true, supported_version: expected_version.clone() } ); assert_eq!( check_version_compatibility("1.2.2".parse().unwrap(), expected_version.clone()), VersionCompatibility { is_major_supported: false, is_minor_supported: false, is_patch_supported: true, supported_version: expected_version.clone() } ); assert_eq!( check_version_compatibility("1.1.3".parse().unwrap(), expected_version.clone()), VersionCompatibility { is_major_supported: false, is_minor_supported: true, is_patch_supported: false, supported_version: expected_version.clone() } ); assert_eq!( check_version_compatibility("0.2.2".parse().unwrap(), expected_version.clone()), VersionCompatibility { is_major_supported: true, is_minor_supported: false, is_patch_supported: true, supported_version: expected_version.clone() } ); assert_eq!( check_version_compatibility("0.2.3".parse().unwrap(), expected_version.clone()), VersionCompatibility { is_major_supported: true, is_minor_supported: false, is_patch_supported: false, supported_version: expected_version.clone() } ); assert_eq!( check_version_compatibility("0.1.3".parse().unwrap(), expected_version.clone()), VersionCompatibility { is_major_supported: true, is_minor_supported: true, is_patch_supported: false, supported_version: expected_version.clone() } ); assert_eq!( check_version_compatibility("0.1.2".parse().unwrap(), expected_version.clone()), VersionCompatibility { is_major_supported: true, is_minor_supported: true, is_patch_supported: true, supported_version: expected_version.clone() } ); } } ================================================ FILE: packages/fuels-accounts/src/provider.rs ================================================ #[cfg(feature = "coin-cache")] use std::sync::Arc; use std::{collections::HashMap, fmt::Debug, net::SocketAddr}; mod cache; mod retry_util; mod retryable_client; mod supported_fuel_core_version; mod supported_versions; pub use cache::TtlConfig; use cache::{CachedClient, SystemClock}; use chrono::{DateTime, Utc}; use fuel_core_client::client::{ FuelClient, pagination::{PageDirection, PaginatedResult, PaginationRequest}, types::{ balance::Balance, contract::ContractBalance, gas_price::{EstimateGasPrice, LatestGasPrice}, }, }; use fuel_core_types::services::executor::TransactionExecutionResult; use fuel_tx::{ AssetId, ConsensusParameters, Receipt, Transaction as FuelTransaction, TxId, UtxoId, }; #[cfg(feature = "coin-cache")] use fuels_core::types::coin_type_id::CoinTypeId; use fuels_core::{ constants::{DEFAULT_GAS_ESTIMATION_BLOCK_HORIZON, DEFAULT_GAS_ESTIMATION_TOLERANCE}, types::{ Address, BlockHeight, Bytes32, ContractId, DryRun, DryRunner, Nonce, block::{Block, Header}, chain_info::ChainInfo, coin::Coin, coin_type::CoinType, errors::Result, message::Message, message_proof::MessageProof, node_info::NodeInfo, transaction::{Transaction, Transactions}, transaction_builders::{Blob, BlobId}, transaction_response::TransactionResponse, tx_status::TxStatus, }, }; use futures::StreamExt; pub use retry_util::{Backoff, RetryConfig}; pub use supported_fuel_core_version::SUPPORTED_FUEL_CORE_VERSION; use tai64::Tai64; #[cfg(feature = "coin-cache")] use tokio::sync::Mutex; #[cfg(feature = "coin-cache")] use crate::coin_cache::CoinsCache; use crate::provider::{cache::CacheableRpcs, retryable_client::RetryableClient}; const NUM_RESULTS_PER_REQUEST: i32 = 100; #[derive(Debug, Clone, PartialEq)] // ANCHOR: transaction_cost pub struct TransactionCost { pub gas_price: u64, pub metered_bytes_size: u64, pub total_fee: u64, pub script_gas: u64, pub total_gas: u64, } // ANCHOR_END: transaction_cost pub(crate) struct ResourceQueries { utxos: Vec, messages: Vec, asset_id: Option, amount: u128, } impl ResourceQueries { pub fn exclusion_query(&self) -> Option<(Vec, Vec)> { if self.utxos.is_empty() && self.messages.is_empty() { return None; } Some((self.utxos.clone(), self.messages.clone())) } pub fn spend_query(&self, base_asset_id: AssetId) -> Vec<(AssetId, u128, Option)> { vec![(self.asset_id.unwrap_or(base_asset_id), self.amount, None)] } } #[derive(Default)] // ANCHOR: resource_filter pub struct ResourceFilter { pub from: Address, pub asset_id: Option, pub amount: u128, pub excluded_utxos: Vec, pub excluded_message_nonces: Vec, } // ANCHOR_END: resource_filter impl ResourceFilter { pub fn owner(&self) -> Address { self.from } pub(crate) fn resource_queries(&self) -> ResourceQueries { ResourceQueries { utxos: self.excluded_utxos.clone(), messages: self.excluded_message_nonces.clone(), asset_id: self.asset_id, amount: self.amount, } } } /// Encapsulates common client operations in the SDK. /// Note that you may also use `client`, which is an instance /// of `FuelClient`, directly, which provides a broader API. #[derive(Debug, Clone)] pub struct Provider { cached_client: CachedClient, #[cfg(feature = "coin-cache")] coins_cache: Arc>, } impl Provider { pub async fn from(addr: impl Into) -> Result { let addr = addr.into(); Self::connect(format!("http://{addr}")).await } /// Returns the underlying uncached client. pub fn client(&self) -> &FuelClient { self.cached_client.inner().client() } pub fn set_cache_ttl(&mut self, ttl: TtlConfig) { self.cached_client.set_ttl(ttl); } pub async fn clear_cache(&self) { self.cached_client.clear().await; } pub async fn healthy(&self) -> Result { Ok(self.uncached_client().health().await?) } /// Connects to an existing node at the given address. pub async fn connect(url: impl AsRef) -> Result { let client = CachedClient::new( RetryableClient::connect(&url, Default::default()).await?, TtlConfig::default(), SystemClock, ); Ok(Self { cached_client: client, #[cfg(feature = "coin-cache")] coins_cache: Default::default(), }) } pub fn url(&self) -> &str { self.uncached_client().url() } pub async fn blob(&self, blob_id: BlobId) -> Result> { Ok(self .uncached_client() .blob(blob_id.into()) .await? .map(|blob| Blob::new(blob.bytecode))) } pub async fn blob_exists(&self, blob_id: BlobId) -> Result { Ok(self.uncached_client().blob_exists(blob_id.into()).await?) } /// Sends a transaction to the underlying Provider's client. pub async fn send_transaction_and_await_commit( &self, tx: T, ) -> Result { #[cfg(feature = "coin-cache")] let base_asset_id = *self.consensus_parameters().await?.base_asset_id(); #[cfg(feature = "coin-cache")] self.check_inputs_already_in_cache(&tx.used_coins(&base_asset_id)) .await?; let tx = self.prepare_transaction_for_sending(tx).await?; let tx_status = self .uncached_client() .submit_and_await_commit(&tx.clone().into()) .await? .into(); #[cfg(feature = "coin-cache")] if matches!( tx_status, TxStatus::SqueezedOut { .. } | TxStatus::Failure { .. } ) { self.coins_cache .lock() .await .remove_items(tx.used_coins(&base_asset_id)) } Ok(tx_status) } /// Similar to `send_transaction_and_await_commit`, /// but collect all the status received until a final one and return them. pub async fn send_transaction_and_await_status( &self, tx: T, include_preconfirmation: bool, ) -> Result>> { #[cfg(feature = "coin-cache")] let base_asset_id = *self.consensus_parameters().await?.base_asset_id(); #[cfg(feature = "coin-cache")] let used_base_coins = tx.used_coins(&base_asset_id); #[cfg(feature = "coin-cache")] self.check_inputs_already_in_cache(&used_base_coins).await?; let tx = self.prepare_transaction_for_sending(tx).await?.into(); let mut stream = self .uncached_client() .submit_and_await_status(&tx, include_preconfirmation) .await?; let mut statuses = Vec::new(); // Process stream items until we get a final status while let Some(status) = stream.next().await { let tx_status = status.map(TxStatus::from).map_err(Into::into); let is_final = tx_status.as_ref().ok().is_some_and(|s| s.is_final()); statuses.push(tx_status); if is_final { break; } } // Handle cache updates for failures #[cfg(feature = "coin-cache")] if statuses.iter().any(|status| { matches!( &status, Ok(TxStatus::SqueezedOut { .. }) | Ok(TxStatus::Failure { .. }), ) }) { self.coins_cache.lock().await.remove_items(used_base_coins); } Ok(statuses) } async fn prepare_transaction_for_sending(&self, mut tx: T) -> Result { let consensus_parameters = self.consensus_parameters().await?; tx.precompute(&consensus_parameters.chain_id())?; let chain_info = self.chain_info().await?; let Header { height: latest_block_height, state_transition_bytecode_version: latest_chain_executor_version, .. } = chain_info.latest_block.header; if tx.is_using_predicates() { tx.estimate_predicates(self, Some(latest_chain_executor_version)) .await?; tx.clone() .validate_predicates(&consensus_parameters, latest_block_height)?; } Ok(tx) } pub async fn send_transaction(&self, tx: T) -> Result { let tx = self.prepare_transaction_for_sending(tx).await?; self.submit(tx).await } pub async fn await_transaction_commit(&self, id: TxId) -> Result { Ok(self .uncached_client() .await_transaction_commit(&id) .await? .into()) } #[cfg(not(feature = "coin-cache"))] async fn submit(&self, tx: T) -> Result { Ok(self.uncached_client().submit(&tx.into()).await?) } #[cfg(feature = "coin-cache")] async fn find_in_cache<'a>( &self, coin_ids: impl IntoIterator)>, ) -> Option<((Address, AssetId), CoinTypeId)> { let mut locked_cache = self.coins_cache.lock().await; for (key, ids) in coin_ids { let items = locked_cache.get_active(key); if items.is_empty() { continue; } for id in ids { if items.contains(id) { return Some((*key, id.clone())); } } } None } #[cfg(feature = "coin-cache")] async fn check_inputs_already_in_cache<'a>( &self, coin_ids: impl IntoIterator)>, ) -> Result<()> { use fuels_core::types::errors::{Error, transaction}; if let Some(((addr, asset_id), coin_type_id)) = self.find_in_cache(coin_ids).await { let msg = match coin_type_id { CoinTypeId::UtxoId(utxo_id) => format!("coin with utxo_id: `{utxo_id:x}`"), CoinTypeId::Nonce(nonce) => format!("message with nonce: `{nonce}`"), }; Err(Error::Transaction(transaction::Reason::Validation( format!( "{msg} was submitted recently in a transaction - attempting to spend it again will result in an error. Wallet address: `{addr}`, asset id: `{asset_id}`" ), ))) } else { Ok(()) } } #[cfg(feature = "coin-cache")] async fn submit(&self, tx: T) -> Result { let consensus_parameters = self.consensus_parameters().await?; let base_asset_id = consensus_parameters.base_asset_id(); let used_utxos = tx.used_coins(base_asset_id); self.check_inputs_already_in_cache(&used_utxos).await?; let tx_id = self.uncached_client().submit(&tx.into()).await?; self.coins_cache.lock().await.insert_multiple(used_utxos); Ok(tx_id) } pub async fn tx_status(&self, tx_id: &TxId) -> Result { Ok(self .uncached_client() .transaction_status(tx_id) .await? .into()) } pub async fn subscribe_transaction_status<'a>( &'a self, tx_id: &'a TxId, include_preconfirmation: bool, ) -> Result> + 'a> { let stream = self .uncached_client() .subscribe_transaction_status(tx_id, include_preconfirmation) .await?; Ok(stream.map(|status| status.map(Into::into).map_err(Into::into))) } pub async fn chain_info(&self) -> Result { Ok(self.uncached_client().chain_info().await?.into()) } pub async fn consensus_parameters(&self) -> Result { self.cached_client.consensus_parameters().await } pub async fn node_info(&self) -> Result { Ok(self.cached_client.node_info().await?.into()) } pub async fn latest_gas_price(&self) -> Result { Ok(self.uncached_client().latest_gas_price().await?) } pub async fn estimate_gas_price(&self, block_horizon: u32) -> Result { Ok(self .uncached_client() .estimate_gas_price(block_horizon) .await?) } pub async fn dry_run(&self, tx: impl Transaction) -> Result { let [tx_status] = self .uncached_client() .dry_run(Transactions::new().insert(tx).as_slice()) .await? .into_iter() .map(Into::into) .collect::>() .try_into() .expect("should have only one element"); Ok(tx_status) } pub async fn dry_run_multiple( &self, transactions: Transactions, ) -> Result> { Ok(self .uncached_client() .dry_run(transactions.as_slice()) .await? .into_iter() .map(|execution_status| (execution_status.id, execution_status.into())) .collect()) } pub async fn dry_run_opt( &self, tx: impl Transaction, utxo_validation: bool, gas_price: Option, at_height: Option, ) -> Result { let [tx_status] = self .uncached_client() .dry_run_opt( Transactions::new().insert(tx).as_slice(), Some(utxo_validation), gas_price, at_height, ) .await? .into_iter() .map(Into::into) .collect::>() .try_into() .expect("should have only one element"); Ok(tx_status) } pub async fn dry_run_opt_multiple( &self, transactions: Transactions, utxo_validation: bool, gas_price: Option, at_height: Option, ) -> Result> { Ok(self .uncached_client() .dry_run_opt( transactions.as_slice(), Some(utxo_validation), gas_price, at_height, ) .await? .into_iter() .map(|execution_status| (execution_status.id, execution_status.into())) .collect()) } /// Gets all unspent coins owned by address `from`, with asset ID `asset_id`. pub async fn get_coins(&self, from: &Address, asset_id: AssetId) -> Result> { let mut coins: Vec = vec![]; let mut cursor = None; loop { let response = self .uncached_client() .coins( from, Some(&asset_id), PaginationRequest { cursor: cursor.clone(), results: NUM_RESULTS_PER_REQUEST, direction: PageDirection::Forward, }, ) .await?; if response.results.is_empty() { break; } coins.extend(response.results.into_iter().map(Into::into)); cursor = response.cursor; } Ok(coins) } async fn request_coins_to_spend(&self, filter: ResourceFilter) -> Result> { let queries = filter.resource_queries(); let consensus_parameters = self.consensus_parameters().await?; let base_asset_id = *consensus_parameters.base_asset_id(); let res = self .uncached_client() .coins_to_spend( &filter.owner(), queries.spend_query(base_asset_id), queries.exclusion_query(), ) .await? .into_iter() .flatten() .map(CoinType::from) .collect(); Ok(res) } /// Get some spendable coins of asset `asset_id` for address `from` that add up at least to /// amount `amount`. The returned coins (UTXOs) are actual coins that can be spent. The number /// of coins (UXTOs) is optimized to prevent dust accumulation. #[cfg(not(feature = "coin-cache"))] pub async fn get_spendable_resources(&self, filter: ResourceFilter) -> Result> { self.request_coins_to_spend(filter).await } /// Get some spendable coins of asset `asset_id` for address `from` that add up at least to /// amount `amount`. The returned coins (UTXOs) are actual coins that can be spent. The number /// of coins (UXTOs) is optimized to prevent dust accumulation. /// Coins that were recently submitted inside a tx will be ignored from the results. #[cfg(feature = "coin-cache")] pub async fn get_spendable_resources( &self, mut filter: ResourceFilter, ) -> Result> { self.extend_filter_with_cached(&mut filter).await?; self.request_coins_to_spend(filter).await } #[cfg(feature = "coin-cache")] async fn extend_filter_with_cached(&self, filter: &mut ResourceFilter) -> Result<()> { let consensus_parameters = self.consensus_parameters().await?; let mut cache = self.coins_cache.lock().await; let asset_id = filter .asset_id .unwrap_or(*consensus_parameters.base_asset_id()); let used_coins = cache.get_active(&(filter.from, asset_id)); let excluded_utxos = used_coins .iter() .filter_map(|coin_id| match coin_id { CoinTypeId::UtxoId(utxo_id) => Some(utxo_id), _ => None, }) .cloned() .collect::>(); let excluded_message_nonces = used_coins .iter() .filter_map(|coin_id| match coin_id { CoinTypeId::Nonce(nonce) => Some(nonce), _ => None, }) .cloned() .collect::>(); filter.excluded_utxos.extend(excluded_utxos); filter .excluded_message_nonces .extend(excluded_message_nonces); Ok(()) } /// Get the balance of all spendable coins `asset_id` for address `address`. This is different /// from getting coins because we are just returning a number (the sum of UTXOs amount) instead /// of the UTXOs. pub async fn get_asset_balance(&self, address: &Address, asset_id: &AssetId) -> Result { Ok(self .uncached_client() .balance(address, Some(asset_id)) .await?) } /// Get the balance of all spendable coins `asset_id` for contract with id `contract_id`. pub async fn get_contract_asset_balance( &self, contract_id: &ContractId, asset_id: &AssetId, ) -> Result { Ok(self .uncached_client() .contract_balance(contract_id, Some(asset_id)) .await?) } /// Get all the spendable balances of all assets for address `address`. This is different from /// getting the coins because we are only returning the numbers (the sum of UTXOs coins amount /// for each asset id) and not the UTXOs coins themselves pub async fn get_balances(&self, address: &Address) -> Result> { let mut balances = HashMap::new(); let mut register_balances = |results: Vec<_>| { let pairs = results.into_iter().map( |Balance { owner: _, amount, asset_id, }| (asset_id.to_string(), amount), ); balances.extend(pairs); }; let indexation_flags = self.cached_client.node_info().await?.indexation; if indexation_flags.balances { let mut cursor = None; loop { let pagination = PaginationRequest { cursor: cursor.clone(), results: NUM_RESULTS_PER_REQUEST, direction: PageDirection::Forward, }; let response = self.uncached_client().balances(address, pagination).await?; if response.results.is_empty() { break; } register_balances(response.results); cursor = response.cursor; } } else { let pagination = PaginationRequest { cursor: None, results: 9999, direction: PageDirection::Forward, }; let response = self.uncached_client().balances(address, pagination).await?; register_balances(response.results) } Ok(balances) } /// Get all balances of all assets for the contract with id `contract_id`. pub async fn get_contract_balances( &self, contract_id: &ContractId, ) -> Result> { let mut contract_balances = HashMap::new(); let mut cursor = None; loop { let response = self .uncached_client() .contract_balances( contract_id, PaginationRequest { cursor: cursor.clone(), results: NUM_RESULTS_PER_REQUEST, direction: PageDirection::Forward, }, ) .await?; if response.results.is_empty() { break; } contract_balances.extend(response.results.into_iter().map( |ContractBalance { contract: _, amount, asset_id, }| (asset_id, amount), )); cursor = response.cursor; } Ok(contract_balances) } pub async fn get_transaction_by_id(&self, tx_id: &TxId) -> Result> { Ok(self .uncached_client() .transaction(tx_id) .await? .map(Into::into)) } pub async fn get_transactions( &self, request: PaginationRequest, ) -> Result> { let pr = self.uncached_client().transactions(request).await?; Ok(PaginatedResult { cursor: pr.cursor, results: pr.results.into_iter().map(Into::into).collect(), has_next_page: pr.has_next_page, has_previous_page: pr.has_previous_page, }) } // Get transaction(s) by owner pub async fn get_transactions_by_owner( &self, owner: &Address, request: PaginationRequest, ) -> Result> { let pr = self .uncached_client() .transactions_by_owner(owner, request) .await?; Ok(PaginatedResult { cursor: pr.cursor, results: pr.results.into_iter().map(Into::into).collect(), has_next_page: pr.has_next_page, has_previous_page: pr.has_previous_page, }) } pub async fn latest_block_height(&self) -> Result { Ok(self.chain_info().await?.latest_block.header.height) } pub async fn latest_block_time(&self) -> Result>> { Ok(self.chain_info().await?.latest_block.header.time) } pub async fn produce_blocks( &self, blocks_to_produce: u32, start_time: Option>, ) -> Result { let start_time = start_time.map(|time| Tai64::from_unix(time.timestamp()).0); Ok(self .uncached_client() .produce_blocks(blocks_to_produce, start_time) .await? .into()) } pub async fn block(&self, block_id: &Bytes32) -> Result> { Ok(self .uncached_client() .block(block_id) .await? .map(Into::into)) } pub async fn block_by_height(&self, height: BlockHeight) -> Result> { Ok(self .uncached_client() .block_by_height(height) .await? .map(Into::into)) } // - Get block(s) pub async fn get_blocks( &self, request: PaginationRequest, ) -> Result> { let pr = self.uncached_client().blocks(request).await?; Ok(PaginatedResult { cursor: pr.cursor, results: pr.results.into_iter().map(Into::into).collect(), has_next_page: pr.has_next_page, has_previous_page: pr.has_previous_page, }) } pub async fn estimate_transaction_cost( &self, tx: T, tolerance: Option, block_horizon: Option, ) -> Result { let block_horizon = block_horizon.unwrap_or(DEFAULT_GAS_ESTIMATION_BLOCK_HORIZON); let tolerance = tolerance.unwrap_or(DEFAULT_GAS_ESTIMATION_TOLERANCE); let EstimateGasPrice { gas_price, .. } = self.estimate_gas_price(block_horizon).await?; let tx_status = self.dry_run_opt(tx.clone(), false, None, None).await?; let total_gas = Self::apply_tolerance(tx_status.total_gas(), tolerance); let total_fee = Self::apply_tolerance(tx_status.total_fee(), tolerance); let receipts = tx_status.take_receipts(); Ok(TransactionCost { gas_price, metered_bytes_size: tx.metered_bytes_size() as u64, total_fee, total_gas, script_gas: Self::get_script_gas_used(&receipts), }) } fn apply_tolerance(value: u64, tolerance: f64) -> u64 { (value as f64 * (1.0 + tolerance)).ceil() as u64 } fn get_script_gas_used(receipts: &[Receipt]) -> u64 { receipts .iter() .rfind(|r| matches!(r, Receipt::ScriptResult { .. })) .map(|script_result| { script_result .gas_used() .expect("could not retrieve gas used from ScriptResult") }) .unwrap_or(0) } pub async fn get_messages(&self, from: &Address) -> Result> { let mut messages = Vec::new(); let mut cursor = None; loop { let response = self .uncached_client() .messages( Some(from), PaginationRequest { cursor: cursor.clone(), results: NUM_RESULTS_PER_REQUEST, direction: PageDirection::Forward, }, ) .await?; if response.results.is_empty() { break; } messages.extend(response.results.into_iter().map(Into::into)); cursor = response.cursor; } Ok(messages) } pub async fn get_message_proof( &self, tx_id: &TxId, nonce: &Nonce, commit_block_id: Option<&Bytes32>, commit_block_height: Option, ) -> Result { self.uncached_client() .message_proof( tx_id, nonce, commit_block_id, commit_block_height.map(Into::into), ) .await .map(Into::into) .map_err(Into::into) } pub async fn is_user_account(&self, address: impl Into) -> Result { self.uncached_client() .is_user_account(*address.into()) .await } pub fn with_retry_config(mut self, retry_config: RetryConfig) -> Self { self.uncached_client_mut().set_retry_config(retry_config); self } pub async fn contract_exists(&self, contract_id: &ContractId) -> Result { Ok(self.uncached_client().contract_exists(contract_id).await?) } fn uncached_client(&self) -> &RetryableClient { self.cached_client.inner() } fn uncached_client_mut(&mut self) -> &mut RetryableClient { self.cached_client.inner_mut() } } #[cfg_attr(not(target_arch = "wasm32"), async_trait::async_trait)] impl DryRunner for Provider { async fn dry_run(&self, tx: FuelTransaction) -> Result { let [tx_execution_status] = self .uncached_client() .dry_run_opt(&[tx], Some(false), Some(0), None) .await? .try_into() .expect("should have only one element"); let receipts = tx_execution_status.result.receipts(); let script_gas = Self::get_script_gas_used(receipts); let variable_outputs = receipts .iter() .filter( |receipt| matches!(receipt, Receipt::TransferOut { amount, .. } if *amount != 0), ) .count(); let succeeded = matches!( tx_execution_status.result, TransactionExecutionResult::Success { .. } ); let dry_run = DryRun { succeeded, script_gas, variable_outputs, }; Ok(dry_run) } async fn estimate_gas_price(&self, block_horizon: u32) -> Result { Ok(self.estimate_gas_price(block_horizon).await?.gas_price) } async fn consensus_parameters(&self) -> Result { Provider::consensus_parameters(self).await } async fn estimate_predicates( &self, tx: &FuelTransaction, _latest_chain_executor_version: Option, ) -> Result { Ok(self.uncached_client().estimate_predicates(tx).await?) } } ================================================ FILE: packages/fuels-accounts/src/schema/schema.sdl ================================================ input Account @oneOf { address: Address predicate: Predicate } scalar Address type AssembleTransactionResult { transaction: Transaction! status: DryRunTransactionStatus! gasPrice: U64! } scalar AssetId type AssetInfoDetails { contractId: ContractId! subId: SubId! totalSupply: U128! } type Balance { owner: Address! amount: U64! amountU128: U128! assetId: AssetId! } type BalanceConnection { """ Information to aid in pagination. """ pageInfo: PageInfo! """ A list of edges. """ edges: [BalanceEdge!]! """ A list of nodes. """ nodes: [Balance!]! } """ An edge in a connection. """ type BalanceEdge { """ The item at the end of the edge """ node: Balance! """ A cursor for use in pagination """ cursor: String! } input BalanceFilterInput { """ Filter coins based on the `owner` field """ owner: Address! } type Blob { id: BlobId! bytecode: HexString! } scalar BlobId type Block { version: BlockVersion! id: BlockId! height: U32! header: Header! consensus: Consensus! transactionIds: [TransactionId!]! transactions: [Transaction!]! } type BlockConnection { """ Information to aid in pagination. """ pageInfo: PageInfo! """ A list of edges. """ edges: [BlockEdge!]! """ A list of nodes. """ nodes: [Block!]! } """ An edge in a connection. """ type BlockEdge { """ The item at the end of the edge """ node: Block! """ A cursor for use in pagination """ cursor: String! } scalar BlockId enum BlockVersion { V1 } """ Breakpoint, defined as a tuple of contract ID and relative PC offset inside it """ input Breakpoint { contract: ContractId! pc: U64! } scalar Bytes32 type ChainInfo { name: String! latestBlock: Block! daHeight: U64! consensusParameters: ConsensusParameters! gasCosts: GasCosts! } type ChangeOutput { to: Address! amount: U64! assetId: AssetId! } input ChangePolicy @oneOf { """ Adds `Output::Change` to the transaction if it is not already present. Sending remaining assets to the provided address. """ change: Address """ Destroys the remaining assets by the transaction for provided address. """ destroy: Destroy } type Coin { utxoId: UtxoId! owner: Address! amount: U64! assetId: AssetId! """ TxPointer - the height of the block this coin was created in """ blockCreated: U32! """ TxPointer - the index of the transaction that created this coin """ txCreatedIdx: U16! } type CoinConnection { """ Information to aid in pagination. """ pageInfo: PageInfo! """ A list of edges. """ edges: [CoinEdge!]! """ A list of nodes. """ nodes: [Coin!]! } """ An edge in a connection. """ type CoinEdge { """ The item at the end of the edge """ node: Coin! """ A cursor for use in pagination """ cursor: String! } input CoinFilterInput { """ Returns coins owned by the `owner`. """ owner: Address! """ Returns coins only with `asset_id`. """ assetId: AssetId } type CoinOutput { to: Address! amount: U64! assetId: AssetId! } """ The schema analog of the [`coins::CoinType`]. """ union CoinType = Coin | MessageCoin union Consensus = Genesis | PoAConsensus type ConsensusParameters { version: ConsensusParametersVersion! txParams: TxParameters! predicateParams: PredicateParameters! scriptParams: ScriptParameters! contractParams: ContractParameters! feeParams: FeeParameters! baseAssetId: AssetId! blockGasLimit: U64! blockTransactionSizeLimit: U64! chainId: U64! gasCosts: GasCosts! privilegedAddress: Address! } type ConsensusParametersPurpose { witnessIndex: U16! checksum: Bytes32! } enum ConsensusParametersVersion { V1 } type Contract { id: ContractId! bytecode: HexString! salt: Salt! } type ContractBalance { contract: ContractId! amount: U64! assetId: AssetId! } type ContractBalanceConnection { """ Information to aid in pagination. """ pageInfo: PageInfo! """ A list of edges. """ edges: [ContractBalanceEdge!]! """ A list of nodes. """ nodes: [ContractBalance!]! } """ An edge in a connection. """ type ContractBalanceEdge { """ The item at the end of the edge """ node: ContractBalance! """ A cursor for use in pagination """ cursor: String! } input ContractBalanceFilterInput { """ Filter assets based on the `contractId` field """ contract: ContractId! } type ContractCreated { contract: ContractId! stateRoot: Bytes32! } scalar ContractId type ContractOutput { inputIndex: U16! balanceRoot: Bytes32! stateRoot: Bytes32! } type ContractParameters { version: ContractParametersVersion! contractMaxSize: U64! maxStorageSlots: U64! } enum ContractParametersVersion { V1 } type DaCompressedBlock { bytes: HexString! } union DependentCost = LightOperation | HeavyOperation enum Destroy { DESTROY } type DryRunFailureStatus { programState: ProgramState reason: String! receipts: [Receipt!]! totalGas: U64! totalFee: U64! } type DryRunStorageReads { txStatuses: [DryRunTransactionExecutionStatus!]! storageReads: [StorageReadReplayEvent!]! } type DryRunSuccessStatus { programState: ProgramState receipts: [Receipt!]! totalGas: U64! totalFee: U64! } type DryRunTransactionExecutionStatus { id: TransactionId! status: DryRunTransactionStatus! receipts: [Receipt!]! } union DryRunTransactionStatus = DryRunSuccessStatus | DryRunFailureStatus type EstimateGasPrice { gasPrice: U64! } input ExcludeInput { """ Utxos to exclude from the selection. """ utxos: [UtxoId!]! """ Messages to exclude from the selection. """ messages: [Nonce!]! } type FailureStatus { transactionId: TransactionId! blockHeight: U32! block: Block! transaction: Transaction! time: Tai64Timestamp! reason: String! programState: ProgramState receipts: [Receipt!]! totalGas: U64! totalFee: U64! } type FeeParameters { version: FeeParametersVersion! gasPriceFactor: U64! gasPerByte: U64! } enum FeeParametersVersion { V1 } type GasCosts { version: GasCostsVersion! add: U64! addi: U64! aloc: U64! and: U64! andi: U64! bal: U64! bhei: U64! bhsh: U64! burn: U64! cb: U64! cfei: U64! cfsi: U64! div: U64! divi: U64! ecr1: U64! eck1: U64! ed19: U64! eq: U64! exp: U64! expi: U64! flag: U64! gm: U64! gt: U64! gtf: U64! ji: U64! jmp: U64! jne: U64! jnei: U64! jnzi: U64! jmpf: U64! jmpb: U64! jnzf: U64! jnzb: U64! jnef: U64! jneb: U64! lb: U64! log: U64! lt: U64! lw: U64! mint: U64! mlog: U64! modOp: U64! modi: U64! moveOp: U64! movi: U64! mroo: U64! mul: U64! muli: U64! mldv: U64! niop: U64 noop: U64! not: U64! or: U64! ori: U64! poph: U64! popl: U64! pshh: U64! pshl: U64! ret: U64! rvrt: U64! sb: U64! sll: U64! slli: U64! srl: U64! srli: U64! srw: U64! sub: U64! subi: U64! sw: U64! sww: U64! time: U64! tr: U64! tro: U64! wdcm: U64! wqcm: U64! wdop: U64! wqop: U64! wdml: U64! wqml: U64! wddv: U64! wqdv: U64! wdmd: U64! wqmd: U64! wdam: U64! wqam: U64! wdmm: U64! wqmm: U64! xor: U64! xori: U64! ecop: U64 alocDependentCost: DependentCost! bldd: DependentCost bsiz: DependentCost cfe: DependentCost! cfeiDependentCost: DependentCost! call: DependentCost! ccp: DependentCost! croo: DependentCost! csiz: DependentCost! ed19DependentCost: DependentCost! k256: DependentCost! ldc: DependentCost! logd: DependentCost! mcl: DependentCost! mcli: DependentCost! mcp: DependentCost! mcpi: DependentCost! meq: DependentCost! retd: DependentCost! s256: DependentCost! scwq: DependentCost! smo: DependentCost! srwq: DependentCost! swwq: DependentCost! epar: DependentCost contractRoot: DependentCost! stateRoot: DependentCost! vmInitialization: DependentCost! newStoragePerByte: U64! } enum GasCostsVersion { V1 } type Genesis { """ The chain configs define what consensus type to use, what settlement layer to use, rules of block validity, etc. """ chainConfigHash: Bytes32! """ The Binary Merkle Tree root of all genesis coins. """ coinsRoot: Bytes32! """ The Binary Merkle Tree root of state, balances, contracts code hash of each contract. """ contractsRoot: Bytes32! """ The Binary Merkle Tree root of all genesis messages. """ messagesRoot: Bytes32! """ The Binary Merkle Tree root of all processed transaction ids. """ transactionsRoot: Bytes32! } type Header { """ Version of the header """ version: HeaderVersion! """ Hash of the header """ id: BlockId! """ The layer 1 height of messages and events to include since the last layer 1 block number. """ daHeight: U64! """ The version of the consensus parameters used to create this block. """ consensusParametersVersion: U32! """ The version of the state transition bytecode used to create this block. """ stateTransitionBytecodeVersion: U32! """ Number of transactions in this block. """ transactionsCount: U16! """ Number of message receipts in this block. """ messageReceiptCount: U32! """ Merkle root of transactions. """ transactionsRoot: Bytes32! """ Merkle root of message receipts in this block. """ messageOutboxRoot: Bytes32! """ Merkle root of inbox events in this block. """ eventInboxRoot: Bytes32! """ Fuel block height. """ height: U32! """ Merkle root of all previous block header hashes. """ prevRoot: Bytes32! """ The block producer time. """ time: Tai64Timestamp! """ Hash of the application header. """ applicationHash: Bytes32! """ Transaction ID Commitment """ txIdCommitment: Bytes32 } enum HeaderVersion { V1 V2 } type HeavyOperation { base: U64! gasPerUnit: U64! } scalar HexString type IndexationFlags { """ Is balances indexation enabled """ balances: Boolean! """ Is coins to spend indexation enabled """ coinsToSpend: Boolean! """ Is asset metadata indexation enabled """ assetMetadata: Boolean! } union Input = InputCoin | InputContract | InputMessage type InputCoin { utxoId: UtxoId! owner: Address! amount: U64! assetId: AssetId! txPointer: TxPointer! witnessIndex: U16! predicateGasUsed: U64! predicate: HexString! predicateData: HexString! } type InputContract { utxoId: UtxoId! balanceRoot: Bytes32! stateRoot: Bytes32! txPointer: TxPointer! contractId: ContractId! } type InputMessage { sender: Address! recipient: Address! amount: U64! nonce: Nonce! witnessIndex: U16! predicateGasUsed: U64! data: HexString! predicate: HexString! predicateData: HexString! } type LatestGasPrice { gasPrice: U64! blockHeight: U32! } type LightOperation { base: U64! unitsPerGas: U64! } type MerkleProof { proofSet: [Bytes32!]! proofIndex: U64! } type Message { amount: U64! sender: Address! recipient: Address! nonce: Nonce! data: HexString! daHeight: U64! } type MessageCoin { sender: Address! recipient: Address! nonce: Nonce! amount: U64! assetId: AssetId! daHeight: U64! } type MessageConnection { """ Information to aid in pagination. """ pageInfo: PageInfo! """ A list of edges. """ edges: [MessageEdge!]! """ A list of nodes. """ nodes: [Message!]! } """ An edge in a connection. """ type MessageEdge { """ The item at the end of the edge """ node: Message! """ A cursor for use in pagination """ cursor: String! } type MessageProof { messageProof: MerkleProof! blockProof: MerkleProof! messageBlockHeader: Header! commitBlockHeader: Header! sender: Address! recipient: Address! nonce: Nonce! amount: U64! data: HexString! } enum MessageState { UNSPENT SPENT NOT_FOUND } type MessageStatus { state: MessageState! } type Mutation { """ Initialize a new debugger session, returning its ID. A new VM instance is spawned for each session. The session is run in a separate database transaction, on top of the most recent node state. """ startSession: ID! """ End debugger session. """ endSession(id: ID!): Boolean! """ Reset the VM instance to the initial state. """ reset(id: ID!): Boolean! """ Execute a single fuel-asm instruction. """ execute(id: ID!, op: String!): Boolean! """ Set single-stepping mode for the VM instance. """ setSingleStepping(id: ID!, enable: Boolean!): Boolean! """ Set a breakpoint for a VM instance. """ setBreakpoint(id: ID!, breakpoint: Breakpoint!): Boolean! """ Run a single transaction in given session until it hits a breakpoint or completes. """ startTx(id: ID!, txJson: String!): RunResult! """ Resume execution of the VM instance after a breakpoint. Runs until the next breakpoint or until the transaction completes. """ continueTx(id: ID!): RunResult! """ Execute a dry-run of multiple transactions using a fork of current state, no changes are committed. """ dryRun(txs: [HexString!]!, utxoValidation: Boolean, gasPrice: U64, blockHeight: U32): [DryRunTransactionExecutionStatus!]! @deprecated(reason: "This doesn't need to be a mutation. Use query of the same name instead.") """ Submits transaction to the `TxPool`. Returns submitted transaction if the transaction is included in the `TxPool` without problems. """ submit(tx: HexString!, estimatePredicates: Boolean): Transaction! """ Sequentially produces `blocks_to_produce` blocks. The first block starts with `start_timestamp`. If the block production in the [`crate::service::Config`] is `Trigger::Interval { block_time }`, produces blocks with `block_time ` intervals between them. The `start_timestamp` is the timestamp in seconds. """ produceBlocks(startTimestamp: Tai64Timestamp, blocksToProduce: U32!): U32! } type NodeInfo { utxoValidation: Boolean! vmBacktrace: Boolean! maxTx: U64! maxGas: U64! maxSize: U64! maxDepth: U64! nodeVersion: String! indexation: IndexationFlags! txPoolStats: TxPoolStats! peers: [PeerInfo!]! } scalar Nonce union Output = CoinOutput | ContractOutput | ChangeOutput | VariableOutput | ContractCreated """ A separate `Breakpoint` type to be used as an output, as a single type cannot act as both input and output type in async-graphql """ type OutputBreakpoint { contract: ContractId! pc: U64! } """ Information about pagination in a connection """ type PageInfo { """ When paginating backwards, are there more items? """ hasPreviousPage: Boolean! """ When paginating forwards, are there more items? """ hasNextPage: Boolean! """ When paginating backwards, the cursor to continue. """ startCursor: String """ When paginating forwards, the cursor to continue. """ endCursor: String } type PeerInfo { """ The libp2p peer id """ id: String! """ The advertised multi-addrs that can be used to connect to this peer """ addresses: [String!]! """ The self-reported version of the client the peer is using """ clientVersion: String """ The last reported height of the peer """ blockHeight: U32 """ The last heartbeat from this peer in unix epoch time ms """ lastHeartbeatMs: U64! """ The internal fuel p2p reputation of this peer """ appScore: Float! } type PoAConsensus { """ Gets the signature of the block produced by `PoA` consensus. """ signature: Signature! } type Policies { tip: U64 witnessLimit: U64 maturity: U32 maxFee: U64 } type PreconfirmationFailureStatus { reason: String! txPointer: TxPointer! totalGas: U64! totalFee: U64! transactionId: TransactionId! transaction: Transaction receipts: [Receipt!] resolvedOutputs: [ResolvedOutput!] } type PreconfirmationSuccessStatus { txPointer: TxPointer! totalGas: U64! totalFee: U64! transactionId: TransactionId! transaction: Transaction receipts: [Receipt!] resolvedOutputs: [ResolvedOutput!] } input Predicate { predicateAddress: Address! predicate: HexString! predicateData: HexString! } type PredicateParameters { version: PredicateParametersVersion! maxPredicateLength: U64! maxPredicateDataLength: U64! maxGasPerPredicate: U64! maxMessageDataLength: U64! } enum PredicateParametersVersion { V1 } type ProgramState { returnType: ReturnType! data: HexString! } type Query { assetDetails( """ ID of the Asset """ id: AssetId! ): AssetInfoDetails """ Read register value by index. """ register(id: ID!, register: U32!): U64! """ Read read a range of memory bytes. """ memory(id: ID!, start: U32!, size: U32!): String! balance( """ address of the owner """ owner: Address!, """ asset_id of the coin """ assetId: AssetId! ): Balance! balances(filter: BalanceFilterInput!, first: Int, after: String, last: Int, before: String): BalanceConnection! blob( """ ID of the Blob """ id: BlobId! ): Blob block( """ ID of the block """ id: BlockId, """ Height of the block """ height: U32 ): Block blocks(first: Int, after: String, last: Int, before: String): BlockConnection! chain: ChainInfo! transaction( """ The ID of the transaction """ id: TransactionId! ): Transaction transactions(first: Int, after: String, last: Int, before: String): TransactionConnection! transactionsByOwner(owner: Address!, first: Int, after: String, last: Int, before: String): TransactionConnection! """ Assembles the transaction based on the provided requirements. The return transaction contains: - Input coins to cover `required_balances` - Input coins to cover the fee of the transaction based on the gas price from `block_horizon` - `Change` or `Destroy` outputs for all assets from the inputs - `Variable` outputs in the case they are required during the execution - `Contract` inputs and outputs in the case they are required during the execution - Reserved witness slots for signed coins filled with `64` zeroes - Set script gas limit(unless `script` is empty) - Estimated predicates, if `estimate_predicates == true` Returns an error if: - The number of required balances exceeds the maximum number of inputs allowed. - The fee address index is out of bounds. - The same asset has multiple change policies(either the receiver of the change is different, or one of the policies states about the destruction of the token while the other does not). The `Change` output from the transaction also count as a `ChangePolicy`. - The number of excluded coin IDs exceeds the maximum number of inputs allowed. - Required assets have multiple entries. - If accounts don't have sufficient amounts to cover the transaction requirements in assets. - If a constructed transaction breaks the rules defined by consensus parameters. """ assembleTx( """ The original transaction that contains application level logic only """ tx: HexString!, """ Number of blocks into the future to estimate the gas price for """ blockHorizon: U32!, """ The list of required balances for the transaction to include as inputs. The list should be created based on the application-required assets. The base asset requirement should not require assets to cover the transaction fee, which will be calculated and added automatically at the end of the assembly process. """ requiredBalances: [RequiredBalance!]!, """ The index from the `required_balances` list that points to the address who pays fee for the transaction. If you only want to cover the fee of transaction, you can set the required balance to 0, set base asset and point to this required address. """ feeAddressIndex: U16!, """ The list of resources to exclude from the selection for the inputs """ excludeInput: ExcludeInput, """ Perform the estimation of the predicates before cover fee of the transaction """ estimatePredicates: Boolean, """ During the phase of the fee calculation, adds `reserve_gas` to the total gas used by the transaction and fetch assets to cover the fee. """ reserveGas: U64 ): AssembleTransactionResult! """ Estimate the predicate gas for the provided transaction """ estimatePredicates(tx: HexString!): Transaction! """ Returns all possible receipts for test purposes. """ allReceipts: [Receipt!]! """ Execute a dry-run of multiple transactions using a fork of current state, no changes are committed. """ dryRun(txs: [HexString!]!, utxoValidation: Boolean, gasPrice: U64, blockHeight: U32): [DryRunTransactionExecutionStatus!]! """ Execute a dry-run of multiple transactions using a fork of current state, no changes are committed. Also records accesses, so the execution can be replicated locally. """ dryRunRecordStorageReads(txs: [HexString!]!, utxoValidation: Boolean, gasPrice: U64, blockHeight: U32): DryRunStorageReads! """ Get execution trace for an already-executed block. """ storageReadReplay(height: U32!): [StorageReadReplayEvent!]! """ Returns true when the GraphQL API is serving requests. """ health: Boolean! """ Gets the coin by `utxo_id`. """ coin( """ The ID of the coin """ utxoId: UtxoId! ): Coin """ Gets all unspent coins of some `owner` maybe filtered with by `asset_id` per page. """ coins(filter: CoinFilterInput!, first: Int, after: String, last: Int, before: String): CoinConnection! """ For each `query_per_asset`, get some spendable coins(of asset specified by the query) owned by `owner` that add up at least the query amount. The returned coins can be spent. The number of coins is optimized to prevent dust accumulation. The query supports excluding and maximum the number of coins. Returns: The list of spendable coins per asset from the query. The length of the result is the same as the length of `query_per_asset`. The ordering of assets and `query_per_asset` is the same. """ coinsToSpend( """ The `Address` of the coins owner. """ owner: Address!, """ The list of requested assets` coins with asset ids, `target` amount the user wants to reach, and the `max` number of coins in the selection. Several entries with the same asset id are not allowed. The result can't contain more coins than `max_inputs`. """ queryPerAsset: [SpendQueryElementInput!]!, """ The excluded coins from the selection. """ excludedIds: ExcludeInput ): [[CoinType!]!]! daCompressedBlock( """ Height of the block """ height: U32! ): DaCompressedBlock contract( """ ID of the Contract """ id: ContractId! ): Contract contractBalance(contract: ContractId!, asset: AssetId!): ContractBalance! contractBalances(filter: ContractBalanceFilterInput!, first: Int, after: String, last: Int, before: String): ContractBalanceConnection! nodeInfo: NodeInfo! latestGasPrice: LatestGasPrice! estimateGasPrice( """ Number of blocks into the future to estimate the gas price for """ blockHorizon: U32 ): EstimateGasPrice! message( """ The Nonce of the message """ nonce: Nonce! ): Message messages( """ address of the owner """ owner: Address, first: Int, after: String, last: Int, before: String ): MessageConnection! messageProof(transactionId: TransactionId!, nonce: Nonce!, commitBlockId: BlockId, commitBlockHeight: U32): MessageProof! messageStatus(nonce: Nonce!): MessageStatus! relayedTransactionStatus( """ The id of the relayed tx """ id: RelayedTransactionId! ): RelayedTransactionStatus consensusParameters(version: Int!): ConsensusParameters! stateTransitionBytecodeByVersion(version: Int!): StateTransitionBytecode stateTransitionBytecodeByRoot(root: HexString!): StateTransitionBytecode! """ Get storage slot values for a contract at a specific block height. Use the latest block height if not provided. Requires historical execution config to be enabled. """ contractSlotValues(contractId: ContractId!, blockHeight: U32, storageSlots: [Bytes32!]!): [StorageSlot!]! """ Get balance values for a contract at a specific block height. Use the latest block height if not provided. Requires historical execution config to be enabled. """ contractBalanceValues(contractId: ContractId!, blockHeight: U32, assets: [AssetId!]!): [ContractBalance!]! } type Receipt { id: ContractId pc: U64 is: U64 to: ContractId toAddress: Address amount: U64 assetId: AssetId gas: U64 param1: U64 param2: U64 val: U64 ptr: U64 digest: Bytes32 reason: U64 ra: U64 rb: U64 rc: U64 rd: U64 len: U64 receiptType: ReceiptType! result: U64 gasUsed: U64 data: HexString sender: Address recipient: Address nonce: Nonce """ Set in the case of a Panic receipt to indicate a missing contract input id """ contractId: ContractId subId: SubId } enum ReceiptType { CALL RETURN RETURN_DATA PANIC REVERT LOG LOG_DATA TRANSFER TRANSFER_OUT SCRIPT_RESULT MESSAGE_OUT MINT BURN } type RelayedTransactionFailed { blockHeight: U32! failure: String! } scalar RelayedTransactionId union RelayedTransactionStatus = RelayedTransactionFailed input RequiredBalance { assetId: AssetId! amount: U64! account: Account! changePolicy: ChangePolicy! } type ResolvedOutput { utxoId: UtxoId! output: Output! } enum ReturnType { RETURN RETURN_DATA REVERT } type RunResult { state: RunState! breakpoint: OutputBreakpoint jsonReceipts: [String!]! } enum RunState { """ All breakpoints have been processed, and the program has terminated """ COMPLETED """ Stopped on a breakpoint """ BREAKPOINT } scalar Salt type ScriptParameters { version: ScriptParametersVersion! maxScriptLength: U64! maxScriptDataLength: U64! } enum ScriptParametersVersion { V1 } scalar Signature input SpendQueryElementInput { """ Identifier of the asset to spend. """ assetId: AssetId! """ Target amount for the query. """ amount: U128! """ The maximum number of currencies for selection. """ max: U16 """ If true, returns available coins instead of failing when the requested amount is unavailable. """ allowPartial: Boolean } type SqueezedOutStatus { transactionId: TransactionId! reason: String! } type StateTransitionBytecode { root: HexString! bytecode: UploadedBytecode! } type StateTransitionPurpose { root: Bytes32! } type StorageReadReplayEvent { column: U32! key: HexString! value: HexString } type StorageSlot { key: Bytes32! value: HexString! } scalar SubId type SubmittedStatus { time: Tai64Timestamp! } type Subscription { """ Returns a stream of status updates for the given transaction id. If the current status is [`TransactionStatus::Success`], [`TransactionStatus::Failed`], or [`TransactionStatus::SqueezedOut`] the stream will return that and end immediately. Other, intermediate statuses will also be returned but the stream will remain active and wait for a future updates. This stream will wait forever so it's advised to use within a timeout. It is possible for the stream to miss an update if it is polled slower then the updates arrive. In such a case the stream will close without a status. If this occurs the stream can simply be restarted to return the latest status. """ statusChange( """ The ID of the transaction """ id: TransactionId!, """ If true, accept to receive the preconfirmation status """ includePreconfirmation: Boolean ): TransactionStatus! alpha__preconfirmations: TransactionStatus! """ Submits transaction to the `TxPool` and await either success or failure. """ submitAndAwait(tx: HexString!, estimatePredicates: Boolean): TransactionStatus! """ Submits the transaction to the `TxPool` and returns a stream of events. Compared to the `submitAndAwait`, the stream also contains `SubmittedStatus` and potentially preconfirmation as an intermediate state. """ submitAndAwaitStatus(tx: HexString!, estimatePredicates: Boolean, includePreconfirmation: Boolean): TransactionStatus! contractStorageSlots(contractId: ContractId!): StorageSlot! contractStorageBalances(contractId: ContractId!): ContractBalance! alpha__new_blocks: HexString! } type SuccessStatus { transactionId: TransactionId! blockHeight: U32! block: Block! transaction: Transaction! time: Tai64Timestamp! programState: ProgramState receipts: [Receipt!]! totalGas: U64! totalFee: U64! } scalar Tai64Timestamp type Transaction { id: TransactionId! inputAssetIds: [AssetId!] inputContracts: [ContractId!] inputContract: InputContract policies: Policies scriptGasLimit: U64 maturity: U32 mintAmount: U64 mintAssetId: AssetId mintGasPrice: U64 txPointer: TxPointer isScript: Boolean! isCreate: Boolean! isMint: Boolean! isUpgrade: Boolean! isUpload: Boolean! isBlob: Boolean! inputs: [Input!] outputs: [Output!]! outputContract: ContractOutput witnesses: [HexString!] receiptsRoot: Bytes32 status(includePreconfirmation: Boolean): TransactionStatus script: HexString scriptData: HexString bytecodeWitnessIndex: U16 blobId: BlobId salt: Salt storageSlots: [HexString!] bytecodeRoot: Bytes32 subsectionIndex: U16 subsectionsNumber: U16 proofSet: [Bytes32!] upgradePurpose: UpgradePurpose """ Return the transaction bytes using canonical encoding """ rawPayload: HexString! } type TransactionConnection { """ Information to aid in pagination. """ pageInfo: PageInfo! """ A list of edges. """ edges: [TransactionEdge!]! """ A list of nodes. """ nodes: [Transaction!]! } """ An edge in a connection. """ type TransactionEdge { """ The item at the end of the edge """ node: Transaction! """ A cursor for use in pagination """ cursor: String! } scalar TransactionId union TransactionStatus = SubmittedStatus | SuccessStatus | PreconfirmationSuccessStatus | SqueezedOutStatus | FailureStatus | PreconfirmationFailureStatus type TxParameters { version: TxParametersVersion! maxInputs: U16! maxOutputs: U16! maxWitnesses: U32! maxGasPerTx: U64! maxSize: U64! maxBytecodeSubsections: U16! } enum TxParametersVersion { V1 } scalar TxPointer type TxPoolStats { """ The number of transactions in the pool """ txCount: U64! """ The total size of the transactions in the pool """ totalSize: U64! """ The total gas of the transactions in the pool """ totalGas: U64! } scalar U128 scalar U16 scalar U32 scalar U64 union UpgradePurpose = ConsensusParametersPurpose | StateTransitionPurpose type UploadedBytecode { """ Combined bytecode of all uploaded subsections. """ bytecode: HexString! """ Number of uploaded subsections (if incomplete). """ uploadedSubsectionsNumber: Int """ Indicates if the bytecode upload is complete. """ completed: Boolean! } scalar UtxoId type VariableOutput { to: Address! amount: U64! assetId: AssetId! } directive @deprecated(reason: String = "No longer supported") on FIELD_DEFINITION | ARGUMENT_DEFINITION | INPUT_FIELD_DEFINITION | ENUM_VALUE directive @include(if: Boolean!) on FIELD | FRAGMENT_SPREAD | INLINE_FRAGMENT directive @oneOf on INPUT_OBJECT directive @skip(if: Boolean!) on FIELD | FRAGMENT_SPREAD | INLINE_FRAGMENT schema { query: Query mutation: Mutation subscription: Subscription } ================================================ FILE: packages/fuels-accounts/src/signers/fake.rs ================================================ use async_trait::async_trait; use fuel_crypto::{Message, Signature}; use fuels_core::{ traits::Signer, types::{Address, errors::Result}, }; use super::private_key::PrivateKeySigner; #[derive(Clone, Debug, PartialEq, Eq)] pub struct FakeSigner { address: Address, } impl From for FakeSigner { fn from(signer: PrivateKeySigner) -> Self { Self { address: signer.address(), } } } impl FakeSigner { pub fn new(address: Address) -> Self { Self { address } } } #[cfg_attr(target_arch = "wasm32", async_trait(?Send))] #[cfg_attr(not(target_arch = "wasm32"), async_trait)] impl Signer for FakeSigner { async fn sign(&self, _message: Message) -> Result { Ok(Signature::default()) } fn address(&self) -> Address { self.address } } ================================================ FILE: packages/fuels-accounts/src/signers/kms/aws.rs ================================================ use async_trait::async_trait; pub use aws_config; pub use aws_sdk_kms; use aws_sdk_kms::{ Client, primitives::Blob, types::{KeySpec, MessageType, SigningAlgorithmSpec}, }; use fuel_crypto::{Message, PublicKey, Signature}; use fuels_core::{ traits::Signer, types::{ Address, errors::{Error, Result}, }, }; use k256::{PublicKey as K256PublicKey, pkcs8::DecodePublicKey}; use super::signature_utils; const AWS_KMS_ERROR_PREFIX: &str = "AWS KMS Error"; const EXPECTED_KEY_SPEC: KeySpec = KeySpec::EccSecgP256K1; #[derive(Clone, Debug)] pub struct AwsKmsSigner { key_id: String, client: Client, public_key_der: Vec, fuel_address: Address, } impl AwsKmsSigner { pub async fn new(key_id: impl Into, client: &Client) -> Result { let key_id: String = key_id.into(); Self::validate_key_spec(client, &key_id).await?; let public_key = Self::retrieve_public_key(client, &key_id).await?; let fuel_address = Self::derive_fuel_address(&public_key)?; Ok(Self { key_id, client: client.clone(), public_key_der: public_key, fuel_address, }) } async fn validate_key_spec(client: &Client, key_id: &str) -> Result<()> { let response = client .get_public_key() .key_id(key_id) .send() .await .map_err(format_aws_error)?; let key_spec = response.key_spec; match key_spec { Some(EXPECTED_KEY_SPEC) => Ok(()), other => Err(Error::Other(format!( "{AWS_KMS_ERROR_PREFIX}: Invalid key type {other:?}, expected {EXPECTED_KEY_SPEC:?}" ))), } } async fn retrieve_public_key(client: &Client, key_id: &str) -> Result> { let response = client .get_public_key() .key_id(key_id) .send() .await .map_err(format_aws_error)?; response .public_key() .map(|blob| blob.as_ref().to_vec()) .ok_or_else(|| { Error::Other(format!("{AWS_KMS_ERROR_PREFIX}: Empty public key response")) }) } fn derive_fuel_address(public_key: &[u8]) -> Result
{ let k256_key = K256PublicKey::from_public_key_der(public_key) .map_err(|_| Error::Other(format!("{AWS_KMS_ERROR_PREFIX}: Invalid DER encoding")))?; let fuel_public_key = PublicKey::from(k256_key); Ok(Address::from(*fuel_public_key.hash())) } async fn request_kms_signature(&self, message: Message) -> Result> { let response = self .client .sign() .key_id(&self.key_id) .signing_algorithm(SigningAlgorithmSpec::EcdsaSha256) .message_type(MessageType::Digest) .message(Blob::new(message.as_ref().to_vec())) .send() .await .map_err(|err| { Error::Other(format!("{AWS_KMS_ERROR_PREFIX}: Signing failed - {err}")) })?; response .signature .map(|blob| blob.into_inner()) .ok_or_else(|| { Error::Other(format!("{AWS_KMS_ERROR_PREFIX}: Empty signature response")) }) } pub fn key_id(&self) -> &String { &self.key_id } pub fn public_key(&self) -> &Vec { &self.public_key_der } } #[async_trait] impl Signer for AwsKmsSigner { async fn sign(&self, message: Message) -> Result { let signature_der = self.request_kms_signature(message).await?; let k256_key = K256PublicKey::from_public_key_der(&self.public_key_der).map_err(|_| { Error::Other(format!("{AWS_KMS_ERROR_PREFIX}: Invalid cached public key")) })?; let (normalized_sig, recovery_id) = signature_utils::normalize_signature( &signature_der, message, &k256_key, AWS_KMS_ERROR_PREFIX, )?; Ok(signature_utils::convert_to_fuel_signature( normalized_sig, recovery_id, )) } fn address(&self) -> Address { self.fuel_address } } fn format_aws_error(err: impl std::fmt::Display) -> Error { Error::Other(format!("{AWS_KMS_ERROR_PREFIX}: {err}")) } ================================================ FILE: packages/fuels-accounts/src/signers/kms/google.rs ================================================ use async_trait::async_trait; use fuel_crypto::{Message, PublicKey, Signature}; use fuels_core::{ traits::Signer, types::{ Address, errors::{Error, Result}, }, }; pub use google_cloud_kms; use google_cloud_kms::{ client::Client, grpc::kms::v1::{ AsymmetricSignRequest, Digest, GetPublicKeyRequest, crypto_key_version::CryptoKeyVersionAlgorithm::EcSignSecp256k1Sha256, digest::Digest::Sha256, }, }; use k256::{PublicKey as K256PublicKey, pkcs8::DecodePublicKey}; use super::signature_utils; const GOOGLE_KMS_ERROR_PREFIX: &str = "Google KMS Error"; #[derive(Clone, Debug)] pub struct GoogleKmsSigner { key_path: String, client: Client, public_key_pem: String, fuel_address: Address, } #[derive(Debug, Clone)] pub struct CryptoKeyVersionName { pub project_id: String, pub location: String, pub key_ring: String, pub key_id: String, pub key_version: String, } impl CryptoKeyVersionName { pub fn new( project_id: impl Into, location: impl Into, key_ring: impl Into, key_id: impl Into, key_version: impl Into, ) -> Self { Self { project_id: project_id.into(), location: location.into(), key_ring: key_ring.into(), key_id: key_id.into(), key_version: key_version.into(), } } } impl std::fmt::Display for CryptoKeyVersionName { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!( f, "projects/{}/locations/{}/keyRings/{}/cryptoKeys/{}/cryptoKeyVersions/{}", self.project_id, self.location, self.key_ring, self.key_id, self.key_version ) } } impl GoogleKmsSigner { pub async fn new(key_path: impl Into, client: &Client) -> Result { let key_path: String = key_path.into(); let public_key_pem = Self::retrieve_public_key(client, &key_path).await?; let fuel_address = Self::derive_fuel_address(&public_key_pem)?; Ok(Self { key_path, client: client.clone(), public_key_pem, fuel_address, }) } async fn retrieve_public_key(client: &Client, key_path: &str) -> Result { let request = GetPublicKeyRequest { name: key_path.to_string(), }; let response = client .get_public_key(request, None) .await .map_err(|e| format_gcp_error(format!("Failed to get public key: {}", e)))?; if response.algorithm != EcSignSecp256k1Sha256 as i32 { return Err(Error::Other(format!( "{GOOGLE_KMS_ERROR_PREFIX}: Invalid key algorithm: {}, expected EC_SIGN_SECP256K1_SHA256", response.algorithm ))); } Ok(response.pem) } fn derive_fuel_address(pem: &str) -> Result
{ let k256_key = K256PublicKey::from_public_key_pem(pem).map_err(|_| { Error::Other(format!("{GOOGLE_KMS_ERROR_PREFIX}: Invalid PEM encoding")) })?; let fuel_public_key = PublicKey::from(k256_key); Ok(Address::from(*fuel_public_key.hash())) } async fn request_gcp_signature(&self, message: Message) -> Result> { let digest = Digest { digest: Some(Sha256(message.as_ref().to_vec())), }; let request = AsymmetricSignRequest { name: self.key_path.clone(), digest: Some(digest), digest_crc32c: None, ..AsymmetricSignRequest::default() }; let response = self .client .asymmetric_sign(request, None) .await .map_err(|e| format_gcp_error(format!("Signing failed: {}", e)))?; if response.signature.is_empty() { return Err(Error::Other(format!( "{GOOGLE_KMS_ERROR_PREFIX}: Empty signature response" ))); } Ok(response.signature) } pub fn key_path(&self) -> &String { &self.key_path } pub fn public_key(&self) -> &String { &self.public_key_pem } } #[async_trait] impl Signer for GoogleKmsSigner { async fn sign(&self, message: Message) -> Result { let signature_der = self.request_gcp_signature(message).await?; let k256_key = K256PublicKey::from_public_key_pem(&self.public_key_pem).map_err(|_| { Error::Other(format!( "{GOOGLE_KMS_ERROR_PREFIX}: Invalid cached public key" )) })?; let (normalized_sig, recovery_id) = signature_utils::normalize_signature( &signature_der, message, &k256_key, GOOGLE_KMS_ERROR_PREFIX, )?; Ok(signature_utils::convert_to_fuel_signature( normalized_sig, recovery_id, )) } fn address(&self) -> Address { self.fuel_address } } fn format_gcp_error(err: impl std::fmt::Display) -> Error { Error::Other(format!("{GOOGLE_KMS_ERROR_PREFIX}: {err}")) } ================================================ FILE: packages/fuels-accounts/src/signers/kms.rs ================================================ #[cfg(feature = "signer-aws-kms")] pub mod aws; #[cfg(feature = "signer-google-kms")] pub mod google; mod signature_utils { use fuel_crypto::{Message, Signature}; use fuels_core::types::errors::{Error, Result}; use k256::{ PublicKey as K256PublicKey, ecdsa::{RecoveryId, Signature as K256Signature, VerifyingKey}, }; pub fn normalize_signature( signature_der: &[u8], message: Message, expected_pubkey: &K256PublicKey, error_prefix: &str, ) -> Result<(K256Signature, RecoveryId)> { let signature = K256Signature::from_der(signature_der) .map_err(|_| Error::Other(format!("{error_prefix}: Invalid DER signature")))?; let normalized_sig = signature.normalize_s().unwrap_or(signature); let recovery_id = determine_recovery_id(&normalized_sig, message, expected_pubkey, error_prefix)?; Ok((normalized_sig, recovery_id)) } pub fn determine_recovery_id( sig: &K256Signature, message: Message, expected_pubkey: &K256PublicKey, error_prefix: &str, ) -> Result { let recid_even = RecoveryId::new(false, false); let recid_odd = RecoveryId::new(true, false); let expected_verifying_key: VerifyingKey = expected_pubkey.into(); let recovered_even = VerifyingKey::recover_from_prehash(&*message, sig, recid_even); let recovered_odd = VerifyingKey::recover_from_prehash(&*message, sig, recid_odd); if recovered_even .map(|r| r == expected_verifying_key) .unwrap_or(false) { Ok(recid_even) } else if recovered_odd .map(|r| r == expected_verifying_key) .unwrap_or(false) { Ok(recid_odd) } else { Err(Error::Other(format!( "{error_prefix}: Invalid signature (could not recover correct public key)" ))) } } pub fn convert_to_fuel_signature( signature: K256Signature, recovery_id: RecoveryId, ) -> Signature { let recovery_byte = recovery_id.is_y_odd() as u8; let mut bytes: [u8; 64] = signature.to_bytes().into(); bytes[32] = (recovery_byte << 7) | (bytes[32] & 0x7F); Signature::from_bytes(bytes) } } ================================================ FILE: packages/fuels-accounts/src/signers/private_key.rs ================================================ use async_trait::async_trait; use fuel_crypto::{Message, PublicKey, SecretKey, Signature}; use fuels_core::{ traits::Signer, types::{Address, errors::Result}, }; use rand::{CryptoRng, Rng, RngCore}; use zeroize::{Zeroize, ZeroizeOnDrop}; /// Generates a random mnemonic phrase given a random number generator and the number of words to /// generate, `count`. pub fn generate_mnemonic_phrase(rng: &mut R, count: usize) -> Result { Ok(fuel_crypto::generate_mnemonic_phrase(rng, count)?) } #[derive(Clone, Zeroize, ZeroizeOnDrop)] pub struct PrivateKeySigner { private_key: SecretKey, #[zeroize(skip)] address: Address, } impl std::fmt::Debug for PrivateKeySigner { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.debug_struct("PrivateKeySigner") .field("private_key", &"REDACTED") .field("address", &self.address) .finish() } } impl PrivateKeySigner { pub fn new(private_key: SecretKey) -> Self { let public = PublicKey::from(&private_key); let address = Address::from(*public.hash()); Self { private_key, address, } } pub fn random(rng: &mut (impl CryptoRng + RngCore)) -> Self { Self::new(SecretKey::random(rng)) } pub fn address(&self) -> Address { self.address } pub fn secret_key(&self) -> SecretKey { self.private_key } } #[cfg_attr(target_arch = "wasm32", async_trait(?Send))] #[cfg_attr(not(target_arch = "wasm32"), async_trait)] impl Signer for PrivateKeySigner { async fn sign(&self, message: Message) -> Result { let sig = Signature::sign(&self.private_key, &message); Ok(sig) } fn address(&self) -> Address { self.address } } #[cfg(test)] mod tests { use std::str::FromStr; use rand::{SeedableRng, rngs::StdRng}; use super::*; use crate::signers::derivation::DEFAULT_DERIVATION_PATH; #[tokio::test] async fn mnemonic_generation() -> Result<()> { let mnemonic = generate_mnemonic_phrase(&mut rand::thread_rng(), 12)?; let _wallet = PrivateKeySigner::new(SecretKey::new_from_mnemonic_phrase_with_path( &mnemonic, DEFAULT_DERIVATION_PATH, )?); Ok(()) } #[tokio::test] async fn sign_and_verify() -> Result<()> { // ANCHOR: sign_message let mut rng = StdRng::seed_from_u64(2322u64); let mut secret_seed = [0u8; 32]; rng.fill_bytes(&mut secret_seed); let secret = secret_seed.as_slice().try_into()?; // Create a signer using the private key created above. let signer = PrivateKeySigner::new(secret); let message = Message::new("my message".as_bytes()); let signature = signer.sign(message).await?; // Check if signature is what we expect it to be assert_eq!( signature, Signature::from_str( "0x8eeb238db1adea4152644f1cd827b552dfa9ab3f4939718bb45ca476d167c6512a656f4d4c7356bfb9561b14448c230c6e7e4bd781df5ee9e5999faa6495163d" )? ); // Recover the public key that signed the message let recovered_pub_key: PublicKey = signature.recover(&message)?; assert_eq!(*signer.address, *recovered_pub_key.hash()); // Verify signature signature.verify(&recovered_pub_key, &message)?; // ANCHOR_END: sign_message Ok(()) } } ================================================ FILE: packages/fuels-accounts/src/signers.rs ================================================ pub mod derivation { pub const BIP44_PURPOSE: &str = "44'"; pub const COIN_TYPE: &str = "1179993420'"; pub const DEFAULT_DERIVATION_PATH: &str = "m/44'/1179993420'/0'/0/0"; } #[cfg(any(feature = "signer-aws-kms", feature = "signer-google-kms"))] pub mod kms; pub mod fake; pub mod private_key; ================================================ FILE: packages/fuels-accounts/src/wallet.rs ================================================ use crate::{provider::Provider, signers::private_key::PrivateKeySigner}; #[derive(Debug, Clone)] pub struct Wallet> { state: S, provider: Provider, } impl Wallet { pub fn set_provider(&mut self, provider: Provider) { self.provider = provider; } pub fn provider(&self) -> &Provider { &self.provider } } mod unlocked { use async_trait::async_trait; use fuels_core::{ traits::Signer, types::{ Address, AssetId, coin_type_id::CoinTypeId, errors::Result, input::Input, transaction_builders::TransactionBuilder, }, }; use rand::{CryptoRng, RngCore}; use super::{Locked, Wallet}; use crate::{ Account, ViewOnlyAccount, provider::Provider, signers::private_key::PrivateKeySigner, }; #[derive(Debug, Clone)] pub struct Unlocked { signer: S, } impl Unlocked { fn new(signer: S) -> Self { Self { signer } } } impl Wallet> { pub fn new(signer: S, provider: Provider) -> Self { Wallet { state: Unlocked::new(signer), provider, } } pub fn signer(&self) -> &S { &self.state.signer } } impl Wallet> { pub fn random(rng: &mut (impl CryptoRng + RngCore), provider: Provider) -> Self { Self::new(PrivateKeySigner::random(rng), provider) } } impl Wallet> where S: Signer, { pub fn lock(&self) -> Wallet { Wallet::new_locked(self.state.signer.address(), self.provider.clone()) } } #[async_trait] impl ViewOnlyAccount for Wallet> where S: Signer + Clone + Send + Sync + std::fmt::Debug + 'static, { fn address(&self) -> Address { self.state.signer.address() } fn try_provider(&self) -> Result<&Provider> { Ok(&self.provider) } async fn get_asset_inputs_for_amount( &self, asset_id: AssetId, amount: u128, excluded_coins: Option>, ) -> Result> { Ok(self .get_spendable_resources(asset_id, amount, excluded_coins) .await? .into_iter() .map(Input::resource_signed) .collect::>()) } } #[async_trait] impl Account for Wallet> where S: Signer + Clone + Send + Sync + std::fmt::Debug + 'static, { fn add_witnesses(&self, tb: &mut Tb) -> Result<()> { tb.add_signer(self.state.signer.clone())?; Ok(()) } } } pub use unlocked::*; mod locked { use async_trait::async_trait; use fuels_core::types::{ Address, AssetId, coin_type_id::CoinTypeId, errors::Result, input::Input, }; use super::Wallet; use crate::{ViewOnlyAccount, provider::Provider}; #[derive(Debug, Clone)] pub struct Locked { address: Address, } impl Locked { fn new(address: Address) -> Self { Self { address } } } impl Wallet { pub fn new_locked(addr: Address, provider: Provider) -> Self { Self { state: Locked::new(addr), provider, } } } #[async_trait] impl ViewOnlyAccount for Wallet { fn address(&self) -> Address { self.state.address } fn try_provider(&self) -> Result<&Provider> { Ok(&self.provider) } async fn get_asset_inputs_for_amount( &self, asset_id: AssetId, amount: u128, excluded_coins: Option>, ) -> Result> { Ok(self .get_spendable_resources(asset_id, amount, excluded_coins) .await? .into_iter() .map(Input::resource_signed) .collect::>()) } } } pub use locked::*; ================================================ FILE: packages/fuels-code-gen/Cargo.toml ================================================ [package] name = "fuels-code-gen" version = { workspace = true } authors = { workspace = true } edition = { workspace = true } homepage = { workspace = true } license = { workspace = true } repository = { workspace = true } rust-version = { workspace = true } description = "Used for code generation in the Fuel Rust SDK" [dependencies] Inflector = { workspace = true } fuel-abi-types = { workspace = true } itertools = { workspace = true } proc-macro2 = { workspace = true } quote = { workspace = true } regex = { workspace = true } serde_json = { workspace = true } syn = { workspace = true } [dev-dependencies] pretty_assertions = { workspace = true, features = ["alloc"] } [package.metadata.cargo-machete] ignored = ["Inflector"] ================================================ FILE: packages/fuels-code-gen/src/error.rs ================================================ use std::{ fmt::{Debug, Display, Formatter}, io, }; pub struct Error(pub String); impl Error { pub fn combine>(self, err: T) -> Self { error!("{} {}", self.0, err.into().0) } } #[macro_export] macro_rules! error { ($fmt_str: literal $(,$arg: expr)*) => {$crate::error::Error(format!($fmt_str,$($arg),*))} } pub use error; pub type Result = std::result::Result; impl Debug for Error { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { write!(f, "{:?}", self.0) } } impl Display for Error { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { write!(f, "{}", self.0) } } impl std::error::Error for Error {} macro_rules! impl_from { ($($err_type:ty),*) => { $( impl From<$err_type> for self::Error { fn from(err: $err_type) -> Self { Self(err.to_string()) } } )* } } impl_from!( serde_json::Error, io::Error, proc_macro2::LexError, fuel_abi_types::error::Error ); ================================================ FILE: packages/fuels-code-gen/src/lib.rs ================================================ pub use program_bindings::*; pub mod error; mod program_bindings; pub mod utils; ================================================ FILE: packages/fuels-code-gen/src/program_bindings/abigen/abigen_target.rs ================================================ use std::{ convert::TryFrom, env, fs, path::{Path, PathBuf}, str::FromStr, }; use fuel_abi_types::abi::full_program::FullProgramABI; use proc_macro2::Ident; use crate::error::{Error, Result, error}; #[derive(Debug, Clone)] pub struct AbigenTarget { pub(crate) name: String, pub(crate) source: Abi, pub(crate) program_type: ProgramType, } impl AbigenTarget { pub fn new(name: String, source: Abi, program_type: ProgramType) -> Self { Self { name, source, program_type, } } pub fn name(&self) -> &str { &self.name } pub fn source(&self) -> &Abi { &self.source } pub fn program_type(&self) -> ProgramType { self.program_type } } #[derive(Debug, Clone)] pub struct Abi { pub(crate) path: Option, pub(crate) abi: FullProgramABI, } impl Abi { pub fn load_from(path: impl AsRef) -> Result { let path = Self::canonicalize_path(path.as_ref())?; let json_abi = fs::read_to_string(&path).map_err(|e| { error!( "failed to read `abi` file with path {}: {}", path.display(), e ) })?; let abi = Self::parse_from_json(&json_abi)?; Ok(Abi { path: Some(path), abi, }) } fn canonicalize_path(path: &Path) -> Result { let current_dir = env::current_dir() .map_err(|e| error!("unable to get current directory: ").combine(e))?; let root = current_dir.canonicalize().map_err(|e| { error!( "unable to canonicalize current directory {}: ", current_dir.display() ) .combine(e) })?; let path = root.join(path); if path.is_relative() { path.canonicalize().map_err(|e| { error!( "unable to canonicalize file from working dir {} with path {}: {}", env::current_dir() .map(|cwd| cwd.display().to_string()) .unwrap_or_else(|err| format!("??? ({err})")), path.display(), e ) }) } else { Ok(path) } } fn parse_from_json(json_abi: &str) -> Result { FullProgramABI::from_json_abi(json_abi) .map_err(|e| error!("malformed `abi`. Did you use `forc` to create it?: ").combine(e)) } pub fn path(&self) -> Option<&PathBuf> { self.path.as_ref() } pub fn abi(&self) -> &FullProgramABI { &self.abi } } impl FromStr for Abi { type Err = Error; fn from_str(json_abi: &str) -> Result { let abi = Abi::parse_from_json(json_abi)?; Ok(Abi { path: None, abi }) } } #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum ProgramType { Script, Contract, Predicate, } impl FromStr for ProgramType { type Err = Error; fn from_str(string: &str) -> std::result::Result { let program_type = match string { "Script" => ProgramType::Script, "Contract" => ProgramType::Contract, "Predicate" => ProgramType::Predicate, _ => { return Err(error!( "`{string}` is not a valid program type. Expected one of: `Script`, `Contract`, `Predicate`" )); } }; Ok(program_type) } } impl TryFrom for ProgramType { type Error = syn::Error; fn try_from(ident: Ident) -> std::result::Result { ident .to_string() .as_str() .parse() .map_err(|e| Self::Error::new(ident.span(), e)) } } ================================================ FILE: packages/fuels-code-gen/src/program_bindings/abigen/bindings/contract.rs ================================================ use fuel_abi_types::abi::full_program::{FullABIFunction, FullProgramABI}; use itertools::Itertools; use proc_macro2::{Ident, TokenStream}; use quote::{TokenStreamExt, quote}; use crate::{ error::Result, program_bindings::{ abigen::{ bindings::function_generator::FunctionGenerator, configurables::generate_code_for_configurable_constants, logs::{generate_id_error_codes_pairs, log_formatters_instantiation_code}, }, generated_code::GeneratedCode, }, utils::{TypePath, ident}, }; pub(crate) fn contract_bindings( name: &Ident, abi: FullProgramABI, no_std: bool, ) -> Result { if no_std { return Ok(GeneratedCode::default()); } let log_formatters = log_formatters_instantiation_code(quote! {contract_id.clone().into()}, &abi.logged_types); let error_codes = generate_id_error_codes_pairs(abi.error_codes); let error_codes = quote! {::std::collections::HashMap::from([#(#error_codes),*])}; let methods_name = ident(&format!("{name}Methods")); let contract_methods_name = ident(&format!("{name}MethodVariants")); let contract_functions = expand_functions(&abi.functions)?; let constant_methods_code = generate_constant_methods_pattern(&abi.functions, &contract_methods_name)?; let configuration_struct_name = ident(&format!("{name}Configurables")); let constant_configuration_code = generate_code_for_configurable_constants(&configuration_struct_name, &abi.configurables)?; let code = quote! { #[derive(Debug, Clone)] pub struct #name { contract_id: ::fuels::types::ContractId, account: A, log_decoder: ::fuels::core::codec::LogDecoder, encoder_config: ::fuels::core::codec::EncoderConfig, } impl #name { pub const METHODS: #contract_methods_name = #contract_methods_name; } impl #name { pub fn new( contract_id: ::fuels::types::ContractId, account: A, ) -> Self { let log_decoder = ::fuels::core::codec::LogDecoder::new(#log_formatters, #error_codes); let encoder_config = ::fuels::core::codec::EncoderConfig::default(); Self { contract_id, account, log_decoder, encoder_config } } pub fn contract_id(&self) -> ::fuels::types::ContractId { self.contract_id } pub fn account(&self) -> &A { &self.account } pub fn with_account(self, account: U) -> #name { #name { contract_id: self.contract_id, account, log_decoder: self.log_decoder, encoder_config: self.encoder_config } } pub fn with_encoder_config(mut self, encoder_config: ::fuels::core::codec::EncoderConfig) -> #name:: { self.encoder_config = encoder_config; self } pub async fn get_balances(&self) -> ::fuels::types::errors::Result<::std::collections::HashMap<::fuels::types::AssetId, u64>> where A: ::fuels::accounts::ViewOnlyAccount { ::fuels::accounts::ViewOnlyAccount::try_provider(&self.account)? .get_contract_balances(&self.contract_id) .await .map_err(::std::convert::Into::into) } pub fn methods(&self) -> #methods_name where A: Clone { #methods_name { contract_id: self.contract_id.clone(), account: self.account.clone(), log_decoder: self.log_decoder.clone(), encoder_config: self.encoder_config.clone(), } } } // Implement struct that holds the contract methods pub struct #methods_name { contract_id: ::fuels::types::ContractId, account: A, log_decoder: ::fuels::core::codec::LogDecoder, encoder_config: ::fuels::core::codec::EncoderConfig, } impl #methods_name { #contract_functions } impl ::fuels::programs::calls::ContractDependency for #name { fn id(&self) -> ::fuels::types::ContractId { self.contract_id } fn log_decoder(&self) -> ::fuels::core::codec::LogDecoder { self.log_decoder.clone() } } #constant_configuration_code #constant_methods_code }; // All publicly available types generated above should be listed here. let type_paths = [ name, &methods_name, &configuration_struct_name, &contract_methods_name, ] .map(|type_name| TypePath::new(type_name).expect("We know the given types are not empty")) .into_iter() .collect(); Ok(GeneratedCode::new(code, type_paths, no_std)) } fn expand_functions(functions: &[FullABIFunction]) -> Result { functions .iter() .map(expand_fn) .fold_ok(TokenStream::default(), |mut all_code, code| { all_code.append_all(code); all_code }) } /// Transforms a function defined in [`FullABIFunction`] into a [`TokenStream`] /// that represents that same function signature as a Rust-native function /// declaration. pub(crate) fn expand_fn(abi_fun: &FullABIFunction) -> Result { let mut generator = FunctionGenerator::new(abi_fun)?; generator.set_docs(abi_fun.doc_strings()?); let original_output = generator.output_type(); generator.set_output_type( quote! {::fuels::programs::calls::CallHandler }, ); let fn_selector = generator.fn_selector(); let arg_tokens = generator.tokenized_args(); let is_payable = abi_fun.is_payable(); let body = quote! { ::fuels::programs::calls::CallHandler::new_contract_call( self.contract_id.clone(), self.account.clone(), #fn_selector, &#arg_tokens, self.log_decoder.clone(), #is_payable, self.encoder_config.clone(), ) }; generator.set_body(body); Ok(generator.generate()) } fn generate_constant_methods_pattern( functions: &[FullABIFunction], contract_methods_name: &Ident, ) -> Result { let method_descriptors = functions.iter().map(|func| { let method_name = ident(func.name()); let fn_name = func.name(); let fn_selector = proc_macro2::Literal::byte_string(&crate::utils::encode_fn_selector(fn_name)); quote! { pub const fn #method_name(&self) -> ::fuels::types::MethodDescriptor { ::fuels::types::MethodDescriptor { name: #fn_name, fn_selector: #fn_selector, } } } }); let all_methods = functions.iter().map(|func| { let method_name = ident(func.name()); quote! { Self.#method_name() } }); let method_count = functions.len(); let code = quote! { #[derive(Debug, Clone, Copy)] pub struct #contract_methods_name; impl #contract_methods_name { #(#method_descriptors)* pub const fn iter(&self) -> [::fuels::types::MethodDescriptor; #method_count] { [#(#all_methods),*] } } }; Ok(code) } #[cfg(test)] mod tests { use std::collections::HashMap; use fuel_abi_types::abi::{ full_program::FullABIFunction, program::Attribute, unified_program::{UnifiedABIFunction, UnifiedTypeApplication, UnifiedTypeDeclaration}, }; use pretty_assertions::assert_eq; use quote::quote; use crate::{error::Result, program_bindings::abigen::bindings::contract::expand_fn}; #[test] fn expand_contract_method_simple() -> Result<()> { let the_function = UnifiedABIFunction { inputs: vec![UnifiedTypeApplication { name: String::from("bimbam"), type_id: 1, ..Default::default() }], name: "hello_world".to_string(), attributes: Some(vec![Attribute { name: "doc-comment".to_string(), arguments: vec!["This is a doc string".to_string()], }]), ..Default::default() }; let types = [ ( 0, UnifiedTypeDeclaration { type_id: 0, type_field: String::from("()"), ..Default::default() }, ), ( 1, UnifiedTypeDeclaration { type_id: 1, type_field: String::from("bool"), ..Default::default() }, ), ] .into_iter() .collect::>(); let result = expand_fn(&FullABIFunction::from_counterpart(&the_function, &types)?); let expected = quote! { #[doc = "This is a doc string"] pub fn hello_world(&self, bimbam: ::core::primitive::bool) -> ::fuels::programs::calls::CallHandler { ::fuels::programs::calls::CallHandler::new_contract_call( self.contract_id.clone(), self.account.clone(), ::fuels::core::codec::encode_fn_selector("hello_world"), &[::fuels::core::traits::Tokenizable::into_token(bimbam)], self.log_decoder.clone(), false, self.encoder_config.clone(), ) } }; assert_eq!(result?.to_string(), expected.to_string()); Ok(()) } #[test] fn expand_contract_method_complex() -> Result<()> { // given let the_function = UnifiedABIFunction { inputs: vec![UnifiedTypeApplication { name: String::from("the_only_allowed_input"), type_id: 4, ..Default::default() }], name: "hello_world".to_string(), output: UnifiedTypeApplication { name: String::from("stillnotused"), type_id: 1, ..Default::default() }, attributes: Some(vec![ Attribute { name: "doc-comment".to_string(), arguments: vec!["This is a doc string".to_string()], }, Attribute { name: "doc-comment".to_string(), arguments: vec!["This is another doc string".to_string()], }, ]), }; let types = [ ( 1, UnifiedTypeDeclaration { type_id: 1, type_field: String::from("enum EntropyCirclesEnum"), components: Some(vec![ UnifiedTypeApplication { name: String::from("Postcard"), type_id: 2, ..Default::default() }, UnifiedTypeApplication { name: String::from("Teacup"), type_id: 3, ..Default::default() }, ]), ..Default::default() }, ), ( 2, UnifiedTypeDeclaration { type_id: 2, type_field: String::from("bool"), ..Default::default() }, ), ( 3, UnifiedTypeDeclaration { type_id: 3, type_field: String::from("u64"), ..Default::default() }, ), ( 4, UnifiedTypeDeclaration { type_id: 4, type_field: String::from("struct SomeWeirdFrenchCuisine"), components: Some(vec![ UnifiedTypeApplication { name: String::from("Beef"), type_id: 2, ..Default::default() }, UnifiedTypeApplication { name: String::from("BurgundyWine"), type_id: 3, ..Default::default() }, ]), ..Default::default() }, ), ] .into_iter() .collect::>(); // when let result = expand_fn(&FullABIFunction::from_counterpart(&the_function, &types)?); // then // Some more editing was required because it is not rustfmt-compatible (adding/removing parentheses or commas) let expected = quote! { #[doc = "This is a doc string"] #[doc = "This is another doc string"] pub fn hello_world( &self, the_only_allowed_input: self::SomeWeirdFrenchCuisine ) -> ::fuels::programs::calls::CallHandler { ::fuels::programs::calls::CallHandler::new_contract_call( self.contract_id.clone(), self.account.clone(), ::fuels::core::codec::encode_fn_selector( "hello_world"), &[::fuels::core::traits::Tokenizable::into_token( the_only_allowed_input )], self.log_decoder.clone(), false, self.encoder_config.clone(), ) } }; assert_eq!(result?.to_string(), expected.to_string()); Ok(()) } } ================================================ FILE: packages/fuels-code-gen/src/program_bindings/abigen/bindings/function_generator.rs ================================================ use fuel_abi_types::abi::full_program::FullABIFunction; use proc_macro2::TokenStream; use quote::{ToTokens, quote}; use crate::{ error::Result, program_bindings::{ resolved_type::TypeResolver, utils::{Component, Components}, }, utils::{TypePath, safe_ident}, }; #[derive(Debug)] pub(crate) struct FunctionGenerator { name: String, args: Components, output_type: TokenStream, body: TokenStream, docs: Vec, } impl FunctionGenerator { pub fn new(fun: &FullABIFunction) -> Result { // All abi-method-calling Rust functions are currently generated at the top-level-mod of // the Program in question (e.g. abigen_bindings::my_contract_mod`). If we ever nest // these functions in a deeper mod we would need to propagate the mod to here instead of // just hard-coding the default path. let args = Components::new(fun.inputs(), true, TypePath::default())?; // We are not checking that the ABI contains non-SDK supported types so that the user can // still interact with an ABI even if some methods will fail at runtime. let output_type = TypeResolver::default().resolve(fun.output())?; Ok(Self { name: fun.name().to_string(), args, output_type: output_type.to_token_stream(), body: Default::default(), docs: vec![], }) } pub fn set_name(&mut self, name: String) -> &mut Self { self.name = name; self } pub fn set_body(&mut self, body: TokenStream) -> &mut Self { self.body = body; self } pub fn set_docs(&mut self, docs: Vec) -> &mut Self { self.docs = docs; self } pub fn fn_selector(&self) -> TokenStream { let name = &self.name; quote! {::fuels::core::codec::encode_fn_selector(#name)} } pub fn tokenized_args(&self) -> TokenStream { let arg_names = self.args.iter().map(|Component { ident, .. }| { quote! {#ident} }); quote! {[#(::fuels::core::traits::Tokenizable::into_token(#arg_names)),*]} } pub fn set_output_type(&mut self, output_type: TokenStream) -> &mut Self { self.output_type = output_type; self } pub fn output_type(&self) -> &TokenStream { &self.output_type } pub fn generate(&self) -> TokenStream { let name = safe_ident(&self.name); let docs: Vec = self .docs .iter() .map(|doc| { quote! { #[doc = #doc] } }) .collect(); let arg_declarations = self.args.iter().map( |Component { ident, resolved_type, .. }| { quote! { #ident: #resolved_type } }, ); let output_type = self.output_type(); let body = &self.body; let params = quote! { &self, #(#arg_declarations),* }; quote! { #(#docs)* pub fn #name(#params) -> #output_type { #body } } } } #[cfg(test)] mod tests { use fuel_abi_types::abi::full_program::{FullTypeApplication, FullTypeDeclaration}; use pretty_assertions::assert_eq; use super::*; #[test] fn correct_fn_selector_resolving_code() -> Result<()> { let function = given_a_fun(); let sut = FunctionGenerator::new(&function)?; let fn_selector_code = sut.fn_selector(); let expected = quote! { ::fuels::core::codec::encode_fn_selector("test_function") }; assert_eq!(fn_selector_code.to_string(), expected.to_string()); Ok(()) } #[test] fn correct_tokenized_args() -> Result<()> { let function = given_a_fun(); let sut = FunctionGenerator::new(&function)?; let tokenized_args = sut.tokenized_args(); assert_eq!( tokenized_args.to_string(), "[:: fuels :: core :: traits :: Tokenizable :: into_token (arg_0)]" ); Ok(()) } #[test] fn tokenizes_correctly() -> Result<()> { // given let function = given_a_fun(); let mut sut = FunctionGenerator::new(&function)?; sut.set_docs(vec![ " This is a doc".to_string(), " This is another doc".to_string(), ]) .set_body(quote! {this is ze body}); // when let tokenized: TokenStream = sut.generate(); // then let expected = quote! { #[doc = " This is a doc"] #[doc = " This is another doc"] pub fn test_function(&self, arg_0: self::CustomStruct<::core::primitive::u8>) -> self::CustomStruct<::core::primitive::u64> { this is ze body } }; // then assert_eq!(tokenized.to_string(), expected.to_string()); Ok(()) } fn given_a_fun() -> FullABIFunction { let generic_type_t = FullTypeDeclaration { type_field: "generic T".to_string(), components: vec![], type_parameters: vec![], alias_of: None, }; let custom_struct_type = FullTypeDeclaration { type_field: "struct CustomStruct".to_string(), components: vec![FullTypeApplication { name: "field_a".to_string(), type_decl: generic_type_t.clone(), type_arguments: vec![], error_message: None, }], type_parameters: vec![generic_type_t], alias_of: None, }; let fn_output = FullTypeApplication { name: "".to_string(), type_decl: custom_struct_type.clone(), type_arguments: vec![FullTypeApplication { name: "".to_string(), type_decl: FullTypeDeclaration { type_field: "u64".to_string(), components: vec![], type_parameters: vec![], alias_of: None, }, type_arguments: vec![], error_message: None, }], error_message: None, }; let fn_inputs = vec![FullTypeApplication { name: "arg_0".to_string(), type_decl: custom_struct_type, type_arguments: vec![FullTypeApplication { name: "".to_string(), type_decl: FullTypeDeclaration { type_field: "u8".to_string(), components: vec![], type_parameters: vec![], alias_of: None, }, type_arguments: vec![], error_message: None, }], error_message: None, }]; FullABIFunction::new("test_function".to_string(), fn_inputs, fn_output, vec![]) .expect("Hand crafted function known to be correct") } } ================================================ FILE: packages/fuels-code-gen/src/program_bindings/abigen/bindings/predicate.rs ================================================ use fuel_abi_types::abi::full_program::{FullABIFunction, FullProgramABI}; use proc_macro2::{Ident, TokenStream}; use quote::quote; use crate::{ error::Result, program_bindings::{ abigen::{ bindings::{function_generator::FunctionGenerator, utils::extract_main_fn}, configurables::generate_code_for_configurable_constants, }, generated_code::GeneratedCode, }, utils::{TypePath, ident}, }; pub(crate) fn predicate_bindings( name: &Ident, abi: FullProgramABI, no_std: bool, ) -> Result { let main_function_abi = extract_main_fn(&abi.functions)?; let encode_function = expand_fn(main_function_abi)?; let encoder_struct_name = ident(&format!("{name}Encoder")); let configuration_struct_name = ident(&format!("{name}Configurables")); let constant_configuration_code = generate_code_for_configurable_constants(&configuration_struct_name, &abi.configurables)?; let code = quote! { #[derive(Default)] pub struct #encoder_struct_name{ encoder: ::fuels::core::codec::ABIEncoder, } impl #encoder_struct_name { #encode_function pub fn new(encoder_config: ::fuels::core::codec::EncoderConfig) -> Self { Self { encoder: ::fuels::core::codec::ABIEncoder::new(encoder_config) } } } #constant_configuration_code }; // All publicly available types generated above should be listed here. let type_paths = [&encoder_struct_name, &configuration_struct_name] .map(|type_name| TypePath::new(type_name).expect("We know the given types are not empty")) .into_iter() .collect(); Ok(GeneratedCode::new(code, type_paths, no_std)) } fn expand_fn(fn_abi: &FullABIFunction) -> Result { let mut generator = FunctionGenerator::new(fn_abi)?; let arg_tokens = generator.tokenized_args(); let body = quote! { self.encoder.encode(&#arg_tokens) }; let output_type = quote! { ::fuels::types::errors::Result<::std::vec::Vec> }; generator .set_docs(vec!["Encode the predicate arguments".to_string()]) .set_name("encode_data".to_string()) .set_output_type(output_type) .set_body(body); Ok(generator.generate()) } ================================================ FILE: packages/fuels-code-gen/src/program_bindings/abigen/bindings/script.rs ================================================ use std::default::Default; use fuel_abi_types::abi::full_program::{FullABIFunction, FullProgramABI}; use proc_macro2::{Ident, TokenStream}; use quote::quote; use crate::{ error::Result, program_bindings::{ abigen::{ bindings::{function_generator::FunctionGenerator, utils::extract_main_fn}, configurables::generate_code_for_configurable_constants, logs::{generate_id_error_codes_pairs, log_formatters_instantiation_code}, }, generated_code::GeneratedCode, }, utils::{TypePath, ident}, }; pub(crate) fn script_bindings( name: &Ident, abi: FullProgramABI, no_std: bool, ) -> Result { if no_std { return Ok(GeneratedCode::default()); } let main_function_abi = extract_main_fn(&abi.functions)?; let main_function = expand_fn(main_function_abi)?; let log_formatters = log_formatters_instantiation_code( quote! {::fuels::types::ContractId::zeroed()}, &abi.logged_types, ); let error_codes = generate_id_error_codes_pairs(abi.error_codes); let error_codes = quote! {vec![#(#error_codes),*].into_iter().collect()}; let configuration_struct_name = ident(&format!("{name}Configurables")); let constant_configuration_code = generate_code_for_configurable_constants(&configuration_struct_name, &abi.configurables)?; let code = quote! { #[derive(Debug,Clone)] pub struct #name{ account: A, unconfigured_binary: ::std::vec::Vec, configurables: ::fuels::core::Configurables, converted_into_loader: bool, log_decoder: ::fuels::core::codec::LogDecoder, encoder_config: ::fuels::core::codec::EncoderConfig, } impl #name { pub fn new(account: A, binary_filepath: &str) -> Self { let binary = ::std::fs::read(binary_filepath) .expect(&format!("could not read script binary {binary_filepath:?}")); Self { account, unconfigured_binary: binary, configurables: ::core::default::Default::default(), converted_into_loader: false, log_decoder: ::fuels::core::codec::LogDecoder::new(#log_formatters, #error_codes), encoder_config: ::fuels::core::codec::EncoderConfig::default(), } } pub fn with_account(self, account: U) -> #name { #name { account, unconfigured_binary: self.unconfigured_binary, log_decoder: self.log_decoder, encoder_config: self.encoder_config, configurables: self.configurables, converted_into_loader: self.converted_into_loader, } } pub fn with_configurables(mut self, configurables: impl Into<::fuels::core::Configurables>) -> Self { self.configurables = configurables.into(); self } pub fn code(&self) -> ::std::vec::Vec { let regular = ::fuels::programs::executable::Executable::from_bytes(self.unconfigured_binary.clone()).with_configurables(self.configurables.clone()); if self.converted_into_loader { let loader = regular.convert_to_loader().expect("cannot fail since we already converted to the loader successfully"); loader.code() } else { regular.code() } } pub fn account(&self) -> &A { &self.account } pub fn with_encoder_config(mut self, encoder_config: ::fuels::core::codec::EncoderConfig) -> Self { self.encoder_config = encoder_config; self } pub fn log_decoder(&self) -> ::fuels::core::codec::LogDecoder { self.log_decoder.clone() } /// Will upload the script code as a blob to the network and change the script code /// into a loader that will fetch the blob and load it into memory before executing the /// code inside. Allows you to optimize fees by paying for most of the code once and /// then just running a small loader. pub async fn convert_into_loader(&mut self) -> ::fuels::types::errors::Result<&mut Self> where A: ::fuels::accounts::Account + Clone { if !self.converted_into_loader { let regular = ::fuels::programs::executable::Executable::from_bytes(self.unconfigured_binary.clone()).with_configurables(self.configurables.clone()); let loader = regular.convert_to_loader()?; loader.upload_blob(self.account.clone()).await?; self.converted_into_loader = true; } ::fuels::types::errors::Result::Ok(self) } } impl #name { #main_function } #constant_configuration_code }; // All publicly available types generated above should be listed here. let type_paths = [name, &configuration_struct_name] .map(|type_name| TypePath::new(type_name).expect("We know the given types are not empty")) .into_iter() .collect(); Ok(GeneratedCode::new(code, type_paths, no_std)) } fn expand_fn(fn_abi: &FullABIFunction) -> Result { let mut generator = FunctionGenerator::new(fn_abi)?; let arg_tokens = generator.tokenized_args(); let original_output_type = generator.output_type(); let body = quote! { let encoded_args = ::fuels::core::codec::ABIEncoder::new(self.encoder_config).encode(&#arg_tokens); ::fuels::programs::calls::CallHandler::new_script_call( self.code(), encoded_args, self.account.clone(), self.log_decoder.clone() ) }; generator .set_output_type(quote! {::fuels::programs::calls::CallHandler }) .set_docs(fn_abi.doc_strings()?) .set_body(body); Ok(generator.generate()) } #[cfg(test)] mod tests { use std::collections::HashMap; use fuel_abi_types::abi::{ full_program::FullABIFunction, program::Attribute, unified_program::{UnifiedABIFunction, UnifiedTypeApplication, UnifiedTypeDeclaration}, }; use pretty_assertions::assert_eq; use quote::quote; use crate::{error::Result, program_bindings::abigen::bindings::script::expand_fn}; #[test] fn expand_script_main_function() -> Result<()> { let the_function = UnifiedABIFunction { inputs: vec![UnifiedTypeApplication { name: String::from("bimbam"), type_id: 1, ..Default::default() }], name: "main".to_string(), attributes: Some(vec![ Attribute { name: "doc-comment".to_string(), arguments: vec!["This is a doc string".to_string()], }, Attribute { name: "doc-comment".to_string(), arguments: vec!["This is another doc string".to_string()], }, ]), ..Default::default() }; let types = [ ( 0, UnifiedTypeDeclaration { type_id: 0, type_field: String::from("()"), ..Default::default() }, ), ( 1, UnifiedTypeDeclaration { type_id: 1, type_field: String::from("bool"), ..Default::default() }, ), ] .into_iter() .collect::>(); let result = expand_fn(&FullABIFunction::from_counterpart(&the_function, &types)?); let expected = quote! { #[doc = "This is a doc string"] #[doc = "This is another doc string"] pub fn main(&self, bimbam: ::core::primitive::bool) -> ::fuels::programs::calls::CallHandler { let encoded_args=::fuels::core::codec::ABIEncoder::new(self.encoder_config) .encode(&[::fuels::core::traits::Tokenizable::into_token(bimbam)]); ::fuels::programs::calls::CallHandler::new_script_call( self.code(), encoded_args, self.account.clone(), self.log_decoder.clone() ) } }; assert_eq!(result?.to_string(), expected.to_string()); Ok(()) } } ================================================ FILE: packages/fuels-code-gen/src/program_bindings/abigen/bindings/utils.rs ================================================ use fuel_abi_types::abi::full_program::FullABIFunction; use crate::error::{Result, error}; pub(crate) fn extract_main_fn(abi: &[FullABIFunction]) -> Result<&FullABIFunction> { let candidates = abi .iter() .filter(|function| function.name() == "main") .collect::>(); match candidates.as_slice() { [single_main_fn] => Ok(single_main_fn), _ => { let fn_names = abi .iter() .map(|candidate| candidate.name()) .collect::>(); Err(error!( "`abi` must have only one function with the name 'main'. Got: {fn_names:?}" )) } } } #[cfg(test)] mod tests { use fuel_abi_types::abi::full_program::{FullTypeApplication, FullTypeDeclaration}; use super::*; #[test] fn correctly_extracts_the_main_fn() { let functions = ["fn_1", "main", "fn_2"].map(given_a_fun_named); let fun = extract_main_fn(&functions).expect("should have succeeded"); assert_eq!(*fun, functions[1]); } #[test] fn fails_if_there_is_more_than_one_main_fn() { let functions = ["main", "another", "main"].map(given_a_fun_named); let err = extract_main_fn(&functions).expect_err("should have failed"); assert_eq!( err.to_string(), r#"`abi` must have only one function with the name 'main'. Got: ["main", "another", "main"]"# ); } fn given_a_fun_named(fn_name: &str) -> FullABIFunction { FullABIFunction::new( fn_name.to_string(), vec![], FullTypeApplication { name: "".to_string(), type_decl: FullTypeDeclaration { type_field: "".to_string(), components: vec![], type_parameters: vec![], alias_of: None, }, type_arguments: vec![], error_message: None, }, vec![], ) .expect("hand-crafted, should not fail!") } } ================================================ FILE: packages/fuels-code-gen/src/program_bindings/abigen/bindings.rs ================================================ use crate::{ error::Result, program_bindings::{ abigen::{ ProgramType, abigen_target::AbigenTarget, bindings::{ contract::contract_bindings, predicate::predicate_bindings, script::script_bindings, }, }, generated_code::GeneratedCode, }, utils::ident, }; mod contract; mod function_generator; mod predicate; mod script; mod utils; pub(crate) fn generate_bindings(target: AbigenTarget, no_std: bool) -> Result { let bindings_generator = match target.program_type { ProgramType::Script => script_bindings, ProgramType::Contract => contract_bindings, ProgramType::Predicate => predicate_bindings, }; let name = ident(&target.name); let abi = target.source.abi; bindings_generator(&name, abi, no_std) } ================================================ FILE: packages/fuels-code-gen/src/program_bindings/abigen/configurables.rs ================================================ use fuel_abi_types::abi::full_program::FullConfigurable; use proc_macro2::{Ident, TokenStream}; use quote::quote; use crate::{ error::Result, program_bindings::resolved_type::{ResolvedType, TypeResolver}, utils::safe_ident, }; #[derive(Debug)] pub(crate) struct ResolvedConfigurable { pub name: Ident, pub ttype: ResolvedType, pub offset: u64, } impl ResolvedConfigurable { pub fn new(configurable: &FullConfigurable) -> Result { let type_application = &configurable.application; Ok(ResolvedConfigurable { name: safe_ident(&format!("with_{}", configurable.name)), ttype: TypeResolver::default().resolve(type_application)?, offset: configurable.offset, }) } } pub(crate) fn generate_code_for_configurable_constants( configurable_struct_name: &Ident, configurables: &[FullConfigurable], ) -> Result { let resolved_configurables = configurables .iter() .map(ResolvedConfigurable::new) .collect::>>()?; let struct_decl = generate_struct_decl(configurable_struct_name); let struct_impl = generate_struct_impl(configurable_struct_name, &resolved_configurables); let from_impl = generate_from_impl(configurable_struct_name); Ok(quote! { #struct_decl #struct_impl #from_impl }) } fn generate_struct_decl(configurable_struct_name: &Ident) -> TokenStream { quote! { #[derive(Clone, Debug, Default)] pub struct #configurable_struct_name { offsets_with_data: ::std::vec::Vec<::fuels::core::Configurable>, encoder: ::fuels::core::codec::ABIEncoder, } } } fn generate_struct_impl( configurable_struct_name: &Ident, resolved_configurables: &[ResolvedConfigurable], ) -> TokenStream { let builder_methods = generate_builder_methods(resolved_configurables); quote! { impl #configurable_struct_name { pub fn new(encoder_config: ::fuels::core::codec::EncoderConfig) -> Self { Self { encoder: ::fuels::core::codec::ABIEncoder::new(encoder_config), ..::std::default::Default::default() } } #builder_methods } } } fn generate_builder_methods(resolved_configurables: &[ResolvedConfigurable]) -> TokenStream { let methods = resolved_configurables.iter().map( |ResolvedConfigurable { name, ttype, offset, }| { let encoder_code = generate_encoder_code(ttype); quote! { #[allow(non_snake_case)] // Generate the `with_XXX` methods for setting the configurables pub fn #name(mut self, value: #ttype) -> ::fuels::prelude::Result { let encoded = #encoder_code?; self.offsets_with_data.push(::fuels::core::Configurable { offset: #offset, data: encoded, }); ::fuels::prelude::Result::Ok(self) } } }, ); quote! { #(#methods)* } } fn generate_encoder_code(ttype: &ResolvedType) -> TokenStream { quote! { self.encoder.encode(&[ <#ttype as ::fuels::core::traits::Tokenizable>::into_token(value) ]) } } fn generate_from_impl(configurable_struct_name: &Ident) -> TokenStream { quote! { impl From<#configurable_struct_name> for ::fuels::core::Configurables { fn from(config: #configurable_struct_name) -> Self { ::fuels::core::Configurables::new(config.offsets_with_data) } } impl From<#configurable_struct_name> for ::std::vec::Vec<::fuels::core::Configurable> { fn from(config: #configurable_struct_name) -> ::std::vec::Vec<::fuels::core::Configurable> { config.offsets_with_data } } } } ================================================ FILE: packages/fuels-code-gen/src/program_bindings/abigen/logs.rs ================================================ use fuel_abi_types::abi::full_program::FullLoggedType; use proc_macro2::TokenStream; use quote::quote; use crate::program_bindings::resolved_type::TypeResolver; pub(crate) fn log_formatters_instantiation_code( contract_id: TokenStream, logged_types: &[FullLoggedType], ) -> TokenStream { let resolved_logs = resolve_logs(logged_types); let log_id_log_formatter_pairs = generate_log_id_log_formatter_pairs(&resolved_logs); quote! {::fuels::core::codec::log_formatters_lookup(vec![#(#log_id_log_formatter_pairs),*], #contract_id)} } #[derive(Debug)] struct ResolvedLog { log_id: String, log_formatter: TokenStream, } /// Reads the parsed logged types from the ABI and creates ResolvedLogs fn resolve_logs(logged_types: &[FullLoggedType]) -> Vec { logged_types .iter() .map(|l| { let resolved_type = TypeResolver::default() .resolve(&l.application) .expect("Failed to resolve log type"); let is_error_type = l .application .type_decl .components .iter() .any(|component| component.error_message.is_some()); let log_formatter = if is_error_type { quote! { ::fuels::core::codec::LogFormatter::new_error::<#resolved_type>() } } else { quote! { ::fuels::core::codec::LogFormatter::new_log::<#resolved_type>() } }; ResolvedLog { log_id: l.log_id.clone(), log_formatter, } }) .collect() } fn generate_log_id_log_formatter_pairs( resolved_logs: &[ResolvedLog], ) -> impl Iterator { resolved_logs.iter().map(|r| { let id = &r.log_id; let log_formatter = &r.log_formatter; quote! { (#id.to_string(), #log_formatter) } }) } pub(crate) fn generate_id_error_codes_pairs( error_codes: impl IntoIterator, ) -> impl Iterator { error_codes.into_iter().map(|(id, ed)| { let pkg = ed.pos.pkg; let file = ed.pos.file; let line = ed.pos.line; let column = ed.pos.column; let log_id = ed.log_id.map_or( quote! {::core::option::Option::None}, |l| quote! {::core::option::Option::Some(#l.to_string())}, ); let msg = ed.msg.map_or( quote! {::core::option::Option::None}, |m| quote! {::core::option::Option::Some(#m.to_string())}, ); quote! { (#id, ::fuels::core::codec::ErrorDetails::new( #pkg.to_string(), #file.to_string(), #line, #column, #log_id, #msg ) ) } }) } ================================================ FILE: packages/fuels-code-gen/src/program_bindings/abigen.rs ================================================ use std::{collections::HashSet, path::PathBuf}; pub use abigen_target::{Abi, AbigenTarget, ProgramType}; use fuel_abi_types::abi::full_program::{FullLoggedType, FullTypeDeclaration}; use inflector::Inflector; use itertools::Itertools; use proc_macro2::TokenStream; use quote::quote; use regex::Regex; use crate::{ error::Result, program_bindings::{ abigen::bindings::generate_bindings, custom_types::generate_types, generated_code::GeneratedCode, }, utils::ident, }; mod abigen_target; mod bindings; mod configurables; mod logs; pub struct Abigen; impl Abigen { /// Generate code which can be used to interact with the underlying /// contract, script or predicate in a type-safe manner. /// /// # Arguments /// /// * `targets`: `AbigenTargets` detailing which ABI to generate bindings /// for, and of what nature (Contract, Script or Predicate). /// * `no_std`: don't use the Rust std library. pub fn generate(targets: Vec, no_std: bool) -> Result { let generated_code = Self::generate_code(no_std, targets)?; let use_statements = generated_code.use_statements_for_uniquely_named_types(); let code = if no_std { Self::wasm_paths_hotfix(&generated_code.code()) } else { generated_code.code() }; Ok(quote! { #code #use_statements }) } fn wasm_paths_hotfix(code: &TokenStream) -> TokenStream { [ (r"::\s*std\s*::\s*string", "::alloc::string"), (r"::\s*std\s*::\s*format", "::alloc::format"), (r"::\s*std\s*::\s*vec", "::alloc::vec"), (r"::\s*std\s*::\s*boxed", "::alloc::boxed"), ] .map(|(reg_expr_str, substitute)| (Regex::new(reg_expr_str).unwrap(), substitute)) .into_iter() .fold(code.to_string(), |code, (regex, wasm_include)| { regex.replace_all(&code, wasm_include).to_string() }) .parse() .expect("Wasm hotfix failed!") } fn generate_code(no_std: bool, parsed_targets: Vec) -> Result { let custom_types = Self::filter_custom_types(&parsed_targets); let shared_types = Self::filter_shared_types(custom_types); let logged_types = parsed_targets .iter() .flat_map(|abi| abi.source.abi.logged_types.clone()) .collect_vec(); let bindings = Self::generate_all_bindings(parsed_targets, no_std, &shared_types)?; let shared_types = Self::generate_shared_types(shared_types, &logged_types, no_std)?; let mod_name = ident("abigen_bindings"); Ok(shared_types.merge(bindings).wrap_in_mod(mod_name)) } fn generate_all_bindings( targets: Vec, no_std: bool, shared_types: &HashSet, ) -> Result { targets .into_iter() .map(|target| Self::generate_binding(target, no_std, shared_types)) .fold_ok(GeneratedCode::default(), |acc, generated_code| { acc.merge(generated_code) }) } fn generate_binding( target: AbigenTarget, no_std: bool, shared_types: &HashSet, ) -> Result { let mod_name = ident(&format!("{}_mod", &target.name.to_snake_case())); let recompile_trigger = Self::generate_macro_recompile_trigger(target.source.path.as_ref(), no_std); let types = generate_types( &target.source.abi.types, shared_types, &target.source.abi.logged_types, no_std, )?; let bindings = generate_bindings(target, no_std)?; Ok(recompile_trigger .merge(types) .merge(bindings) .wrap_in_mod(mod_name)) } /// Any changes to the file pointed to by `path` will cause the reevaluation of the current /// procedural macro. This is a hack until /// lands. fn generate_macro_recompile_trigger(path: Option<&PathBuf>, no_std: bool) -> GeneratedCode { let code = path .as_ref() .map(|path| { let stringified_path = path.display().to_string(); quote! { const _: &[u8] = include_bytes!(#stringified_path); } }) .unwrap_or_default(); GeneratedCode::new(code, Default::default(), no_std) } fn generate_shared_types( shared_types: HashSet, logged_types: &Vec, no_std: bool, ) -> Result { let types = generate_types(&shared_types, &HashSet::default(), logged_types, no_std)?; if types.is_empty() { Ok(Default::default()) } else { let mod_name = ident("shared_types"); Ok(types.wrap_in_mod(mod_name)) } } fn filter_custom_types( all_types: &[AbigenTarget], ) -> impl Iterator { all_types .iter() .flat_map(|target| &target.source.abi.types) .filter(|ttype| ttype.is_custom_type()) } /// A type is considered "shared" if it appears at least twice in /// `all_custom_types`. /// /// # Arguments /// /// * `all_custom_types`: types from all ABIs whose bindings are being /// generated. fn filter_shared_types<'a>( all_custom_types: impl IntoIterator, ) -> HashSet { all_custom_types.into_iter().duplicates().cloned().collect() } } #[cfg(test)] mod tests { use super::*; #[test] fn correctly_determines_shared_types() { let types = ["type_0", "type_1", "type_0"].map(|type_field| FullTypeDeclaration { type_field: type_field.to_string(), components: vec![], type_parameters: vec![], alias_of: None, }); let shared_types = Abigen::filter_shared_types(&types); assert_eq!(shared_types, HashSet::from([types[0].clone()])) } } ================================================ FILE: packages/fuels-code-gen/src/program_bindings/custom_types/enums.rs ================================================ use std::collections::HashSet; use fuel_abi_types::abi::full_program::FullTypeDeclaration; use proc_macro2::{Ident, TokenStream}; use quote::quote; use crate::{ error::{Result, error}, program_bindings::{ custom_types::utils::extract_generic_parameters, generated_code::GeneratedCode, resolved_type::ResolvedType, utils::{Component, Components, tokenize_generics}, }, }; /// Returns a TokenStream containing the declaration, `Parameterize`, /// `Tokenizable` and `TryFrom` implementations for the enum described by the /// given TypeDeclaration. pub(crate) fn expand_custom_enum( type_decl: &FullTypeDeclaration, no_std: bool, log_id: Option<&String>, ) -> Result { let enum_type_path = type_decl.custom_type_path()?; let enum_ident = enum_type_path.ident().unwrap(); let components = Components::new(&type_decl.components, false, enum_type_path.parent())?; if components.is_empty() { return Err(error!("enum must have at least one component")); } let generics = extract_generic_parameters(type_decl); let code = enum_decl(enum_ident, &components, &generics, no_std, log_id); let enum_code = GeneratedCode::new(code, HashSet::from([enum_ident.into()]), no_std); Ok(enum_code.wrap_in_mod(enum_type_path.parent())) } fn maybe_impl_error(enum_ident: &Ident, components: &Components) -> Option { components.has_error_messages().then(|| { let display_match_branches = components.iter().map(|Component{ident, resolved_type, error_message}| { let error_msg = error_message.as_deref().expect("error message is there - checked above"); if let ResolvedType::Unit = resolved_type { quote! {#enum_ident::#ident => ::std::write!(f, "{}", #error_msg)} } else { quote! {#enum_ident::#ident(val) => ::std::write!(f, "{}: {:?}", #error_msg, val)} } }); let custom_display_impl = quote! { impl ::std::fmt::Display for #enum_ident { fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result { match &self { #(#display_match_branches,)* } } } }; quote! { #custom_display_impl impl ::std::error::Error for #enum_ident{} } }) } fn enum_decl( enum_ident: &Ident, components: &Components, generics: &[Ident], no_std: bool, log_id: Option<&String>, ) -> TokenStream { let maybe_disable_std = no_std.then(|| quote! {#[NoStd]}); let enum_variants = components.as_enum_variants(); let unused_generics_variant = components.generate_variant_for_unused_generics(generics); let (generics_wo_bounds, generics_w_bounds) = tokenize_generics(generics); let maybe_impl_error = maybe_impl_error(enum_ident, components); let log_impl = log_id.map(|log_id| { let log_id_u64: u64 = log_id .parse::() .expect("log id should be a valid u64 string"); quote! { impl #generics_w_bounds ::fuels::core::codec::Log for #enum_ident #generics_wo_bounds { const LOG_ID: &'static str = #log_id; const LOG_ID_U64: u64 = #log_id_u64; } } }); quote! { #[allow(clippy::enum_variant_names)] #[derive( Clone, Debug, Eq, PartialEq, ::fuels::macros::Parameterize, ::fuels::macros::Tokenizable, ::fuels::macros::TryFrom, )] #maybe_disable_std pub enum #enum_ident #generics_w_bounds { #(#enum_variants,)* #unused_generics_variant } #maybe_impl_error #log_impl } } ================================================ FILE: packages/fuels-code-gen/src/program_bindings/custom_types/structs.rs ================================================ use std::collections::HashSet; use fuel_abi_types::abi::full_program::FullTypeDeclaration; use proc_macro2::{Ident, TokenStream}; use quote::quote; use crate::{ error::Result, program_bindings::{ custom_types::utils::extract_generic_parameters, generated_code::GeneratedCode, resolved_type::ResolvedType, utils::{Component, Components, tokenize_generics}, }, }; /// Returns a TokenStream containing the declaration, `Parameterize`, /// `Tokenizable` and `TryFrom` implementations for the struct described by the /// given TypeDeclaration. pub(crate) fn expand_custom_struct( type_decl: &FullTypeDeclaration, no_std: bool, log_id: Option<&String>, ) -> Result { let struct_type_path = type_decl.custom_type_path()?; let struct_ident = struct_type_path.ident().unwrap(); let components = Components::new(&type_decl.components, true, struct_type_path.parent())?; let generic_parameters = extract_generic_parameters(type_decl); let code = struct_decl( struct_ident, &components, &generic_parameters, no_std, log_id, ); let struct_code = GeneratedCode::new(code, HashSet::from([struct_ident.into()]), no_std); Ok(struct_code.wrap_in_mod(struct_type_path.parent())) } fn unzip_field_names_and_types(components: &Components) -> (Vec<&Ident>, Vec<&ResolvedType>) { components .iter() .map( |Component { ident, resolved_type, .. }| (ident, resolved_type), ) .unzip() } fn struct_decl( struct_ident: &Ident, components: &Components, generics: &[Ident], no_std: bool, log_id: Option<&String>, ) -> TokenStream { let derive_default = components .is_empty() .then(|| quote!(::core::default::Default,)); let maybe_disable_std = no_std.then(|| quote! {#[NoStd]}); let (generics_wo_bounds, generics_w_bounds) = tokenize_generics(generics); let (field_names, field_types): (Vec<_>, Vec<_>) = unzip_field_names_and_types(components); let (phantom_fields, phantom_types) = components.generate_parameters_for_unused_generics(generics); let log_impl = log_id.map(|log_id| { let log_id_u64: u64 = log_id.parse::().expect("log id should be a valid u64 string"); quote! { impl #generics_w_bounds ::fuels::core::codec::Log for #struct_ident #generics_wo_bounds { const LOG_ID: &'static str = #log_id; const LOG_ID_U64: u64 = #log_id_u64; } } }); quote! { #[derive( Clone, Debug, Eq, PartialEq, #derive_default ::fuels::macros::Parameterize, ::fuels::macros::Tokenizable, ::fuels::macros::TryFrom, )] #maybe_disable_std pub struct #struct_ident #generics_w_bounds { #( pub #field_names: #field_types, )* #(#[Ignore] pub #phantom_fields: #phantom_types, )* } impl #generics_w_bounds #struct_ident #generics_wo_bounds { pub fn new(#(#field_names: #field_types,)*) -> Self { Self { #(#field_names,)* #(#phantom_fields: ::core::default::Default::default(),)* } } } #log_impl } } ================================================ FILE: packages/fuels-code-gen/src/program_bindings/custom_types/utils.rs ================================================ use fuel_abi_types::{ abi::full_program::FullTypeDeclaration, utils::{self, extract_generic_name}, }; use proc_macro2::Ident; /// Returns a vector of TokenStreams, one for each of the generic parameters /// used by the given type. pub(crate) fn extract_generic_parameters(type_decl: &FullTypeDeclaration) -> Vec { type_decl .type_parameters .iter() .map(|decl| { let name = extract_generic_name(&decl.type_field).unwrap_or_else(|| { panic!("Type parameters should only contain ids of generic types!") }); utils::ident(&name) }) .collect() } #[cfg(test)] mod tests { use fuel_abi_types::{ abi::unified_program::UnifiedTypeDeclaration, utils::extract_custom_type_name, }; use pretty_assertions::assert_eq; use super::*; use crate::error::Result; #[test] fn extracts_generic_types() -> Result<()> { // given let declaration = UnifiedTypeDeclaration { type_id: 0, type_field: "".to_string(), components: None, type_parameters: Some(vec![1, 2]), alias_of: None, }; let generic_1 = UnifiedTypeDeclaration { type_id: 1, type_field: "generic T".to_string(), components: None, type_parameters: None, alias_of: None, }; let generic_2 = UnifiedTypeDeclaration { type_id: 2, type_field: "generic K".to_string(), components: None, type_parameters: None, alias_of: None, }; let types = [generic_1, generic_2] .map(|decl| (decl.type_id, decl)) .into_iter() .collect(); // when let generics = extract_generic_parameters(&FullTypeDeclaration::from_counterpart( &declaration, &types, )); // then let stringified_generics = generics .into_iter() .map(|generic| generic.to_string()) .collect::>(); assert_eq!(stringified_generics, vec!["T", "K"]); Ok(()) } #[test] fn can_extract_struct_name() { let declaration = UnifiedTypeDeclaration { type_id: 0, type_field: "struct SomeName".to_string(), components: None, type_parameters: None, alias_of: None, }; let struct_name = extract_custom_type_name(&declaration.type_field).unwrap(); assert_eq!(struct_name, "SomeName"); } #[test] fn can_extract_enum_name() { let declaration = UnifiedTypeDeclaration { type_id: 0, type_field: "enum SomeEnumName".to_string(), components: None, type_parameters: None, alias_of: None, }; let struct_name = extract_custom_type_name(&declaration.type_field).unwrap(); assert_eq!(struct_name, "SomeEnumName"); } } ================================================ FILE: packages/fuels-code-gen/src/program_bindings/custom_types.rs ================================================ use std::collections::{HashMap, HashSet}; use fuel_abi_types::abi::full_program::{FullLoggedType, FullTypeDeclaration}; use itertools::Itertools; use quote::quote; use crate::{ error::Result, program_bindings::{ custom_types::{enums::expand_custom_enum, structs::expand_custom_struct}, generated_code::GeneratedCode, utils::sdk_provided_custom_types_lookup, }, utils::TypePath, }; mod enums; mod structs; pub(crate) mod utils; /// Generates Rust code for each type inside `types` if: /// * the type is not present inside `shared_types`, and /// * if it should be generated (see: [`should_skip_codegen`], and /// * if it is a struct or an enum. /// /// /// # Arguments /// /// * `types`: Types you wish to generate Rust code for. /// * `shared_types`: Types that are shared between multiple /// contracts/scripts/predicates and thus generated elsewhere. pub(crate) fn generate_types<'a>( types: impl IntoIterator, shared_types: &HashSet, logged_types: impl IntoIterator, no_std: bool, ) -> Result { let log_ids: HashMap<_, _> = logged_types .into_iter() .map(|l| (l.application.type_decl.type_field.clone(), l.log_id.clone())) .collect(); types .into_iter() .filter(|ttype| !should_skip_codegen(ttype)) .map(|ttype: &FullTypeDeclaration| { let log_id = log_ids.get(&ttype.type_field); if shared_types.contains(ttype) { reexport_the_shared_type(ttype, no_std) } else if ttype.is_struct_type() { expand_custom_struct(ttype, no_std, log_id) } else { expand_custom_enum(ttype, no_std, log_id) } }) .fold_ok(GeneratedCode::default(), |acc, generated_code| { acc.merge(generated_code) }) } /// Instead of generating bindings for `ttype` this fn will just generate a `pub use` pointing to /// the already generated equivalent shared type. fn reexport_the_shared_type(ttype: &FullTypeDeclaration, no_std: bool) -> Result { // e.g. some_library::another_mod::SomeStruct let type_path = ttype .custom_type_path() .expect("This must be a custom type due to the previous filter step"); let type_mod = type_path.parent(); let from_top_lvl_to_shared_types = TypePath::new("super::shared_types").expect("This is known to be a valid TypePath"); let top_lvl_mod = TypePath::default(); let from_current_mod_to_top_level = top_lvl_mod.relative_path_from(&type_mod); let path = from_current_mod_to_top_level .append(from_top_lvl_to_shared_types) .append(type_path); // e.g. pub use super::super::super::shared_types::some_library::another_mod::SomeStruct; let the_reexport = quote! {pub use #path;}; Ok(GeneratedCode::new(the_reexport, Default::default(), no_std).wrap_in_mod(type_mod)) } // Checks whether the given type should not have code generated for it. This // is mainly because the corresponding type in Rust already exists -- // e.g. the contract's Vec type is mapped to std::vec::Vec from the Rust // stdlib, ContractId is a custom type implemented by fuels-rs, etc. // Others like 'std::vec::RawVec' are skipped because they are // implementation details of the contract's Vec type and are not directly // used in the SDK. pub fn should_skip_codegen(type_decl: &FullTypeDeclaration) -> bool { if !type_decl.is_custom_type() { return true; } let type_path = type_decl.custom_type_path().unwrap(); is_type_sdk_provided(&type_path) || is_type_unused(&type_path) } fn is_type_sdk_provided(type_path: &TypePath) -> bool { sdk_provided_custom_types_lookup().contains_key(type_path) } fn is_type_unused(type_path: &TypePath) -> bool { let msg = "Known to be correct"; [ TypePath::new("std::vec::RawVec").expect(msg), TypePath::new("std::bytes::RawBytes").expect(msg), ] .contains(type_path) } // Doing string -> TokenStream -> string isn't pretty but gives us the opportunity to // have a better understanding of the generated code so we consider it ok. // To generate the expected examples, output of the functions were taken // with code @9ca376, and formatted in-IDE using rustfmt. It should be noted that // rustfmt added an extra `,` after the last struct/enum field, which is not added // by the `expand_custom_*` functions, and so was removed from the expected string. // TODO(iqdecay): append extra `,` to last enum/struct field so it is aligned with rustfmt #[cfg(test)] mod tests { use std::collections::HashMap; use fuel_abi_types::abi::unified_program::{UnifiedTypeApplication, UnifiedTypeDeclaration}; use pretty_assertions::assert_eq; use quote::quote; use super::*; #[test] fn test_expand_custom_enum() -> Result<()> { let p = UnifiedTypeDeclaration { type_id: 0, type_field: String::from("enum MatchaTea"), components: Some(vec![ UnifiedTypeApplication { name: String::from("LongIsland"), type_id: 1, ..Default::default() }, UnifiedTypeApplication { name: String::from("MoscowMule"), type_id: 2, ..Default::default() }, ]), ..Default::default() }; let types = [ (0, p.clone()), ( 1, UnifiedTypeDeclaration { type_id: 1, type_field: String::from("u64"), ..Default::default() }, ), ( 2, UnifiedTypeDeclaration { type_id: 2, type_field: String::from("bool"), ..Default::default() }, ), ] .into_iter() .collect::>(); let actual = expand_custom_enum( &FullTypeDeclaration::from_counterpart(&p, &types), false, None, )?; let expected = quote! { #[allow(clippy::enum_variant_names)] #[derive( Clone, Debug, Eq, PartialEq, ::fuels::macros::Parameterize, ::fuels::macros::Tokenizable, ::fuels::macros::TryFrom, )] pub enum MatchaTea { LongIsland(::core::primitive::u64), MoscowMule(::core::primitive::bool), } }; assert_eq!(actual.code().to_string(), expected.to_string()); Ok(()) } #[test] fn test_enum_with_no_variants_cannot_be_constructed() -> Result<()> { let p = UnifiedTypeDeclaration { type_id: 0, type_field: "enum SomeEmptyEnum".to_string(), components: Some(vec![]), ..Default::default() }; let types = [(0, p.clone())].into_iter().collect::>(); expand_custom_enum( &FullTypeDeclaration::from_counterpart(&p, &types), false, None, ) .expect_err("Was able to construct an enum without variants"); Ok(()) } #[test] fn test_expand_struct_inside_enum() -> Result<()> { let inner_struct = UnifiedTypeApplication { name: String::from("Infrastructure"), type_id: 1, ..Default::default() }; let enum_components = vec![ inner_struct, UnifiedTypeApplication { name: "Service".to_string(), type_id: 2, ..Default::default() }, ]; let p = UnifiedTypeDeclaration { type_id: 0, type_field: String::from("enum Amsterdam"), components: Some(enum_components), ..Default::default() }; let types = [ (0, p.clone()), ( 1, UnifiedTypeDeclaration { type_id: 1, type_field: String::from("struct Building"), components: Some(vec![]), ..Default::default() }, ), ( 2, UnifiedTypeDeclaration { type_id: 2, type_field: String::from("u32"), ..Default::default() }, ), ] .into_iter() .collect::>(); let actual = expand_custom_enum( &FullTypeDeclaration::from_counterpart(&p, &types), false, None, )?; let expected = quote! { #[allow(clippy::enum_variant_names)] #[derive( Clone, Debug, Eq, PartialEq, ::fuels::macros::Parameterize, ::fuels::macros::Tokenizable, ::fuels::macros::TryFrom, )] pub enum Amsterdam { Infrastructure(self::Building), Service(::core::primitive::u32), } }; assert_eq!(actual.code().to_string(), expected.to_string()); Ok(()) } #[test] fn test_expand_array_inside_enum() -> Result<()> { let enum_components = vec![UnifiedTypeApplication { name: "SomeArr".to_string(), type_id: 1, ..Default::default() }]; let p = UnifiedTypeDeclaration { type_id: 0, type_field: String::from("enum SomeEnum"), components: Some(enum_components), ..Default::default() }; let types = [ (0, p.clone()), ( 1, UnifiedTypeDeclaration { type_id: 1, type_field: "[u64; 7]".to_string(), components: Some(vec![UnifiedTypeApplication { type_id: 2, ..Default::default() }]), ..Default::default() }, ), ( 2, UnifiedTypeDeclaration { type_id: 2, type_field: "u64".to_string(), ..Default::default() }, ), ] .into_iter() .collect::>(); let actual = expand_custom_enum( &FullTypeDeclaration::from_counterpart(&p, &types), false, None, )?; let expected = quote! { #[allow(clippy::enum_variant_names)] #[derive( Clone, Debug, Eq, PartialEq, ::fuels::macros::Parameterize, ::fuels::macros::Tokenizable, ::fuels::macros::TryFrom, )] pub enum SomeEnum { SomeArr([::core::primitive::u64; 7usize]), } }; assert_eq!(actual.code().to_string(), expected.to_string()); Ok(()) } #[test] fn test_expand_custom_enum_with_enum() -> Result<()> { let p = UnifiedTypeDeclaration { type_id: 3, type_field: String::from("enum EnumLevel3"), components: Some(vec![UnifiedTypeApplication { name: String::from("El2"), type_id: 2, ..Default::default() }]), ..Default::default() }; let types = [ (3, p.clone()), ( 2, UnifiedTypeDeclaration { type_id: 2, type_field: String::from("enum EnumLevel2"), components: Some(vec![UnifiedTypeApplication { name: String::from("El1"), type_id: 1, ..Default::default() }]), ..Default::default() }, ), ( 1, UnifiedTypeDeclaration { type_id: 1, type_field: String::from("enum EnumLevel1"), components: Some(vec![UnifiedTypeApplication { name: String::from("Num"), type_id: 0, ..Default::default() }]), ..Default::default() }, ), ( 0, UnifiedTypeDeclaration { type_id: 0, type_field: String::from("u32"), ..Default::default() }, ), ] .into_iter() .collect::>(); let log_id = "42".to_string(); let actual = expand_custom_enum( &FullTypeDeclaration::from_counterpart(&p, &types), false, Some(&log_id), )?; let expected = quote! { #[allow(clippy::enum_variant_names)] #[derive( Clone, Debug, Eq, PartialEq, ::fuels::macros::Parameterize, ::fuels::macros::Tokenizable, ::fuels::macros::TryFrom, )] pub enum EnumLevel3 { El2(self::EnumLevel2), } impl ::fuels::core::codec::Log for EnumLevel3 { const LOG_ID: &'static str = "42"; const LOG_ID_U64: u64 = 42u64; } }; assert_eq!(actual.code().to_string(), expected.to_string()); Ok(()) } #[test] fn test_expand_custom_struct() -> Result<()> { let p = UnifiedTypeDeclaration { type_field: String::from("struct Cocktail"), components: Some(vec![ UnifiedTypeApplication { name: String::from("long_island"), type_id: 1, ..Default::default() }, UnifiedTypeApplication { name: String::from("cosmopolitan"), type_id: 2, ..Default::default() }, UnifiedTypeApplication { name: String::from("mojito"), type_id: 3, ..Default::default() }, ]), ..Default::default() }; let types = [ (0, p.clone()), ( 1, UnifiedTypeDeclaration { type_id: 1, type_field: String::from("bool"), ..Default::default() }, ), ( 2, UnifiedTypeDeclaration { type_id: 2, type_field: String::from("u64"), ..Default::default() }, ), ( 3, UnifiedTypeDeclaration { type_id: 3, type_field: String::from("u32"), ..Default::default() }, ), ] .into_iter() .collect::>(); let actual = expand_custom_struct( &FullTypeDeclaration::from_counterpart(&p, &types), false, None, )?; let expected = quote! { #[derive( Clone, Debug, Eq, PartialEq, ::fuels::macros::Parameterize, ::fuels::macros::Tokenizable, ::fuels::macros::TryFrom, )] pub struct Cocktail { pub long_island: ::core::primitive::bool, pub cosmopolitan: ::core::primitive::u64, pub mojito: ::core::primitive::u32, } impl Cocktail { pub fn new( long_island: ::core::primitive::bool, cosmopolitan: ::core::primitive::u64, mojito: ::core::primitive::u32, ) -> Self { Self { long_island, cosmopolitan, mojito, } } } }; assert_eq!(actual.code().to_string(), expected.to_string()); Ok(()) } #[test] fn test_struct_with_no_fields_can_be_constructed() -> Result<()> { let p = UnifiedTypeDeclaration { type_id: 0, type_field: "struct SomeEmptyStruct".to_string(), components: Some(vec![]), ..Default::default() }; let types = [(0, p.clone())].into_iter().collect::>(); let actual = expand_custom_struct( &FullTypeDeclaration::from_counterpart(&p, &types), false, None, )?; let expected = quote! { #[derive( Clone, Debug, Eq, PartialEq, ::core::default::Default, ::fuels::macros::Parameterize, ::fuels::macros::Tokenizable, ::fuels::macros::TryFrom, )] pub struct SomeEmptyStruct {} impl SomeEmptyStruct { pub fn new() -> Self { Self {} } } }; assert_eq!(actual.code().to_string(), expected.to_string()); Ok(()) } #[test] fn test_expand_custom_struct_with_struct() -> Result<()> { let p = UnifiedTypeDeclaration { type_id: 0, type_field: String::from("struct Cocktail"), components: Some(vec![ UnifiedTypeApplication { name: String::from("long_island"), type_id: 1, ..Default::default() }, UnifiedTypeApplication { name: String::from("mojito"), type_id: 2, ..Default::default() }, ]), ..Default::default() }; let types = [ (0, p.clone()), ( 1, UnifiedTypeDeclaration { type_id: 1, type_field: String::from("struct Shaker"), components: Some(vec![]), ..Default::default() }, ), ( 2, UnifiedTypeDeclaration { type_id: 2, type_field: String::from("u32"), ..Default::default() }, ), ] .into_iter() .collect::>(); let log_id = "13".to_string(); let actual = expand_custom_struct( &FullTypeDeclaration::from_counterpart(&p, &types), false, Some(&log_id), )?; let expected = quote! { #[derive( Clone, Debug, Eq, PartialEq, ::fuels::macros::Parameterize, ::fuels::macros::Tokenizable, ::fuels::macros::TryFrom, )] pub struct Cocktail { pub long_island: self::Shaker, pub mojito: ::core::primitive::u32, } impl Cocktail { pub fn new(long_island: self::Shaker, mojito: ::core::primitive::u32,) -> Self { Self { long_island, mojito, } } } impl ::fuels::core::codec::Log for Cocktail { const LOG_ID: &'static str = "13"; const LOG_ID_U64: u64 = 13u64; } }; assert_eq!(actual.code().to_string(), expected.to_string()); Ok(()) } #[test] fn shared_types_are_just_reexported() { // given let type_decl = FullTypeDeclaration { type_field: "struct some_shared_lib::SharedStruct".to_string(), components: vec![], type_parameters: vec![], alias_of: None, }; let shared_types = HashSet::from([type_decl.clone()]); // when let generated_code = generate_types(&[type_decl], &shared_types, [], false).unwrap(); // then let expected_code = quote! { #[allow(clippy::too_many_arguments)] #[allow(clippy::disallowed_names)] #[no_implicit_prelude] pub mod some_shared_lib { use ::core::{ clone::Clone, convert::{Into, TryFrom, From}, iter::IntoIterator, iter::Iterator, marker::Sized, panic, }; use ::std::{string::ToString, format, vec, default::Default}; pub use super::super::shared_types::some_shared_lib::SharedStruct; } }; assert_eq!(generated_code.code().to_string(), expected_code.to_string()); } } ================================================ FILE: packages/fuels-code-gen/src/program_bindings/generated_code.rs ================================================ use std::collections::{HashMap, HashSet}; use itertools::Itertools; use proc_macro2::{Ident, TokenStream}; use quote::quote; use crate::utils::TypePath; #[derive(Default, Debug)] pub(crate) struct GeneratedCode { top_level_code: TokenStream, usable_types: HashSet, code_in_mods: HashMap, no_std: bool, } impl GeneratedCode { pub fn new(code: TokenStream, usable_types: HashSet, no_std: bool) -> Self { Self { top_level_code: code, code_in_mods: HashMap::default(), usable_types, no_std, } } fn prelude(&self) -> TokenStream { let lib = if self.no_std { quote! {::alloc} } else { quote! {::std} }; quote! { use ::core::{ clone::Clone, convert::{Into, TryFrom, From}, iter::IntoIterator, iter::Iterator, marker::Sized, panic, }; use #lib::{string::ToString, format, vec, default::Default}; } } pub fn code(&self) -> TokenStream { let top_level_code = &self.top_level_code; let prelude = self.prelude(); let code_in_mods = self .code_in_mods .iter() .sorted_by_key(|(mod_name, _)| { // Sorted to make test expectations maintainable *mod_name }) .map(|(mod_name, generated_code)| { let code = generated_code.code(); quote! { #[allow(clippy::too_many_arguments)] #[allow(clippy::disallowed_names)] #[no_implicit_prelude] pub mod #mod_name { #prelude #code } } }); quote! { #top_level_code #(#code_in_mods)* } } pub fn is_empty(&self) -> bool { self.code().is_empty() } pub fn merge(mut self, another: GeneratedCode) -> Self { self.top_level_code.extend(another.top_level_code); self.usable_types.extend(another.usable_types); for (mod_name, code) in another.code_in_mods { let entry = self.code_in_mods.entry(mod_name).or_default(); *entry = std::mem::take(entry).merge(code); } self } pub fn wrap_in_mod(mut self, mod_name: impl Into) -> Self { let mut parts = mod_name.into().take_parts(); parts.reverse(); for mod_name in parts { self = self.wrap_in_single_mod(mod_name) } self } fn wrap_in_single_mod(self, mod_name: Ident) -> Self { Self { code_in_mods: HashMap::from([(mod_name, self)]), ..Default::default() } } pub fn use_statements_for_uniquely_named_types(&self) -> TokenStream { let type_paths = self .types_with_unique_names() .into_iter() .filter(|type_path| type_path.has_multiple_parts()); quote! { #(pub use #type_paths;)* } } fn types_with_unique_names(&self) -> Vec { self.code_in_mods .iter() .flat_map(|(mod_name, code)| { code.types_with_unique_names() .into_iter() .map(|type_path| type_path.prepend(mod_name.into())) .collect::>() }) .chain(self.usable_types.iter().cloned()) .sorted_by(|lhs, rhs| lhs.ident().cmp(&rhs.ident())) .group_by(|e| e.ident().cloned()) .into_iter() .filter_map(|(_, group)| { let mut types = group.collect::>(); (types.len() == 1).then_some(types.pop().unwrap()) }) .collect() } } #[cfg(test)] mod tests { use super::*; use crate::utils::ident; #[test] fn can_merge_top_level_code() { // given let struct_1 = given_some_struct_code("Struct1"); let struct_2 = given_some_struct_code("Struct2"); // when let joined = struct_1.merge(struct_2); // then let expected_code = quote! { struct Struct1; struct Struct2; }; assert_eq!(joined.code().to_string(), expected_code.to_string()); } #[test] fn wrapping_in_mod_updates_code() { // given let some_type = given_some_struct_code("SomeType"); // when let wrapped_in_mod = some_type.wrap_in_mod(given_type_path("a_mod")); // then let expected_code = quote! { #[allow(clippy::too_many_arguments)] #[allow(clippy::disallowed_names)] #[no_implicit_prelude] pub mod a_mod { use ::core::{ clone::Clone, convert::{Into, TryFrom, From}, iter::IntoIterator, iter::Iterator, marker::Sized, panic, }; use ::std::{string::ToString, format, vec, default::Default}; struct SomeType; } }; assert_eq!(wrapped_in_mod.code().to_string(), expected_code.to_string()); } #[test] fn wrapping_in_mod_updates_use_statements() { // given let some_type = given_some_struct_code("SomeType"); let wrapped_in_mod = some_type.wrap_in_mod(given_type_path("a_mod")); // when let use_statements = wrapped_in_mod.use_statements_for_uniquely_named_types(); // then let expected_use_statements = quote! {pub use a_mod::SomeType;}; assert_eq!( use_statements.to_string(), expected_use_statements.to_string() ); } #[test] fn merging_code_will_merge_mods_as_well() { // given let common_struct_1 = given_some_struct_code("SomeStruct1") .wrap_in_mod(given_type_path("common_mod::deeper_mod")); let common_struct_2 = given_some_struct_code("SomeStruct2").wrap_in_mod(given_type_path("common_mod")); let top_level_struct = given_some_struct_code("TopLevelStruct"); let different_mod_struct = given_some_struct_code("SomeStruct3").wrap_in_mod(given_type_path("different_mod")); // when let merged_code = common_struct_1 .merge(common_struct_2) .merge(top_level_struct) .merge(different_mod_struct); // then let prelude = quote! { use ::core::{ clone::Clone, convert::{Into, TryFrom, From}, iter::IntoIterator, iter::Iterator, marker::Sized, panic, }; use ::std::{string::ToString, format, vec, default::Default}; }; let expected_code = quote! { struct TopLevelStruct; #[allow(clippy::too_many_arguments)] #[allow(clippy::disallowed_names)] #[no_implicit_prelude] pub mod common_mod { #prelude struct SomeStruct2; #[allow(clippy::too_many_arguments)] #[allow(clippy::disallowed_names)] #[no_implicit_prelude] pub mod deeper_mod { #prelude struct SomeStruct1; } } #[allow(clippy::too_many_arguments)] #[allow(clippy::disallowed_names)] #[no_implicit_prelude] pub mod different_mod { #prelude struct SomeStruct3; } }; let code = merged_code.code(); assert_eq!(code.to_string(), expected_code.to_string()); let use_statements = merged_code.use_statements_for_uniquely_named_types(); let expected_use_statements = quote! { pub use common_mod::deeper_mod::SomeStruct1; pub use common_mod::SomeStruct2; pub use different_mod::SomeStruct3; }; assert_eq!( use_statements.to_string(), expected_use_statements.to_string() ); } #[test] fn use_statement_not_generated_for_top_level_type() { let usable_types = ["TopLevelImport", "something::Deeper"] .map(given_type_path) .into_iter() .collect(); let code = GeneratedCode::new(Default::default(), usable_types, false); let use_statements = code.use_statements_for_uniquely_named_types(); let expected_use_statements = quote! { pub use something::Deeper; }; assert_eq!( use_statements.to_string(), expected_use_statements.to_string() ); } #[test] fn use_statements_only_for_uniquely_named_types() { // given let not_unique_struct = given_some_struct_code("NotUnique").wrap_in_mod(TypePath::new("another_mod").unwrap()); let generated_code = GeneratedCode::new( Default::default(), HashSet::from([ given_type_path("some_mod::Unique"), given_type_path("even_though::the_duplicate_is::in_another_mod::NotUnique"), ]), false, ) .merge(not_unique_struct); // when let use_statements = generated_code.use_statements_for_uniquely_named_types(); // then let expected_use_statements = quote! { pub use some_mod::Unique; }; assert_eq!( use_statements.to_string(), expected_use_statements.to_string() ); } fn given_some_struct_code(struct_name: &str) -> GeneratedCode { let struct_ident = ident(struct_name); GeneratedCode::new( quote! {struct #struct_ident;}, HashSet::from([given_type_path(struct_name)]), false, ) } fn given_type_path(path: &str) -> TypePath { TypePath::new(path).expect("hand crafted, should be valid") } } ================================================ FILE: packages/fuels-code-gen/src/program_bindings/resolved_type.rs ================================================ use std::fmt::{Display, Formatter}; use fuel_abi_types::{ abi::full_program::FullTypeApplication, utils::{self, extract_array_len, extract_generic_name, extract_str_len, has_tuple_format}, }; use proc_macro2::{Ident, TokenStream}; use quote::{ToTokens, quote}; use crate::{ error::{Result, error}, program_bindings::utils::sdk_provided_custom_types_lookup, utils::TypePath, }; #[derive(Debug, Clone, PartialEq)] pub enum GenericType { Named(Ident), Constant(usize), } impl ToTokens for GenericType { fn to_tokens(&self, tokens: &mut TokenStream) { let stream = match self { GenericType::Named(ident) => ident.to_token_stream(), GenericType::Constant(constant) => constant.to_token_stream(), }; tokens.extend(stream); } } /// Represents a Rust type alongside its generic parameters. For when you want to reference an ABI /// type in Rust code since [`ResolvedType`] can be converted into a [`TokenStream`] via /// `resolved_type.to_token_stream()`. #[derive(Debug, Clone)] pub enum ResolvedType { Unit, Primitive(TypePath), StructOrEnum { path: TypePath, generics: Vec, }, Array(Box, usize), Tuple(Vec), Generic(GenericType), } impl ResolvedType { pub fn generics(&self) -> Vec { match self { ResolvedType::StructOrEnum { generics: elements, .. } | ResolvedType::Tuple(elements) => { elements.iter().flat_map(|el| el.generics()).collect() } ResolvedType::Array(el, _) => el.generics(), ResolvedType::Generic(inner) => vec![inner.clone()], _ => vec![], } } } impl ToTokens for ResolvedType { fn to_tokens(&self, tokens: &mut TokenStream) { let tokenized = match self { ResolvedType::Unit => quote! {()}, ResolvedType::Primitive(path) => path.into_token_stream(), ResolvedType::StructOrEnum { path, generics } => { if generics.is_empty() { path.to_token_stream() } else { quote! { #path<#(#generics),*>} } } ResolvedType::Array(el, count) => quote! { [#el; #count]}, ResolvedType::Tuple(elements) => { // it is important to leave a trailing comma because a tuple with // one element is written as (element,) not (element) which is // resolved to just element quote! { (#(#elements,)*) } } ResolvedType::Generic(generic_type) => generic_type.into_token_stream(), }; tokens.extend(tokenized) } } impl Display for ResolvedType { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { write!(f, "{}", self.to_token_stream()) } } /// Used to resolve [`FullTypeApplication`]s into [`ResolvedType`]s pub(crate) struct TypeResolver { /// The mod in which the produced [`ResolvedType`]s are going to end up in. current_mod: TypePath, } impl Default for TypeResolver { fn default() -> Self { TypeResolver::new(Default::default()) } } impl TypeResolver { pub(crate) fn new(current_mod: TypePath) -> Self { Self { current_mod } } pub(crate) fn resolve(&self, type_application: &FullTypeApplication) -> Result { let resolvers = [ Self::try_as_primitive_type, Self::try_as_bits256, Self::try_as_generic, Self::try_as_array, Self::try_as_sized_ascii_string, Self::try_as_ascii_string, Self::try_as_tuple, Self::try_as_raw_slice, Self::try_as_custom_type, ]; for resolver in resolvers { if let Some(resolved) = resolver(self, type_application)? { return Ok(resolved); } } let type_field = &type_application.type_decl.type_field; Err(error!("could not resolve '{type_field}' to any known type")) } fn resolve_multiple( &self, type_applications: &[FullTypeApplication], ) -> Result> { type_applications .iter() .map(|type_application| self.resolve(type_application)) .collect() } fn try_as_generic( &self, type_application: &FullTypeApplication, ) -> Result> { let Some(name) = extract_generic_name(&type_application.type_decl.type_field) else { return Ok(None); }; let ident = utils::safe_ident(&name); Ok(Some(ResolvedType::Generic(GenericType::Named(ident)))) } fn try_as_array(&self, type_application: &FullTypeApplication) -> Result> { let type_decl = &type_application.type_decl; let Some(len) = extract_array_len(&type_decl.type_field) else { return Ok(None); }; let components = self.resolve_multiple(&type_decl.components)?; let type_inside = match components.as_slice() { [single_type] => single_type, other => { return Err(error!( "array must have only one component. Actual components: {other:?}" )); } }; Ok(Some(ResolvedType::Array( Box::new(type_inside.clone()), len, ))) } fn try_as_sized_ascii_string( &self, type_application: &FullTypeApplication, ) -> Result> { let Some(len) = extract_str_len(&type_application.type_decl.type_field) else { return Ok(None); }; let path = TypePath::new("::fuels::types::SizedAsciiString").expect("this is a valid TypePath"); Ok(Some(ResolvedType::StructOrEnum { path, generics: vec![ResolvedType::Generic(GenericType::Constant(len))], })) } fn try_as_ascii_string( &self, type_application: &FullTypeApplication, ) -> Result> { let maybe_resolved = (type_application.type_decl.type_field == "str").then(|| { let path = TypePath::new("::fuels::types::AsciiString").expect("this is a valid TypePath"); ResolvedType::StructOrEnum { path, generics: vec![], } }); Ok(maybe_resolved) } fn try_as_tuple(&self, type_application: &FullTypeApplication) -> Result> { let type_decl = &type_application.type_decl; if !has_tuple_format(&type_decl.type_field) { return Ok(None); } let inner_types = self.resolve_multiple(&type_decl.components)?; Ok(Some(ResolvedType::Tuple(inner_types))) } fn try_as_primitive_type( &self, type_decl: &FullTypeApplication, ) -> Result> { let type_field = &type_decl.type_decl.type_field; let maybe_resolved = match type_field.as_str() { "()" => Some(ResolvedType::Unit), "bool" | "u8" | "u16" | "u32" | "u64" => { let path = format!("::core::primitive::{type_field}"); let type_path = TypePath::new(path).expect("to be a valid path"); Some(ResolvedType::Primitive(type_path)) } "struct std::u128::U128" | "struct U128" => { let u128_path = TypePath::new("::core::primitive::u128").expect("is correct"); Some(ResolvedType::Primitive(u128_path)) } "u256" => { let u256_path = TypePath::new("::fuels::types::U256").expect("is correct"); Some(ResolvedType::Primitive(u256_path)) } _ => None, }; Ok(maybe_resolved) } fn try_as_bits256( &self, type_application: &FullTypeApplication, ) -> Result> { if type_application.type_decl.type_field != "b256" { return Ok(None); } let path = TypePath::new("::fuels::types::Bits256").expect("to be valid"); Ok(Some(ResolvedType::StructOrEnum { path, generics: vec![], })) } fn try_as_raw_slice( &self, type_application: &FullTypeApplication, ) -> Result> { if type_application.type_decl.type_field != "raw untyped slice" { return Ok(None); } let path = TypePath::new("::fuels::types::RawSlice").expect("this is a valid TypePath"); Ok(Some(ResolvedType::StructOrEnum { path, generics: vec![], })) } fn try_as_custom_type( &self, type_application: &FullTypeApplication, ) -> Result> { let type_decl = &type_application.type_decl; if !type_decl.is_custom_type() { return Ok(None); } let original_path = type_decl.custom_type_path()?; let used_path = sdk_provided_custom_types_lookup() .get(&original_path) .cloned() .unwrap_or_else(|| original_path.relative_path_from(&self.current_mod)); let generics = self.resolve_multiple(&type_application.type_arguments)?; Ok(Some(ResolvedType::StructOrEnum { path: used_path, generics, })) } } #[cfg(test)] mod tests { use std::{collections::HashMap, str::FromStr}; use fuel_abi_types::{ abi::{ full_program::FullTypeDeclaration, unified_program::{UnifiedTypeApplication, UnifiedTypeDeclaration}, }, utils::ident, }; use super::*; #[test] fn correctly_extracts_used_generics() { let resolved_type = ResolvedType::StructOrEnum { path: Default::default(), generics: vec![ ResolvedType::Tuple(vec![ResolvedType::Array( Box::new(ResolvedType::StructOrEnum { path: Default::default(), generics: vec![ ResolvedType::Generic(GenericType::Named(ident("A"))), ResolvedType::Generic(GenericType::Constant(10)), ], }), 2, )]), ResolvedType::Generic(GenericType::Named(ident("B"))), ], }; let generics = resolved_type.generics(); assert_eq!( generics, vec![ GenericType::Named(ident("A")), GenericType::Constant(10), GenericType::Named(ident("B")) ] ) } fn test_resolve_first_type( expected: &str, type_declarations: &[UnifiedTypeDeclaration], ) -> Result<()> { let types = type_declarations .iter() .map(|td| (td.type_id, td.clone())) .collect::>(); let type_application = UnifiedTypeApplication { type_id: type_declarations[0].type_id, ..Default::default() }; let application = FullTypeApplication::from_counterpart(&type_application, &types); let resolved_type = TypeResolver::default() .resolve(&application) .map_err(|e| e.combine(error!("failed to resolve {:?}", type_application)))?; let actual = resolved_type.to_token_stream().to_string(); let expected = TokenStream::from_str(expected).unwrap().to_string(); assert_eq!(actual, expected); Ok(()) } fn test_resolve_primitive_type(type_field: &str, expected: &str) -> Result<()> { test_resolve_first_type( expected, &[UnifiedTypeDeclaration { type_id: 0, type_field: type_field.to_string(), ..Default::default() }], ) } #[test] fn test_resolve_u8() -> Result<()> { test_resolve_primitive_type("u8", "::core::primitive::u8") } #[test] fn test_resolve_u16() -> Result<()> { test_resolve_primitive_type("u16", "::core::primitive::u16") } #[test] fn test_resolve_u32() -> Result<()> { test_resolve_primitive_type("u32", "::core::primitive::u32") } #[test] fn test_resolve_u64() -> Result<()> { test_resolve_primitive_type("u64", "::core::primitive::u64") } #[test] fn test_resolve_bool() -> Result<()> { test_resolve_primitive_type("bool", "::core::primitive::bool") } #[test] fn test_resolve_b256() -> Result<()> { test_resolve_primitive_type("b256", "::fuels::types::Bits256") } #[test] fn test_resolve_unit() -> Result<()> { test_resolve_primitive_type("()", "()") } #[test] fn test_resolve_array() -> Result<()> { test_resolve_first_type( "[::core::primitive::u8 ; 3usize]", &[ UnifiedTypeDeclaration { type_id: 0, type_field: "[u8; 3]".to_string(), components: Some(vec![UnifiedTypeApplication { type_id: 1, ..Default::default() }]), ..Default::default() }, UnifiedTypeDeclaration { type_id: 1, type_field: "u8".to_string(), ..Default::default() }, ], ) } #[test] fn test_resolve_vector() -> Result<()> { test_resolve_first_type( ":: std :: vec :: Vec", &[ UnifiedTypeDeclaration { type_id: 0, type_field: "struct std::vec::Vec".to_string(), components: Some(vec![ UnifiedTypeApplication { name: "buf".to_string(), type_id: 2, type_arguments: Some(vec![UnifiedTypeApplication { type_id: 1, ..Default::default() }]), error_message: None, }, UnifiedTypeApplication { name: "len".to_string(), type_id: 3, ..Default::default() }, ]), type_parameters: Some(vec![1]), alias_of: None, }, UnifiedTypeDeclaration { type_id: 1, type_field: "generic T".to_string(), ..Default::default() }, UnifiedTypeDeclaration { type_id: 2, type_field: "raw untyped ptr".to_string(), ..Default::default() }, UnifiedTypeDeclaration { type_id: 3, type_field: "struct std::vec::RawVec".to_string(), components: Some(vec![ UnifiedTypeApplication { name: "ptr".to_string(), type_id: 2, ..Default::default() }, UnifiedTypeApplication { name: "cap".to_string(), type_id: 4, ..Default::default() }, ]), type_parameters: Some(vec![1]), alias_of: None, }, UnifiedTypeDeclaration { type_id: 4, type_field: "u64".to_string(), ..Default::default() }, UnifiedTypeDeclaration { type_id: 5, type_field: "u8".to_string(), ..Default::default() }, ], ) } #[test] fn test_resolve_bytes() -> Result<()> { test_resolve_first_type( ":: fuels :: types :: Bytes", &[ UnifiedTypeDeclaration { type_id: 0, type_field: "struct String".to_string(), components: Some(vec![UnifiedTypeApplication { name: "bytes".to_string(), type_id: 1, ..Default::default() }]), ..Default::default() }, UnifiedTypeDeclaration { type_id: 0, type_field: "struct std::bytes::Bytes".to_string(), components: Some(vec![ UnifiedTypeApplication { name: "buf".to_string(), type_id: 1, ..Default::default() }, UnifiedTypeApplication { name: "len".to_string(), type_id: 3, ..Default::default() }, ]), ..Default::default() }, UnifiedTypeDeclaration { type_id: 1, type_field: "struct std::bytes::RawBytes".to_string(), components: Some(vec![ UnifiedTypeApplication { name: "ptr".to_string(), type_id: 2, ..Default::default() }, UnifiedTypeApplication { name: "cap".to_string(), type_id: 3, ..Default::default() }, ]), ..Default::default() }, UnifiedTypeDeclaration { type_id: 2, type_field: "raw untyped ptr".to_string(), ..Default::default() }, UnifiedTypeDeclaration { type_id: 3, type_field: "u64".to_string(), ..Default::default() }, ], ) } #[test] fn test_resolve_std_string() -> Result<()> { test_resolve_first_type( ":: std :: string :: String", &[ UnifiedTypeDeclaration { type_id: 0, type_field: "struct std::string::String".to_string(), components: Some(vec![UnifiedTypeApplication { name: "bytes".to_string(), type_id: 1, ..Default::default() }]), ..Default::default() }, UnifiedTypeDeclaration { type_id: 1, type_field: "struct std::bytes::Bytes".to_string(), components: Some(vec![ UnifiedTypeApplication { name: "buf".to_string(), type_id: 2, ..Default::default() }, UnifiedTypeApplication { name: "len".to_string(), type_id: 4, ..Default::default() }, ]), ..Default::default() }, UnifiedTypeDeclaration { type_id: 2, type_field: "struct std::bytes::RawBytes".to_string(), components: Some(vec![ UnifiedTypeApplication { name: "ptr".to_string(), type_id: 3, ..Default::default() }, UnifiedTypeApplication { name: "cap".to_string(), type_id: 4, ..Default::default() }, ]), ..Default::default() }, UnifiedTypeDeclaration { type_id: 3, type_field: "raw untyped ptr".to_string(), ..Default::default() }, UnifiedTypeDeclaration { type_id: 4, type_field: "u64".to_string(), ..Default::default() }, ], ) } #[test] fn test_resolve_static_str() -> Result<()> { test_resolve_primitive_type("str[3]", ":: fuels :: types :: SizedAsciiString < 3usize >") } #[test] fn test_resolve_struct() -> Result<()> { test_resolve_first_type( "self :: SomeStruct", &[ UnifiedTypeDeclaration { type_id: 0, type_field: "struct SomeStruct".to_string(), components: Some(vec![ UnifiedTypeApplication { name: "foo".to_string(), type_id: 1, ..Default::default() }, UnifiedTypeApplication { name: "bar".to_string(), type_id: 2, ..Default::default() }, ]), type_parameters: Some(vec![1]), alias_of: None, }, UnifiedTypeDeclaration { type_id: 1, type_field: "generic T".to_string(), ..Default::default() }, UnifiedTypeDeclaration { type_id: 2, type_field: "u8".to_string(), ..Default::default() }, ], ) } #[test] fn test_resolve_enum() -> Result<()> { test_resolve_first_type( "self :: SomeEnum", &[ UnifiedTypeDeclaration { type_id: 0, type_field: "enum SomeEnum".to_string(), components: Some(vec![ UnifiedTypeApplication { name: "foo".to_string(), type_id: 1, ..Default::default() }, UnifiedTypeApplication { name: "bar".to_string(), type_id: 2, ..Default::default() }, ]), type_parameters: Some(vec![1]), alias_of: None, }, UnifiedTypeDeclaration { type_id: 1, type_field: "generic T".to_string(), ..Default::default() }, UnifiedTypeDeclaration { type_id: 2, type_field: "u8".to_string(), ..Default::default() }, ], ) } #[test] fn test_resolve_tuple() -> Result<()> { test_resolve_first_type( "(::core::primitive::u8, ::core::primitive::u16, ::core::primitive::bool, T,)", &[ UnifiedTypeDeclaration { type_id: 0, type_field: "(u8, u16, bool, T)".to_string(), components: Some(vec![ UnifiedTypeApplication { type_id: 1, ..Default::default() }, UnifiedTypeApplication { type_id: 2, ..Default::default() }, UnifiedTypeApplication { type_id: 3, ..Default::default() }, UnifiedTypeApplication { type_id: 4, ..Default::default() }, ]), type_parameters: Some(vec![4]), alias_of: None, }, UnifiedTypeDeclaration { type_id: 1, type_field: "u8".to_string(), ..Default::default() }, UnifiedTypeDeclaration { type_id: 2, type_field: "u16".to_string(), ..Default::default() }, UnifiedTypeDeclaration { type_id: 3, type_field: "bool".to_string(), ..Default::default() }, UnifiedTypeDeclaration { type_id: 4, type_field: "generic T".to_string(), ..Default::default() }, ], ) } #[test] fn custom_types_uses_correct_path_for_sdk_provided_types() { let resolver = TypeResolver::default(); for (type_path, expected_path) in sdk_provided_custom_types_lookup() { // given let type_application = given_fn_arg_of_custom_type(&type_path); // when let resolved_type = resolver.resolve(&type_application).unwrap(); // then let expected_type_name = expected_path.into_token_stream(); assert_eq!( resolved_type.to_token_stream().to_string(), expected_type_name.to_string() ); } } fn given_fn_arg_of_custom_type(type_path: &TypePath) -> FullTypeApplication { FullTypeApplication { name: "some_arg".to_string(), type_decl: FullTypeDeclaration { type_field: format!("struct {type_path}"), components: vec![], type_parameters: vec![], alias_of: None, }, type_arguments: vec![], error_message: None, } } } ================================================ FILE: packages/fuels-code-gen/src/program_bindings/utils.rs ================================================ use std::collections::{HashMap, HashSet}; use fuel_abi_types::abi::full_program::FullTypeApplication; use inflector::Inflector; use itertools::Itertools; use proc_macro2::{Ident, TokenStream}; use quote::quote; use crate::{ error::Result, program_bindings::resolved_type::{GenericType, ResolvedType, TypeResolver}, utils::{self, TypePath, safe_ident}, }; #[derive(Debug)] pub(crate) struct Component { pub(crate) ident: Ident, pub(crate) resolved_type: ResolvedType, pub(crate) error_message: Option, } #[derive(Debug)] pub(crate) struct Components { components: Vec, } impl Components { pub fn new( type_applications: &[FullTypeApplication], snake_case: bool, parent_module: TypePath, ) -> Result { let type_resolver = TypeResolver::new(parent_module); let components = type_applications .iter() .map(|type_application| { let name = if snake_case { type_application.name.to_snake_case() } else { type_application.name.to_owned() }; let ident = safe_ident(&name); let resolved_type = type_resolver.resolve(type_application)?; let error_message = type_application.error_message.clone(); Result::Ok(Component { ident, resolved_type, error_message, }) }) .collect::>>()?; Ok(Self { components }) } pub fn has_error_messages(&self) -> bool { self.components .iter() .all(|component| component.error_message.is_some()) } pub fn iter(&self) -> impl Iterator { self.components.iter() } pub fn is_empty(&self) -> bool { self.components.is_empty() } pub fn as_enum_variants(&self) -> impl Iterator + '_ { self.components.iter().map( |Component { ident, resolved_type, .. }| { if let ResolvedType::Unit = resolved_type { quote! {#ident} } else { quote! {#ident(#resolved_type)} } }, ) } pub fn generate_parameters_for_unused_generics( &self, declared_generics: &[Ident], ) -> (Vec, Vec) { self.unused_named_generics(declared_generics) .enumerate() .map(|(index, generic)| { let ident = utils::ident(&format!("_unused_generic_{index}")); let ty = quote! {::core::marker::PhantomData<#generic>}; (ident, ty) }) .unzip() } pub fn generate_variant_for_unused_generics( &self, declared_generics: &[Ident], ) -> Option { let phantom_types = self .unused_named_generics(declared_generics) .map(|generic| { quote! {::core::marker::PhantomData<#generic>} }) .collect_vec(); (!phantom_types.is_empty()).then(|| { quote! { #[Ignore] IgnoreMe(#(#phantom_types),*) } }) } fn named_generics(&self) -> HashSet { self.components .iter() .flat_map(|Component { resolved_type, .. }| resolved_type.generics()) .filter_map(|generic_type| { if let GenericType::Named(name) = generic_type { Some(name) } else { None } }) .collect() } fn unused_named_generics<'a>( &'a self, declared_generics: &'a [Ident], ) -> impl Iterator { let used_generics = self.named_generics(); declared_generics .iter() .filter(move |generic| !used_generics.contains(generic)) } } pub(crate) fn tokenize_generics(generics: &[Ident]) -> (TokenStream, TokenStream) { if generics.is_empty() { return (Default::default(), Default::default()); } ( quote! {<#(#generics,)*>}, quote! {<#(#generics: ::fuels::core::traits::Tokenizable + ::fuels::core::traits::Parameterize, )*>}, ) } pub(crate) fn sdk_provided_custom_types_lookup() -> HashMap { [ ("std::address::Address", "::fuels::types::Address"), ("std::asset_id::AssetId", "::fuels::types::AssetId"), ("std::b512::B512", "::fuels::types::B512"), ("std::bytes::Bytes", "::fuels::types::Bytes"), ("std::contract_id::ContractId", "::fuels::types::ContractId"), ("std::identity::Identity", "::fuels::types::Identity"), ("std::option::Option", "::core::option::Option"), ("std::result::Result", "::core::result::Result"), ("std::string::String", "::std::string::String"), ("std::vec::Vec", "::std::vec::Vec"), ( "std::vm::evm::evm_address::EvmAddress", "::fuels::types::EvmAddress", ), ] .into_iter() .map(|(original_type_path, provided_type_path)| { let msg = "known at compile time to be correctly formed"; ( TypePath::new(original_type_path).expect(msg), TypePath::new(provided_type_path).expect(msg), ) }) .collect() } #[cfg(test)] mod tests { use fuel_abi_types::abi::full_program::FullTypeDeclaration; use super::*; #[test] fn respects_snake_case_flag() -> Result<()> { // given let type_application = type_application_named("WasNotSnakeCased"); // when let sut = Components::new(&[type_application], true, TypePath::default())?; // then assert_eq!(sut.iter().next().unwrap().ident, "was_not_snake_cased"); Ok(()) } #[test] fn avoids_collisions_with_reserved_keywords() -> Result<()> { { let type_application = type_application_named("if"); let sut = Components::new(&[type_application], false, TypePath::default())?; assert_eq!(sut.iter().next().unwrap().ident, "if_"); } { let type_application = type_application_named("let"); let sut = Components::new(&[type_application], false, TypePath::default())?; assert_eq!(sut.iter().next().unwrap().ident, "let_"); } Ok(()) } fn type_application_named(name: &str) -> FullTypeApplication { FullTypeApplication { name: name.to_string(), type_decl: FullTypeDeclaration { type_field: "u64".to_string(), components: vec![], type_parameters: vec![], alias_of: None, }, type_arguments: vec![], error_message: None, } } } ================================================ FILE: packages/fuels-code-gen/src/program_bindings.rs ================================================ mod abigen; mod custom_types; mod generated_code; mod resolved_type; mod utils; pub use abigen::{Abi, Abigen, AbigenTarget, ProgramType}; ================================================ FILE: packages/fuels-code-gen/src/utils.rs ================================================ pub use fuel_abi_types::utils::{TypePath, ident, safe_ident}; pub fn encode_fn_selector(name: &str) -> Vec { let bytes = name.as_bytes().to_vec(); let len = bytes.len() as u64; [len.to_be_bytes().to_vec(), bytes].concat() } ================================================ FILE: packages/fuels-core/Cargo.toml ================================================ [package] name = "fuels-core" version = { workspace = true } authors = { workspace = true } edition = { workspace = true } homepage = { workspace = true } license = { workspace = true } repository = { workspace = true } rust-version = { workspace = true } description = "Fuel Rust SDK core." [dependencies] async-trait = { workspace = true, default-features = false } chrono = { workspace = true } fuel-abi-types = { workspace = true } fuel-asm = { workspace = true } fuels-code-gen = { workspace = true } fuel-core-chain-config = { workspace = true } fuel-core-client = { workspace = true, optional = true } fuel-core-types = { workspace = true } fuel-crypto = { workspace = true } fuel-tx = { workspace = true } fuel-types = { workspace = true, features = ["default"] } fuel-vm = { workspace = true } fuels-macros = { workspace = true } hex = { workspace = true, features = ["std"] } itertools = { workspace = true } postcard = { version = "1", default-features = true, features = ["alloc"] } serde = { workspace = true, features = ["derive"] } serde_json = { workspace = true, default-features = true } sha2 = { workspace = true } thiserror = { workspace = true, default-features = false } uint = { workspace = true, default-features = false } auto_impl = { workspace = true } [dev-dependencies] fuel-tx = { workspace = true, features = ["test-helpers", "random"] } tokio = { workspace = true, features = ["test-util", "macros"] } [features] default = ["std"] std = ["dep:fuel-core-client", "fuel-core-types/std"] fault-proving = ["fuel-core-chain-config/fault-proving", "fuel-core-types/fault-proving", "fuel-core-client?/fault-proving"] ================================================ FILE: packages/fuels-core/src/codec/abi_decoder/bounded_decoder.rs ================================================ use crate::{ codec::{ DecoderConfig, utils::{CodecDirection, CounterWithLimit}, }, types::{ StaticStringToken, Token, U256, errors::{Result, error}, param_types::{EnumVariants, NamedParamType, ParamType}, }, }; use std::iter::repeat_n; use std::{io::Read, str}; /// Is used to decode bytes into `Token`s from which types implementing `Tokenizable` can be /// instantiated. Implements decoding limits to control resource usage. pub(crate) struct BoundedDecoder { depth_tracker: CounterWithLimit, token_tracker: CounterWithLimit, } impl BoundedDecoder { pub(crate) fn new(config: DecoderConfig) -> Self { let depth_tracker = CounterWithLimit::new(config.max_depth, "depth", CodecDirection::Decoding); let token_tracker = CounterWithLimit::new(config.max_tokens, "token", CodecDirection::Decoding); Self { depth_tracker, token_tracker, } } pub(crate) fn decode( &mut self, param_type: &ParamType, bytes: &mut R, ) -> Result { self.decode_param(param_type, bytes) } pub(crate) fn decode_multiple( &mut self, param_types: &[ParamType], bytes: &mut R, ) -> Result> { self.decode_params(param_types, bytes) } fn run_w_depth_tracking( &mut self, decoder: impl FnOnce(&mut Self) -> Result, ) -> Result { self.depth_tracker.increase()?; let res = decoder(self); self.depth_tracker.decrease(); res } fn decode_param(&mut self, param_type: &ParamType, bytes: &mut R) -> Result { self.token_tracker.increase()?; match param_type { ParamType::Unit => Ok(Token::Unit), ParamType::Bool => decode(bytes, |[value]| Token::Bool(value != 0)), ParamType::U8 => decode(bytes, |[value]| Token::U8(value)), ParamType::U16 => decode(bytes, |value| Token::U16(u16::from_be_bytes(value))), ParamType::U32 => decode(bytes, |value| Token::U32(u32::from_be_bytes(value))), ParamType::U64 => decode(bytes, |value| Token::U64(u64::from_be_bytes(value))), ParamType::U128 => decode(bytes, |value| Token::U128(u128::from_be_bytes(value))), ParamType::U256 => decode(bytes, |value| Token::U256(U256::from(value))), ParamType::B256 => decode(bytes, Token::B256), ParamType::Bytes => Ok(Token::Bytes(decode_slice(bytes)?)), ParamType::String => Self::decode_std_string(bytes), ParamType::RawSlice => Ok(Token::RawSlice(decode_slice(bytes)?)), ParamType::StringArray(length) => Self::decode_string_array(bytes, *length), ParamType::StringSlice => Self::decode_string_slice(bytes), ParamType::Tuple(param_types) => { self.run_w_depth_tracking(|ctx| ctx.decode_tuple(param_types, bytes)) } ParamType::Array(param_type, length) => { self.run_w_depth_tracking(|ctx| ctx.decode_array(param_type, bytes, *length)) } ParamType::Vector(param_type) => { self.run_w_depth_tracking(|ctx| ctx.decode_vector(param_type, bytes)) } ParamType::Struct { fields, .. } => { self.run_w_depth_tracking(|ctx| ctx.decode_struct(fields, bytes)) } ParamType::Enum { enum_variants, .. } => { self.run_w_depth_tracking(|ctx| ctx.decode_enum(enum_variants, bytes)) } } } fn decode_std_string(bytes: &mut R) -> Result { let data = decode_slice(bytes)?; let string = str::from_utf8(&data)?.to_string(); Ok(Token::String(string)) } fn decode_string_array(bytes: &mut R, length: usize) -> Result { let data = decode_sized(bytes, length)?; let decoded = str::from_utf8(&data)?.to_string(); Ok(Token::StringArray(StaticStringToken::new( decoded, Some(length), ))) } fn decode_string_slice(bytes: &mut R) -> Result { let data = decode_slice(bytes)?; let decoded = str::from_utf8(&data)?.to_string(); Ok(Token::StringSlice(StaticStringToken::new(decoded, None))) } fn decode_tuple(&mut self, param_types: &[ParamType], bytes: &mut R) -> Result { Ok(Token::Tuple(self.decode_params(param_types, bytes)?)) } fn decode_array( &mut self, param_type: &ParamType, bytes: &mut R, length: usize, ) -> Result { Ok(Token::Array( self.decode_params(repeat_n(param_type, length), bytes)?, )) } fn decode_vector(&mut self, param_type: &ParamType, bytes: &mut R) -> Result { let length = decode_len(bytes)?; Ok(Token::Vector( self.decode_params(repeat_n(param_type, length), bytes)?, )) } fn decode_struct( &mut self, fields: &[NamedParamType], bytes: &mut R, ) -> Result { Ok(Token::Struct( self.decode_params(fields.iter().map(|(_, pt)| pt), bytes)?, )) } fn decode_enum( &mut self, enum_variants: &EnumVariants, bytes: &mut R, ) -> Result { let discriminant = decode(bytes, u64::from_be_bytes)?; let (_, selected_variant) = enum_variants.select_variant(discriminant)?; let decoded = self.decode_param(selected_variant, bytes)?; Ok(Token::Enum(Box::new(( discriminant, decoded, enum_variants.clone(), )))) } fn decode_params<'a, R: Read>( &mut self, param_types: impl IntoIterator, bytes: &mut R, ) -> Result> { let mut tokens = vec![]; for param_type in param_types { tokens.push(self.decode_param(param_type, bytes)?); } Ok(tokens) } } /// Decodes a fixed-size array of bytes using a converter function. fn decode( bytes: &mut R, f: impl FnOnce([u8; SIZE]) -> Out, ) -> Result { let mut buffer = [0u8; SIZE]; bytes.read_exact(&mut buffer)?; Ok(f(buffer)) } /// Reads a byte array with known size. fn decode_sized(bytes: &mut R, len: usize) -> Result> { let mut data = vec![0; len]; bytes.read_exact(&mut data)?; Ok(data) } /// Decodes a length prefix. fn decode_len(bytes: &mut R) -> Result { let len_u64 = decode(bytes, u64::from_be_bytes)?; let len: usize = len_u64 .try_into() .map_err(|_| error!(Other, "could not convert `u64` to `usize`"))?; Ok(len) } /// Decodes a size-prefixed slice. fn decode_slice(bytes: &mut R) -> Result> { let len = decode_len(bytes)?; let mut data = vec![0; len]; bytes.read_exact(&mut data)?; Ok(data) } ================================================ FILE: packages/fuels-core/src/codec/abi_decoder/decode_as_debug_str.rs ================================================ use std::iter::zip; use crate::types::{ Token, errors::{Result, error}, param_types::ParamType, }; fn inner_types_debug(tokens: &[Token], inner_type: &ParamType, join_str: &str) -> Result { let inner_types_log = tokens .iter() .map(|token| decode_as_debug_str(inner_type, token)) .collect::>>()? .join(join_str); Ok(inner_types_log) } pub(crate) fn decode_as_debug_str(param_type: &ParamType, token: &Token) -> Result { let result = match (param_type, token) { (ParamType::Unit, Token::Unit) => "()".to_string(), (ParamType::Bool, Token::Bool(val)) => val.to_string(), (ParamType::U8, Token::U8(val)) => val.to_string(), (ParamType::U16, Token::U16(val)) => val.to_string(), (ParamType::U32, Token::U32(val)) => val.to_string(), (ParamType::U64, Token::U64(val)) => val.to_string(), (ParamType::U128, Token::U128(val)) => val.to_string(), (ParamType::U256, Token::U256(val)) => val.to_string(), (ParamType::B256, Token::B256(val)) => { format!("Bits256({val:?})") } (ParamType::Bytes, Token::Bytes(val)) => { format!("Bytes({val:?})") } (ParamType::String, Token::String(val)) => val.clone(), (ParamType::RawSlice, Token::RawSlice(val)) => { format!("RawSlice({val:?})") } (ParamType::StringArray(..), Token::StringArray(str_token)) => { format!("SizedAsciiString {{ data: \"{}\" }}", str_token.data) } (ParamType::StringSlice, Token::StringSlice(str_token)) => { format!("AsciiString {{ data: \"{}\" }}", str_token.data) } (ParamType::Tuple(types), Token::Tuple(tokens)) => { let elements = zip(types, tokens) .map(|(ptype, token)| decode_as_debug_str(ptype, token)) .collect::>>()? .join(", "); format!("({elements})") } (ParamType::Array(inner_type, _), Token::Array(tokens)) => { let elements = inner_types_debug(tokens, inner_type, ", ")?; format!("[{elements}]") } (ParamType::Vector(inner_type), Token::Vector(tokens)) => { let elements = inner_types_debug(tokens, inner_type, ", ")?; format!("[{elements}]") } (ParamType::Struct { name, fields, .. }, Token::Struct(field_tokens)) => { let fields = zip(fields, field_tokens) .map(|((field_name, param_type), token)| -> Result<_> { Ok(format!( "{field_name}: {}", decode_as_debug_str(param_type, token)? )) }) .collect::>>()? .join(", "); format!("{name} {{ {fields} }}") } (ParamType::Enum { .. }, Token::Enum(selector)) => { let (discriminant, token, variants) = selector.as_ref(); let (variant_name, variant_param_type) = variants.select_variant(*discriminant)?; let variant_str = decode_as_debug_str(variant_param_type, token)?; let variant_str = if variant_str == "()" { "".into() } else { format!("({variant_str})") }; format!("{variant_name}{variant_str}") } _ => { return Err(error!( Codec, "could not decode debug from param type: `{param_type:?}` and token: `{token:?}`" )); } }; Ok(result) } #[cfg(test)] mod tests { use crate::{ codec::ABIDecoder, traits::Parameterize, types::{ AsciiString, Bits256, Bytes, EvmAddress, RawSlice, SizedAsciiString, U256, errors::Result, }, }; #[test] fn param_type_decode_debug() -> Result<()> { let decoder = ABIDecoder::default(); { assert_eq!( format!("{:?}", true), decoder.decode_as_debug_str(&bool::param_type(), [1].as_slice())? ); assert_eq!( format!("{:?}", 128u8), decoder.decode_as_debug_str(&u8::param_type(), [128].as_slice())? ); assert_eq!( format!("{:?}", 256u16), decoder.decode_as_debug_str(&u16::param_type(), [1, 0].as_slice())? ); assert_eq!( format!("{:?}", 512u32), decoder.decode_as_debug_str(&u32::param_type(), [0, 0, 2, 0].as_slice())? ); assert_eq!( format!("{:?}", 1024u64), decoder .decode_as_debug_str(&u64::param_type(), [0, 0, 0, 0, 0, 0, 4, 0].as_slice())? ); assert_eq!( format!("{:?}", 1024u128), decoder.decode_as_debug_str( &u128::param_type(), [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 0].as_slice() )? ); assert_eq!( format!("{:?}", U256::from(2048)), decoder.decode_as_debug_str( &U256::param_type(), [ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8, 0 ] .as_slice() )? ); } { let bytes = [ 239, 134, 175, 169, 105, 108, 240, 220, 99, 133, 226, 196, 7, 166, 225, 89, 161, 16, 60, 239, 183, 226, 174, 6, 54, 251, 51, 211, 203, 42, 158, 74, ]; let bits256 = Bits256(bytes); assert_eq!( format!("{bits256:?}"), decoder.decode_as_debug_str( &Bits256::param_type(), [ 239, 134, 175, 169, 105, 108, 240, 220, 99, 133, 226, 196, 7, 166, 225, 89, 161, 16, 60, 239, 183, 226, 174, 6, 54, 251, 51, 211, 203, 42, 158, 74 ] .as_slice() )? ); assert_eq!( format!("{:?}", Bytes(bytes.to_vec())), decoder.decode_as_debug_str( &Bytes::param_type(), [ 0, 0, 0, 0, 0, 0, 0, 32, 239, 134, 175, 169, 105, 108, 240, 220, 99, 133, 226, 196, 7, 166, 225, 89, 161, 16, 60, 239, 183, 226, 174, 6, 54, 251, 51, 211, 203, 42, 158, 74 ] .as_slice() )? ); assert_eq!( format!("{:?}", RawSlice(bytes.to_vec())), decoder.decode_as_debug_str( &RawSlice::param_type(), [ 0, 0, 0, 0, 0, 0, 0, 32, 239, 134, 175, 169, 105, 108, 240, 220, 99, 133, 226, 196, 7, 166, 225, 89, 161, 16, 60, 239, 183, 226, 174, 6, 54, 251, 51, 211, 203, 42, 158, 74 ] .as_slice() )? ); assert_eq!( format!("{:?}", EvmAddress::from(bits256)), decoder.decode_as_debug_str( &EvmAddress::param_type(), [ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7, 166, 225, 89, 161, 16, 60, 239, 183, 226, 174, 6, 54, 251, 51, 211, 203, 42, 158, 74 ] .as_slice() )? ); } { assert_eq!( format!("{:?}", AsciiString::new("Fuel".to_string())?), decoder.decode_as_debug_str( &AsciiString::param_type(), [0, 0, 0, 0, 0, 0, 0, 4, 70, 117, 101, 108].as_slice() )? ); assert_eq!( format!("{:?}", SizedAsciiString::<4>::new("Fuel".to_string())?), decoder.decode_as_debug_str( &SizedAsciiString::<4>::param_type(), [70, 117, 101, 108, 0, 0, 0, 0].as_slice() )? ); assert_eq!( format!("{}", "Fuel"), decoder.decode_as_debug_str( &String::param_type(), [0, 0, 0, 0, 0, 0, 0, 4, 70, 117, 101, 108].as_slice() )? ); } { assert_eq!( format!("{:?}", (1, 2)), decoder.decode_as_debug_str(&<(u8, u8)>::param_type(), [1, 2].as_slice())? ); assert_eq!( format!("{:?}", [3, 4]), decoder.decode_as_debug_str( &<[u64; 2]>::param_type(), [0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 4].as_slice() )? ); } { assert_eq!( format!("{:?}", Some(42)), decoder.decode_as_debug_str( &>::param_type(), [0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 42].as_slice() )? ); assert_eq!( format!("{:?}", Err::(42u64)), decoder.decode_as_debug_str( &>::param_type(), [0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 42].as_slice() )? ); } Ok(()) } } ================================================ FILE: packages/fuels-core/src/codec/abi_decoder.rs ================================================ mod bounded_decoder; mod decode_as_debug_str; use std::io::Read; use crate::{ codec::abi_decoder::{ bounded_decoder::BoundedDecoder, decode_as_debug_str::decode_as_debug_str, }, types::{Token, errors::Result, param_types::ParamType}, }; #[derive(Debug, Clone, Copy)] pub struct DecoderConfig { /// Entering a struct, array, tuple, enum or vector increases the depth. Decoding will fail if /// the current depth becomes greater than `max_depth` configured here. pub max_depth: usize, /// Every decoded Token will increase the token count. Decoding will fail if the current /// token count becomes greater than `max_tokens` configured here. pub max_tokens: usize, } // ANCHOR: default_decoder_config impl Default for DecoderConfig { fn default() -> Self { Self { max_depth: 45, max_tokens: 10_000, } } } // ANCHOR_END: default_decoder_config #[derive(Default)] pub struct ABIDecoder { pub config: DecoderConfig, } impl ABIDecoder { pub fn new(config: DecoderConfig) -> Self { Self { config } } /// Decodes `bytes` following the schema described in `param_type` into its respective `Token`. /// /// # Arguments /// /// * `param_type`: The `ParamType` of the type we expect is encoded inside `bytes`. /// * `bytes`: The bytes to be used in the decoding process. /// # Examples /// /// ``` /// use fuels_core::codec::ABIDecoder; /// use fuels_core::traits::Tokenizable; /// use fuels_core::types::param_types::ParamType; /// /// let decoder = ABIDecoder::default(); /// /// let token = decoder.decode(&ParamType::U64, [0, 0, 0, 0, 0, 0, 0, 7].as_slice()).unwrap(); /// /// assert_eq!(u64::from_token(token).unwrap(), 7u64); /// ``` pub fn decode(&self, param_type: &ParamType, mut bytes: impl Read) -> Result { BoundedDecoder::new(self.config).decode(param_type, &mut bytes) } /// Same as `decode` but decodes multiple `ParamType`s in one go. /// # Examples /// ``` /// use fuels_core::codec::ABIDecoder; /// use fuels_core::types::param_types::ParamType; /// use fuels_core::types::Token; /// /// let decoder = ABIDecoder::default(); /// let data = [7, 8]; /// /// let tokens = decoder.decode_multiple(&[ParamType::U8, ParamType::U8], data.as_slice()).unwrap(); /// /// assert_eq!(tokens, vec![Token::U8(7), Token::U8(8)]); /// ``` pub fn decode_multiple( &self, param_types: &[ParamType], mut bytes: impl Read, ) -> Result> { BoundedDecoder::new(self.config).decode_multiple(param_types, &mut bytes) } /// Decodes `bytes` following the schema described in `param_type` into its respective debug /// string. /// /// # Arguments /// /// * `param_type`: The `ParamType` of the type we expect is encoded inside `bytes`. /// * `bytes`: The bytes to be used in the decoding process. /// # Examples /// /// ``` /// use fuels_core::codec::ABIDecoder; /// use fuels_core::types::param_types::ParamType; /// /// let decoder = ABIDecoder::default(); /// /// let debug_string = decoder.decode_as_debug_str(&ParamType::U64, [0, 0, 0, 0, 0, 0, 0, 7].as_slice()).unwrap(); /// let expected_value = 7u64; /// /// assert_eq!(debug_string, format!("{expected_value}")); /// ``` pub fn decode_as_debug_str( &self, param_type: &ParamType, mut bytes: impl Read, ) -> Result { let token = BoundedDecoder::new(self.config).decode(param_type, &mut bytes)?; decode_as_debug_str(param_type, &token) } pub fn decode_multiple_as_debug_str( &self, param_types: &[ParamType], mut bytes: impl Read, ) -> Result> { let token = BoundedDecoder::new(self.config).decode_multiple(param_types, &mut bytes)?; token .into_iter() .zip(param_types) .map(|(token, param_type)| decode_as_debug_str(param_type, &token)) .collect() } } #[cfg(test)] mod tests { use std::vec; use ParamType::*; use super::*; use crate::{ constants::WORD_SIZE, to_named, traits::Parameterize, types::{StaticStringToken, U256, errors::Error, param_types::EnumVariants}, }; #[test] fn decode_multiple_uint() -> Result<()> { let types = vec![ ParamType::U8, ParamType::U16, ParamType::U32, ParamType::U64, ParamType::U128, ParamType::U256, ]; let data = [ 255, // u8 255, 255, // u16 255, 255, 255, 255, // u32 255, 255, 255, 255, 255, 255, 255, 255, // u64 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, // u128 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, // u256 ]; let decoded = ABIDecoder::default().decode_multiple(&types, data.as_slice())?; let expected = vec![ Token::U8(u8::MAX), Token::U16(u16::MAX), Token::U32(u32::MAX), Token::U64(u64::MAX), Token::U128(u128::MAX), Token::U256(U256::MAX), ]; assert_eq!(decoded, expected); Ok(()) } #[test] fn decode_bool() -> Result<()> { let types = vec![ParamType::Bool, ParamType::Bool]; let data = [1, 0]; let decoded = ABIDecoder::default().decode_multiple(&types, data.as_slice())?; let expected = vec![Token::Bool(true), Token::Bool(false)]; assert_eq!(decoded, expected); Ok(()) } #[test] fn decode_b256() -> Result<()> { let data = [ 213, 87, 156, 70, 223, 204, 127, 24, 32, 112, 19, 230, 91, 68, 228, 203, 78, 44, 34, 152, 244, 172, 69, 123, 168, 248, 39, 67, 243, 30, 147, 11, ]; let decoded = ABIDecoder::default().decode(&ParamType::B256, data.as_slice())?; assert_eq!(decoded, Token::B256(data)); Ok(()) } #[test] fn decode_string_array() -> Result<()> { let types = vec![ParamType::StringArray(23), ParamType::StringArray(5)]; let data = [ 84, 104, 105, 115, 32, 105, 115, 32, 97, 32, 102, 117, 108, 108, 32, 115, 101, 110, 116, 101, 110, 99, 101, //This is a full sentence 72, 101, 108, 108, 111, // Hello ]; let decoded = ABIDecoder::default().decode_multiple(&types, data.as_slice())?; let expected = vec![ Token::StringArray(StaticStringToken::new( "This is a full sentence".into(), Some(23), )), Token::StringArray(StaticStringToken::new("Hello".into(), Some(5))), ]; assert_eq!(decoded, expected); Ok(()) } #[test] fn decode_string_slice() -> Result<()> { let data = [ 0, 0, 0, 0, 0, 0, 0, 23, // [length] 84, 104, 105, 115, 32, 105, 115, 32, 97, 32, 102, 117, 108, 108, 32, 115, 101, 110, 116, 101, 110, 99, 101, //This is a full sentence ]; let decoded = ABIDecoder::default().decode(&ParamType::StringSlice, data.as_slice())?; let expected = Token::StringSlice(StaticStringToken::new( "This is a full sentence".into(), None, )); assert_eq!(decoded, expected); Ok(()) } #[test] fn decode_string() -> Result<()> { let data = [ 0, 0, 0, 0, 0, 0, 0, 23, // [length] 84, 104, 105, 115, 32, 105, 115, 32, 97, 32, 102, 117, 108, 108, 32, 115, 101, 110, 116, 101, 110, 99, 101, //This is a full sentence ]; let decoded = ABIDecoder::default().decode(&ParamType::String, data.as_slice())?; let expected = Token::String("This is a full sentence".to_string()); assert_eq!(decoded, expected); Ok(()) } #[test] fn decode_tuple() -> Result<()> { let param_type = ParamType::Tuple(vec![ParamType::U32, ParamType::Bool]); let data = [ 0, 0, 0, 255, //u32 1, //bool ]; let result = ABIDecoder::default().decode(¶m_type, data.as_slice())?; let expected = Token::Tuple(vec![Token::U32(255), Token::Bool(true)]); assert_eq!(result, expected); Ok(()) } #[test] fn decode_array() -> Result<()> { let types = vec![ParamType::Array(Box::new(ParamType::U8), 2)]; let data = [255, 42]; let decoded = ABIDecoder::default().decode_multiple(&types, data.as_slice())?; let expected = vec![Token::Array(vec![Token::U8(255), Token::U8(42)])]; assert_eq!(decoded, expected); Ok(()) } #[test] fn decode_struct() -> Result<()> { // struct MyStruct { // foo: u8, // bar: bool, // } let data = [1, 1]; let param_type = ParamType::Struct { name: "".to_string(), fields: to_named(&[ParamType::U8, ParamType::Bool]), generics: vec![], }; let decoded = ABIDecoder::default().decode(¶m_type, data.as_slice())?; let expected = Token::Struct(vec![Token::U8(1), Token::Bool(true)]); assert_eq!(decoded, expected); Ok(()) } #[test] fn decode_bytes() -> Result<()> { let data = [0, 0, 0, 0, 0, 0, 0, 7, 255, 0, 1, 2, 3, 4, 5]; let decoded = ABIDecoder::default().decode(&ParamType::Bytes, data.as_slice())?; let expected = Token::Bytes([255, 0, 1, 2, 3, 4, 5].to_vec()); assert_eq!(decoded, expected); Ok(()) } #[test] fn decode_raw_slice() -> Result<()> { let data = [0, 0, 0, 0, 0, 0, 0, 7, 255, 0, 1, 2, 3, 4, 5]; let decoded = ABIDecoder::default().decode(&ParamType::RawSlice, data.as_slice())?; let expected = Token::RawSlice([255, 0, 1, 2, 3, 4, 5].to_vec()); assert_eq!(decoded, expected); Ok(()) } #[test] fn decode_enum() -> Result<()> { // enum MyEnum { // x: u32, // y: bool, // } let types = to_named(&[ParamType::U32, ParamType::Bool]); let inner_enum_types = EnumVariants::new(types)?; let types = vec![ParamType::Enum { name: "".to_string(), enum_variants: inner_enum_types.clone(), generics: vec![], }]; let data = [ 0, 0, 0, 0, 0, 0, 0, 0, // discriminant 0, 0, 0, 42, // u32 ]; let decoded = ABIDecoder::default().decode_multiple(&types, data.as_slice())?; let expected = vec![Token::Enum(Box::new((0, Token::U32(42), inner_enum_types)))]; assert_eq!(decoded, expected); Ok(()) } #[test] fn decode_nested_struct() -> Result<()> { // struct Foo { // x: u16, // y: Bar, // } // // struct Bar { // a: bool, // b: u8[2], // } let fields = to_named(&[ ParamType::U16, ParamType::Struct { name: "".to_string(), fields: to_named(&[ ParamType::Bool, ParamType::Array(Box::new(ParamType::U8), 2), ]), generics: vec![], }, ]); let nested_struct = ParamType::Struct { name: "".to_string(), fields, generics: vec![], }; let data = [0, 10, 1, 1, 2]; let decoded = ABIDecoder::default().decode(&nested_struct, data.as_slice())?; let my_nested_struct = vec![ Token::U16(10), Token::Struct(vec![ Token::Bool(true), Token::Array(vec![Token::U8(1), Token::U8(2)]), ]), ]; assert_eq!(decoded, Token::Struct(my_nested_struct)); Ok(()) } #[test] fn decode_comprehensive() -> Result<()> { // struct Foo { // x: u16, // y: Bar, // } // // struct Bar { // a: bool, // b: u8[2], // } // fn: long_function(Foo,u8[2],b256,str[3],str) // Parameters let fields = to_named(&[ ParamType::U16, ParamType::Struct { name: "".to_string(), fields: to_named(&[ ParamType::Bool, ParamType::Array(Box::new(ParamType::U8), 2), ]), generics: vec![], }, ]); let nested_struct = ParamType::Struct { name: "".to_string(), fields, generics: vec![], }; let u8_arr = ParamType::Array(Box::new(ParamType::U8), 2); let b256 = ParamType::B256; let types = [nested_struct, u8_arr, b256]; let bytes = [ 0, 10, // u16 1, // bool 1, 2, // array[u8;2] 1, 2, // array[u8;2] 213, 87, 156, 70, 223, 204, 127, 24, 32, 112, 19, 230, 91, 68, 228, 203, 78, 44, 34, 152, 244, 172, 69, 123, 168, 248, 39, 67, 243, 30, 147, 11, // b256 ]; let decoded = ABIDecoder::default().decode_multiple(&types, bytes.as_slice())?; // Expected tokens let foo = Token::Struct(vec![ Token::U16(10), Token::Struct(vec![ Token::Bool(true), Token::Array(vec![Token::U8(1), Token::U8(2)]), ]), ]); let u8_arr = Token::Array(vec![Token::U8(1), Token::U8(2)]); let b256 = Token::B256([ 213, 87, 156, 70, 223, 204, 127, 24, 32, 112, 19, 230, 91, 68, 228, 203, 78, 44, 34, 152, 244, 172, 69, 123, 168, 248, 39, 67, 243, 30, 147, 11, ]); let expected: Vec = vec![foo, u8_arr, b256]; assert_eq!(decoded, expected); Ok(()) } #[test] fn enums_with_all_unit_variants_are_decoded_from_one_word() -> Result<()> { let data = [0, 0, 0, 0, 0, 0, 0, 1]; let types = to_named(&[ParamType::Unit, ParamType::Unit]); let enum_variants = EnumVariants::new(types)?; let enum_w_only_units = ParamType::Enum { name: "".to_string(), enum_variants: enum_variants.clone(), generics: vec![], }; let result = ABIDecoder::default().decode(&enum_w_only_units, data.as_slice())?; let expected_enum = Token::Enum(Box::new((1, Token::Unit, enum_variants))); assert_eq!(result, expected_enum); Ok(()) } #[test] fn out_of_bounds_discriminant_is_detected() -> Result<()> { let data = [0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 2]; let types = to_named(&[ParamType::U64]); let enum_variants = EnumVariants::new(types)?; let enum_type = ParamType::Enum { name: "".to_string(), enum_variants, generics: vec![], }; let result = ABIDecoder::default().decode(&enum_type, data.as_slice()); let error = result.expect_err("should have resulted in an error"); let expected_msg = "discriminant `1` doesn't point to any variant: "; assert!(matches!(error, Error::Other(str) if str.starts_with(expected_msg))); Ok(()) } #[test] pub fn division_by_zero() { let param_type = Vec::<[u16; 0]>::param_type(); let result = ABIDecoder::default().decode(¶m_type, [].as_slice()); assert!(matches!(result, Err(Error::IO(_)))); } #[test] pub fn multiply_overflow_enum() { let result = ABIDecoder::default().decode( &Enum { name: "".to_string(), enum_variants: EnumVariants::new(to_named(&[ Array(Box::new(Array(Box::new(RawSlice), 8)), usize::MAX), B256, B256, B256, B256, B256, B256, B256, B256, B256, B256, ])) .unwrap(), generics: vec![U16], }, [].as_slice(), ); assert!(matches!(result, Err(Error::IO(_)))); } #[test] pub fn multiply_overflow_arith() { let mut param_type: ParamType = U16; for _ in 0..50 { param_type = Array(Box::new(param_type), 8); } let result = ABIDecoder::default().decode( &Enum { name: "".to_string(), enum_variants: EnumVariants::new(to_named(&[param_type])).unwrap(), generics: vec![U16], }, [].as_slice(), ); assert!(matches!(result, Err(Error::IO(_)))); } #[test] pub fn capacity_overflow() { let result = ABIDecoder::default().decode( &Array(Box::new(Array(Box::new(Tuple(vec![])), usize::MAX)), 1), [].as_slice(), ); assert!(matches!(result, Err(Error::Codec(_)))); } #[test] pub fn stack_overflow() { let mut param_type: ParamType = U16; for _ in 0..13500 { param_type = Vector(Box::new(param_type)); } let result = ABIDecoder::default().decode(¶m_type, [].as_slice()); assert!(matches!(result, Err(Error::IO(_)))); } #[test] pub fn capacity_malloc() { let param_type = Array(Box::new(U8), usize::MAX); let result = ABIDecoder::default().decode(¶m_type, [].as_slice()); assert!(matches!(result, Err(Error::IO(_)))); } #[test] fn max_depth_surpassed() { const MAX_DEPTH: usize = 2; let config = DecoderConfig { max_depth: MAX_DEPTH, ..Default::default() }; let msg = format!("depth limit `{MAX_DEPTH}` reached while decoding. Try increasing it"); // for each nested enum so that it may read the discriminant let data = [0; MAX_DEPTH * WORD_SIZE]; [nested_struct, nested_enum, nested_tuple, nested_array] .iter() .map(|fun| fun(MAX_DEPTH + 1)) .for_each(|param_type| { assert_decoding_failed_w_data(config, ¶m_type, &msg, data.as_slice()); }) } #[test] fn depth_is_not_reached() { const MAX_DEPTH: usize = 3; const ACTUAL_DEPTH: usize = MAX_DEPTH - 1; // enough data to decode 2*ACTUAL_DEPTH enums (discriminant + u8 = 2*WORD_SIZE) let data = [0; 2 * ACTUAL_DEPTH * (WORD_SIZE * 2)]; let config = DecoderConfig { max_depth: MAX_DEPTH, ..Default::default() }; [nested_struct, nested_enum, nested_tuple, nested_array] .into_iter() .map(|fun| fun(ACTUAL_DEPTH)) .map(|param_type| { // Wrapping everything in a structure so that we may check whether the depth is // decremented after finishing every struct field. ParamType::Struct { name: "".to_string(), fields: to_named(&[param_type.clone(), param_type]), generics: vec![], } }) .for_each(|param_type| { ABIDecoder::new(config) .decode(¶m_type, data.as_slice()) .unwrap(); }) } #[test] fn too_many_tokens() { let config = DecoderConfig { max_tokens: 3, ..Default::default() }; { let data = [0; 3 * WORD_SIZE]; let inner_param_types = vec![ParamType::U64; 3]; for param_type in [ ParamType::Struct { name: "".to_string(), fields: to_named(&inner_param_types), generics: vec![], }, ParamType::Tuple(inner_param_types.clone()), ParamType::Array(Box::new(ParamType::U64), 3), ] { assert_decoding_failed_w_data( config, ¶m_type, "token limit `3` reached while decoding. Try increasing it", &data, ); } } { let data = [0, 0, 0, 0, 0, 0, 0, 3, 1, 2, 3]; assert_decoding_failed_w_data( config, &ParamType::Vector(Box::new(ParamType::U8)), "token limit `3` reached while decoding. Try increasing it", &data, ); } } #[test] fn token_count_is_being_reset_between_decodings() { // given let config = DecoderConfig { max_tokens: 3, ..Default::default() }; let param_type = ParamType::Array(Box::new(ParamType::StringArray(0)), 2); let decoder = ABIDecoder::new(config); decoder.decode(¶m_type, [].as_slice()).unwrap(); // when let result = decoder.decode(¶m_type, [].as_slice()); // then result.expect("element count to be reset"); } fn assert_decoding_failed_w_data( config: DecoderConfig, param_type: &ParamType, msg: &str, data: &[u8], ) { let decoder = ABIDecoder::new(config); let err = decoder.decode(param_type, data); let Err(Error::Codec(actual_msg)) = err else { panic!("expected a `Codec` error. Got: `{err:?}`"); }; assert_eq!(actual_msg, msg); } fn nested_struct(depth: usize) -> ParamType { let fields = if depth == 1 { vec![] } else { to_named(&[nested_struct(depth - 1)]) }; ParamType::Struct { name: "".to_string(), fields, generics: vec![], } } fn nested_enum(depth: usize) -> ParamType { let fields = if depth == 1 { to_named(&[ParamType::U8]) } else { to_named(&[nested_enum(depth - 1)]) }; ParamType::Enum { name: "".to_string(), enum_variants: EnumVariants::new(fields).unwrap(), generics: vec![], } } fn nested_array(depth: usize) -> ParamType { let field = if depth == 1 { ParamType::U8 } else { nested_array(depth - 1) }; ParamType::Array(Box::new(field), 1) } fn nested_tuple(depth: usize) -> ParamType { let fields = if depth == 1 { vec![ParamType::U8] } else { vec![nested_tuple(depth - 1)] }; ParamType::Tuple(fields) } } ================================================ FILE: packages/fuels-core/src/codec/abi_encoder/bounded_encoder.rs ================================================ use crate::{ codec::{ EncoderConfig, utils::{CodecDirection, CounterWithLimit}, }, types::{EnumSelector, StaticStringToken, Token, U256, errors::Result}, }; pub(crate) struct BoundedEncoder { depth_tracker: CounterWithLimit, token_tracker: CounterWithLimit, } impl BoundedEncoder { pub(crate) fn new(config: EncoderConfig) -> Self { let depth_tracker = CounterWithLimit::new(config.max_depth, "depth", CodecDirection::Encoding); let token_tracker = CounterWithLimit::new(config.max_tokens, "token", CodecDirection::Encoding); Self { depth_tracker, token_tracker, } } pub fn encode(&mut self, tokens: &[Token]) -> Result> { let mut data = vec![]; for token in tokens.iter() { let new_data = self.encode_token(token)?; data.extend(new_data); } Ok(data) } fn run_w_depth_tracking( &mut self, encoder: impl FnOnce(&mut Self) -> Result>, ) -> Result> { self.depth_tracker.increase()?; let res = encoder(self); self.depth_tracker.decrease(); res } fn encode_token(&mut self, arg: &Token) -> Result> { self.token_tracker.increase()?; let encoded_token = match arg { Token::Unit => vec![], Token::Bool(arg_bool) => vec![u8::from(*arg_bool)], Token::U8(arg_u8) => vec![*arg_u8], Token::U16(arg_u16) => arg_u16.to_be_bytes().to_vec(), Token::U32(arg_u32) => arg_u32.to_be_bytes().to_vec(), Token::U64(arg_u64) => arg_u64.to_be_bytes().to_vec(), Token::U128(arg_u128) => arg_u128.to_be_bytes().to_vec(), Token::U256(arg_u256) => Self::encode_u256(*arg_u256), Token::B256(arg_bits256) => arg_bits256.to_vec(), Token::Bytes(data) => Self::encode_bytes(data.to_vec())?, Token::String(string) => Self::encode_bytes(string.clone().into_bytes())?, Token::RawSlice(data) => Self::encode_bytes(data.clone())?, Token::StringArray(arg_string) => Self::encode_string_array(arg_string)?, Token::StringSlice(arg_string) => Self::encode_string_slice(arg_string)?, Token::Tuple(arg_tuple) => self.run_w_depth_tracking(|ctx| ctx.encode(arg_tuple))?, Token::Array(arg_array) => self.run_w_depth_tracking(|ctx| ctx.encode(arg_array))?, Token::Vector(data) => self.run_w_depth_tracking(|ctx| ctx.encode_vector(data))?, Token::Struct(arg_struct) => self.run_w_depth_tracking(|ctx| ctx.encode(arg_struct))?, Token::Enum(arg_enum) => self.run_w_depth_tracking(|ctx| ctx.encode_enum(arg_enum))?, }; Ok(encoded_token) } fn encode_u256(arg_u256: U256) -> Vec { let mut bytes = [0u8; 32]; arg_u256.to_big_endian(&mut bytes); bytes.to_vec() } fn encode_bytes(data: Vec) -> Result> { let len = data.len(); Ok([Self::encode_length(len as u64), data].concat()) } fn encode_string_array(arg_string: &StaticStringToken) -> Result> { Ok(arg_string.get_encodable_str()?.as_bytes().to_vec()) } fn encode_string_slice(arg_string: &StaticStringToken) -> Result> { Self::encode_bytes(arg_string.get_encodable_str()?.as_bytes().to_vec()) } fn encode_vector(&mut self, data: &[Token]) -> Result> { let encoded_data = self.encode(data)?; Ok([Self::encode_length(data.len() as u64), encoded_data].concat()) } fn encode_enum(&mut self, selector: &EnumSelector) -> Result> { let (discriminant, token_within_enum, _) = selector; let encoded_discriminant = Self::encode_discriminant(*discriminant); let encoded_token = self.encode_token(token_within_enum)?; Ok([encoded_discriminant, encoded_token].concat()) } fn encode_length(len: u64) -> Vec { len.to_be_bytes().to_vec() } fn encode_discriminant(discriminant: u64) -> Vec { discriminant.to_be_bytes().to_vec() } } ================================================ FILE: packages/fuels-core/src/codec/abi_encoder.rs ================================================ mod bounded_encoder; use std::default::Default; use crate::{ codec::abi_encoder::bounded_encoder::BoundedEncoder, types::{Token, errors::Result}, }; #[derive(Debug, Clone, Copy)] pub struct EncoderConfig { /// Entering a struct, array, tuple, enum or vector increases the depth. Encoding will fail if /// the current depth becomes greater than `max_depth` configured here. pub max_depth: usize, /// Every encoded argument will increase the token count. Encoding will fail if the current /// token count becomes greater than `max_tokens` configured here. pub max_tokens: usize, } // ANCHOR: default_encoder_config impl Default for EncoderConfig { fn default() -> Self { Self { max_depth: 45, max_tokens: 10_000, } } } // ANCHOR_END: default_encoder_config #[derive(Default, Clone, Debug)] pub struct ABIEncoder { pub config: EncoderConfig, } impl ABIEncoder { pub fn new(config: EncoderConfig) -> Self { Self { config } } /// Encodes `Token`s following the ABI specs defined /// [here](https://github.com/FuelLabs/fuel-specs/blob/master/specs/protocol/abi.md) pub fn encode(&self, tokens: &[Token]) -> Result> { BoundedEncoder::new(self.config).encode(tokens) } } #[cfg(test)] mod tests { use std::slice; use super::*; use crate::{ to_named, types::{ StaticStringToken, U256, errors::Error, param_types::{EnumVariants, ParamType}, }, }; #[test] fn encode_multiple_uint() -> Result<()> { let tokens = [ Token::U8(u8::MAX), Token::U16(u16::MAX), Token::U32(u32::MAX), Token::U64(u64::MAX), Token::U128(u128::MAX), Token::U256(U256::MAX), ]; let result = ABIEncoder::default().encode(&tokens)?; let expected = [ 255, // u8 255, 255, // u16 255, 255, 255, 255, // u32 255, 255, 255, 255, 255, 255, 255, 255, // u64 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, // u128 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, // u256 ]; assert_eq!(result, expected); Ok(()) } #[test] fn encode_bool() -> Result<()> { let token = Token::Bool(true); let result = ABIEncoder::default().encode(&[token])?; let expected = [1]; assert_eq!(result, expected); Ok(()) } #[test] fn encode_b256() -> Result<()> { let data = [ 213, 87, 156, 70, 223, 204, 127, 24, 32, 112, 19, 230, 91, 68, 228, 203, 78, 44, 34, 152, 244, 172, 69, 123, 168, 248, 39, 67, 243, 30, 147, 11, ]; let token = Token::B256(data); let result = ABIEncoder::default().encode(&[token])?; assert_eq!(result, data); Ok(()) } #[test] fn encode_bytes() -> Result<()> { let token = Token::Bytes([255, 0, 1, 2, 3, 4, 5].to_vec()); let result = ABIEncoder::default().encode(&[token])?; let expected = [ 0, 0, 0, 0, 0, 0, 0, 7, // len 255, 0, 1, 2, 3, 4, 5, // data ]; assert_eq!(result, expected); Ok(()) } #[test] fn encode_string() -> Result<()> { let token = Token::String("This is a full sentence".to_string()); let result = ABIEncoder::default().encode(&[token])?; let expected = [ 0, 0, 0, 0, 0, 0, 0, 23, // len 84, 104, 105, 115, 32, 105, 115, 32, 97, 32, 102, 117, 108, 108, 32, 115, 101, 110, 116, 101, 110, 99, 101, //This is a full sentence ]; assert_eq!(result, expected); Ok(()) } #[test] fn encode_raw_slice() -> Result<()> { let token = Token::RawSlice([255, 0, 1, 2, 3, 4, 5].to_vec()); let result = ABIEncoder::default().encode(&[token])?; let expected = [ 0, 0, 0, 0, 0, 0, 0, 7, // len 255, 0, 1, 2, 3, 4, 5, // data ]; assert_eq!(result, expected); Ok(()) } #[test] fn encode_string_array() -> Result<()> { let token = Token::StringArray(StaticStringToken::new( "This is a full sentence".into(), Some(23), )); let result = ABIEncoder::default().encode(&[token])?; let expected = [ 84, 104, 105, 115, 32, 105, 115, 32, 97, 32, 102, 117, 108, 108, 32, 115, 101, 110, 116, 101, 110, 99, 101, //This is a full sentence ]; assert_eq!(result, expected); Ok(()) } #[test] fn encode_string_slice() -> Result<()> { let token = Token::StringSlice(StaticStringToken::new( "This is a full sentence".into(), None, )); let result = ABIEncoder::default().encode(&[token])?; let expected = [ 0, 0, 0, 0, 0, 0, 0, 23, // len 84, 104, 105, 115, 32, 105, 115, 32, 97, 32, 102, 117, 108, 108, 32, 115, 101, 110, 116, 101, 110, 99, 101, //This is a full sentence ]; assert_eq!(result, expected); Ok(()) } #[test] fn encode_tuple() -> Result<()> { let token = Token::Tuple(vec![Token::U32(255), Token::Bool(true)]); let result = ABIEncoder::default().encode(&[token])?; let expected = [ 0, 0, 0, 255, //u32 1, //bool ]; assert_eq!(result, expected); Ok(()) } #[test] fn encode_array() -> Result<()> { let token = Token::Tuple(vec![Token::U32(255), Token::U32(128)]); let result = ABIEncoder::default().encode(&[token])?; let expected = [ 0, 0, 0, 255, //u32 0, 0, 0, 128, //u32 ]; assert_eq!(result, expected); Ok(()) } #[test] fn encode_enum_with_deeply_nested_types() -> Result<()> { /* enum DeeperEnum { v1: bool, v2: str[10] } */ let types = to_named(&[ParamType::Bool, ParamType::StringArray(10)]); let deeper_enum_variants = EnumVariants::new(types)?; let deeper_enum_token = Token::StringArray(StaticStringToken::new("0123456789".into(), Some(10))); /* struct StructA { some_enum: DeeperEnum some_number: u32 } */ let fields = to_named(&[ ParamType::Enum { name: "".to_string(), enum_variants: deeper_enum_variants.clone(), generics: vec![], }, ParamType::Bool, ]); let struct_a_type = ParamType::Struct { name: "".to_string(), fields, generics: vec![], }; let struct_a_token = Token::Struct(vec![ Token::Enum(Box::new((1, deeper_enum_token, deeper_enum_variants))), Token::U32(11332), ]); /* enum TopLevelEnum { v1: StructA, v2: bool, v3: u64 } */ let types = to_named(&[struct_a_type, ParamType::Bool, ParamType::U64]); let top_level_enum_variants = EnumVariants::new(types)?; let top_level_enum_token = Token::Enum(Box::new((0, struct_a_token, top_level_enum_variants))); let result = ABIEncoder::default().encode(slice::from_ref(&top_level_enum_token))?; let expected = [ 0, 0, 0, 0, 0, 0, 0, 0, // TopLevelEnum::v1 discriminant 0, 0, 0, 0, 0, 0, 0, 1, // DeeperEnum::v2 discriminant 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, // str[10] 0, 0, 44, 68, // StructA.some_number ]; assert_eq!(result, expected); Ok(()) } #[test] fn encode_nested_structs() -> Result<()> { let token = Token::Struct(vec![ Token::U16(10), Token::Struct(vec![ Token::Bool(true), Token::Array(vec![Token::U8(1), Token::U8(2)]), ]), ]); let result = ABIEncoder::default().encode(&[token])?; let expected = [ 0, 10, // u16 1, // bool 1, 2, // [u8, u8] ]; assert_eq!(result, expected); Ok(()) } #[test] fn encode_comprehensive() -> Result<()> { let foo = Token::Struct(vec![ Token::U16(10), Token::Struct(vec![ Token::Bool(true), Token::Array(vec![Token::U8(1), Token::U8(2)]), ]), ]); let arr_u8 = Token::Array(vec![Token::U8(1), Token::U8(2)]); let b256 = Token::B256([255; 32]); let str_arr = Token::StringArray(StaticStringToken::new( "This is a full sentence".into(), Some(23), )); let tokens = vec![foo, arr_u8, b256, str_arr]; let result = ABIEncoder::default().encode(&tokens)?; let expected = [ 0, 10, // foo.x == 10u16 1, // foo.y.a == true 1, // foo.y.b.0 == 1u8 2, // foo.y.b.1 == 2u8 1, // u8[2].0 == 1u8 2, // u8[2].0 == 2u8 255, 255, 255, 255, 255, 255, 255, 255, // b256 255, 255, 255, 255, 255, 255, 255, 255, // b256 255, 255, 255, 255, 255, 255, 255, 255, // b256 255, 255, 255, 255, 255, 255, 255, 255, // b256 84, 104, 105, 115, 32, 105, 115, 32, 97, 32, 102, 117, 108, 108, 32, 115, 101, 110, 116, 101, 110, 99, 101, // str[23] ]; assert_eq!(result, expected); Ok(()) } #[test] fn enums_with_only_unit_variants_are_encoded_in_one_word() -> Result<()> { let expected = [0, 0, 0, 0, 0, 0, 0, 1]; let types = to_named(&[ParamType::Unit, ParamType::Unit]); let enum_selector = Box::new((1, Token::Unit, EnumVariants::new(types)?)); let actual = ABIEncoder::default().encode(&[Token::Enum(enum_selector)])?; assert_eq!(actual, expected); Ok(()) } #[test] fn vec_in_enum() -> Result<()> { // arrange let types = to_named(&[ParamType::B256, ParamType::Vector(Box::new(ParamType::U64))]); let variants = EnumVariants::new(types)?; let selector = (1, Token::Vector(vec![Token::U64(5)]), variants); let token = Token::Enum(Box::new(selector)); // act let result = ABIEncoder::default().encode(&[token])?; // assert let expected = [ 0, 0, 0, 0, 0, 0, 0, 1, // enum dicsriminant 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 5, // vec[len, u64] ]; assert_eq!(result, expected); Ok(()) } #[test] fn enum_in_vec() -> Result<()> { // arrange let types = to_named(&[ParamType::B256, ParamType::U8]); let variants = EnumVariants::new(types)?; let selector = (1, Token::U8(8), variants); let enum_token = Token::Enum(Box::new(selector)); let vec_token = Token::Vector(vec![enum_token]); // act let result = ABIEncoder::default().encode(&[vec_token])?; // assert let expected = [ 0, 0, 0, 0, 0, 0, 0, 1, // vec len 0, 0, 0, 0, 0, 0, 0, 1, 8, // enum discriminant and u8 value ]; assert_eq!(result, expected); Ok(()) } #[test] fn vec_in_struct() -> Result<()> { // arrange let token = Token::Struct(vec![Token::Vector(vec![Token::U64(5)]), Token::U8(9)]); // act let result = ABIEncoder::default().encode(&[token])?; // assert let expected = [ 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 5, // vec[len, u64] 9, // u8 ]; assert_eq!(result, expected); Ok(()) } #[test] fn vec_in_vec() -> Result<()> { // arrange let token = Token::Vector(vec![Token::Vector(vec![Token::U8(5), Token::U8(6)])]); // act let result = ABIEncoder::default().encode(&[token])?; // assert let expected = [ 0, 0, 0, 0, 0, 0, 0, 1, // vec1 len 0, 0, 0, 0, 0, 0, 0, 2, 5, 6, // vec2 [len, u8, u8] ]; assert_eq!(result, expected); Ok(()) } #[test] fn max_depth_surpassed() { const MAX_DEPTH: usize = 2; let config = EncoderConfig { max_depth: MAX_DEPTH, ..Default::default() }; let msg = "depth limit `2` reached while encoding. Try increasing it".to_string(); [nested_struct, nested_enum, nested_tuple, nested_array] .iter() .map(|fun| fun(MAX_DEPTH + 1)) .for_each(|token| { assert_encoding_failed(config, token, &msg); }); } fn assert_encoding_failed(config: EncoderConfig, token: Token, msg: &str) { let encoder = ABIEncoder::new(config); let err = encoder.encode(&[token]); let Err(Error::Codec(actual_msg)) = err else { panic!("expected a Codec error. Got: `{err:?}`"); }; assert_eq!(actual_msg, msg); } fn nested_struct(depth: usize) -> Token { let fields = if depth == 1 { vec![Token::U8(255), Token::String("bloopblip".to_string())] } else { vec![nested_struct(depth - 1)] }; Token::Struct(fields) } fn nested_enum(depth: usize) -> Token { if depth == 0 { return Token::U8(255); } let inner_enum = nested_enum(depth - 1); // Create a basic EnumSelector for the current level (the `EnumVariants` is not // actually accurate but it's not used for encoding) let selector = ( 0u64, inner_enum, EnumVariants::new(to_named(&[ParamType::U64])).unwrap(), ); Token::Enum(Box::new(selector)) } fn nested_array(depth: usize) -> Token { if depth == 1 { Token::Array(vec![Token::U8(255)]) } else { Token::Array(vec![nested_array(depth - 1)]) } } fn nested_tuple(depth: usize) -> Token { let fields = if depth == 1 { vec![Token::U8(255), Token::String("bloopblip".to_string())] } else { vec![nested_tuple(depth - 1)] }; Token::Tuple(fields) } } ================================================ FILE: packages/fuels-core/src/codec/abi_formatter.rs ================================================ use std::{collections::HashMap, io::Read}; use fuel_abi_types::abi::unified_program::UnifiedProgramABI; use itertools::Itertools; use super::{ABIDecoder, DecoderConfig}; use crate::{Result, error, types::param_types::ParamType}; pub struct ABIFormatter { functions: HashMap>, configurables: Vec<(String, ParamType)>, decoder: ABIDecoder, } impl ABIFormatter { pub fn has_fn(&self, fn_name: &str) -> bool { self.functions.contains_key(fn_name) } pub fn with_decoder_config(mut self, config: DecoderConfig) -> Self { self.decoder = ABIDecoder::new(config); self } pub fn from_abi(abi: UnifiedProgramABI) -> Result { let functions = abi .functions .iter() .map(|fun| (fun.name.clone(), fun.clone())) .collect::>(); let type_lookup = abi .types .iter() .map(|decl| (decl.type_id, decl.clone())) .collect::>(); let functions = functions .into_iter() .map(|(name, fun)| { let args = fun .inputs .iter() .map(|type_application| { ParamType::try_from_type_application(type_application, &type_lookup) }) .collect::>>()?; Ok((name.clone(), args)) }) .collect::>>()?; let configurables = abi .configurables .into_iter() .flatten() .sorted_by_key(|c| c.offset) .map(|c| { let param_type = ParamType::try_from_type_application(&c.application, &type_lookup)?; Ok((c.name, param_type)) }) .collect::>>()?; Ok(Self { functions, decoder: ABIDecoder::default(), configurables, }) } pub fn from_json_abi(abi: impl AsRef) -> Result { let parsed_abi = UnifiedProgramABI::from_json_abi(abi.as_ref())?; Self::from_abi(parsed_abi) } pub fn decode_fn_args(&self, fn_name: &str, data: R) -> Result> { let args = self .functions .get(fn_name) .ok_or_else(|| error!(Codec, "Function '{}' not found in the ABI", fn_name))?; self.decoder.decode_multiple_as_debug_str(args, data) } pub fn decode_configurables( &self, configurable_data: R, ) -> Result> { let param_types = self .configurables .iter() .map(|(_, param_type)| param_type) .cloned() .collect::>(); let decoded = self .decoder .decode_multiple_as_debug_str(¶m_types, configurable_data)? .into_iter() .zip(&self.configurables) .map(|(value, (name, _))| (name.clone(), value)) .collect(); Ok(decoded) } } #[cfg(test)] mod tests { use super::*; use crate::types::errors::Error; #[test] fn gracefully_handles_missing_fn() { // given let decoder = ABIFormatter::from_abi(UnifiedProgramABI::default()).unwrap(); // when let err = decoder .decode_fn_args("non_existent_fn", [].as_slice()) .unwrap_err(); // then let Error::Codec(err) = err else { panic!("Expected Codec error, got {:?}", err); }; assert_eq!(err, "Function 'non_existent_fn' not found in the ABI"); } } ================================================ FILE: packages/fuels-core/src/codec/function_selector.rs ================================================ pub use fuels_code_gen::utils::encode_fn_selector; /// This uses the default `EncoderConfig` configuration. #[macro_export] macro_rules! calldata { ( $($arg: expr),* ) => { ::fuels::core::codec::ABIEncoder::default().encode(&[$(::fuels::core::traits::Tokenizable::into_token($arg)),*]) } } pub use calldata; ================================================ FILE: packages/fuels-core/src/codec/logs.rs ================================================ use std::{ any::TypeId, collections::{HashMap, HashSet}, fmt::{Debug, Formatter}, iter::FilterMap, }; /// Trait that represents a log with a unique identifier. pub trait Log { /// Returns the unique identifier of the log as a string. const LOG_ID: &'static str; /// Returns the unique identifier of the log as a `u64`. const LOG_ID_U64: u64; } #[derive(Debug, Clone)] pub struct ErrorDetails { pub(crate) pkg: String, pub(crate) file: String, pub(crate) line: u64, pub(crate) column: u64, pub(crate) log_id: Option, pub(crate) msg: Option, } impl ErrorDetails { pub fn new( pkg: String, file: String, line: u64, column: u64, log_id: Option, msg: Option, ) -> Self { Self { pkg, file, line, column, log_id, msg, } } } use fuel_tx::{ContractId, Receipt}; use crate::{ codec::{ABIDecoder, DecoderConfig}, traits::{Parameterize, Tokenizable}, types::errors::{Error, Result, error}, }; #[derive(Clone)] pub struct LogFormatter { formatter: fn(DecoderConfig, &[u8]) -> Result, type_id: TypeId, } impl LogFormatter { pub fn new_log() -> Self { Self { formatter: Self::format_log::, type_id: TypeId::of::(), } } pub fn new_error() -> Self { Self { formatter: Self::format_error::, type_id: TypeId::of::(), } } fn format_log( decoder_config: DecoderConfig, bytes: &[u8], ) -> Result { let token = ABIDecoder::new(decoder_config).decode(&T::param_type(), bytes)?; Ok(format!("{:?}", T::from_token(token)?)) } fn format_error( decoder_config: DecoderConfig, bytes: &[u8], ) -> Result { let token = ABIDecoder::new(decoder_config).decode(&T::param_type(), bytes)?; Ok(T::from_token(token)?.to_string()) } pub fn can_handle_type(&self) -> bool { TypeId::of::() == self.type_id } pub fn format(&self, decoder_config: DecoderConfig, bytes: &[u8]) -> Result { (self.formatter)(decoder_config, bytes) } } impl Debug for LogFormatter { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { f.debug_struct("LogFormatter") .field("type_id", &self.type_id) .finish() } } /// Holds a unique log ID #[derive(Debug, Clone, Default, PartialEq, Eq, Hash)] pub struct LogId(ContractId, String); /// Struct used to pass the log mappings from the Abigen #[derive(Debug, Clone, Default)] pub struct LogDecoder { /// A mapping of LogId and param-type log_formatters: HashMap, error_codes: HashMap, decoder_config: DecoderConfig, } #[derive(Debug)] pub struct LogResult { pub results: Vec>, } impl LogResult { pub fn filter_succeeded(&self) -> Vec<&str> { self.results .iter() .filter_map(|result| result.as_deref().ok()) .collect() } pub fn filter_failed(&self) -> Vec<&Error> { self.results .iter() .filter_map(|result| result.as_ref().err()) .collect() } } impl LogDecoder { pub fn new( log_formatters: HashMap, error_codes: HashMap, ) -> Self { Self { log_formatters, error_codes, decoder_config: Default::default(), } } pub fn get_error_codes(&self, id: &u64) -> Option<&ErrorDetails> { self.error_codes.get(id) } pub fn set_decoder_config(&mut self, decoder_config: DecoderConfig) -> &mut Self { self.decoder_config = decoder_config; self } /// Get all logs results from the given receipts as `Result` pub fn decode_logs(&self, receipts: &[Receipt]) -> LogResult { let results = receipts .iter() .extract_log_id_and_data() .map(|(log_id, data)| self.format_log(&log_id, &data)) .collect(); LogResult { results } } fn format_log(&self, log_id: &LogId, data: &[u8]) -> Result { self.log_formatters .get(log_id) .ok_or_else(|| { error!( Codec, "missing log formatter for log_id: `{:?}`, data: `{:?}`. \ Consider adding external contracts using `with_contracts()`", log_id, data ) }) .and_then(|log_formatter| log_formatter.format(self.decoder_config, data)) } pub(crate) fn decode_last_log(&self, receipts: &[Receipt]) -> Result { receipts .iter() .rev() .extract_log_id_and_data() .next() .ok_or_else(|| error!(Codec, "no receipts found for decoding last log")) .and_then(|(log_id, data)| self.format_log(&log_id, &data)) } pub(crate) fn decode_last_two_logs(&self, receipts: &[Receipt]) -> Result<(String, String)> { let res = receipts .iter() .rev() .extract_log_id_and_data() .map(|(log_id, data)| self.format_log(&log_id, &data)) .take(2) .collect::>>(); match res.as_deref() { Ok([rhs, lhs]) => Ok((lhs.to_string(), rhs.to_string())), Ok(some_slice) => Err(error!( Codec, "expected to have two logs. Found {}", some_slice.len() )), Err(_) => Err(res.expect_err("must be an error")), } } /// Get decoded logs with specific type from the given receipts. /// Note that this method returns the actual type and not a `String` representation. pub fn decode_logs_with_type( &self, receipts: &[Receipt], ) -> Result> { let target_ids: HashSet = self .log_formatters .iter() .filter(|(_, log_formatter)| log_formatter.can_handle_type::()) .map(|(log_id, _)| log_id.clone()) .collect(); receipts .iter() .extract_log_id_and_data() .filter_map(|(log_id, bytes)| { target_ids.contains(&log_id).then(|| { let token = ABIDecoder::new(self.decoder_config) .decode(&T::param_type(), bytes.as_slice())?; T::from_token(token) }) }) .collect() } /// Get LogIds and lazy decoders for specific type from a single receipt. pub fn decode_logs_lazy<'a, T: Tokenizable + Parameterize + 'static>( &'a self, receipt: &'a Receipt, ) -> impl Iterator Result> + 'a { let target_ids: HashSet<&LogId> = self .log_formatters .iter() .filter(|(_, log_formatter)| log_formatter.can_handle_type::()) .map(|(log_id, _)| log_id) .collect(); std::iter::once(receipt).extract_matching_logs_lazy::(target_ids, self.decoder_config) } pub fn merge(&mut self, log_decoder: LogDecoder) { self.log_formatters.extend(log_decoder.log_formatters); self.error_codes.extend(log_decoder.error_codes); } } trait ExtractLogIdData { type Output: Iterator)>; fn extract_log_id_and_data(self) -> Self::Output; } trait ExtractLogIdLazy { fn extract_matching_logs_lazy( self, target_ids: HashSet<&LogId>, decoder_config: DecoderConfig, ) -> impl Iterator Result>; } impl<'a, I: Iterator> ExtractLogIdData for I { type Output = FilterMap Option<(LogId, Vec)>>; fn extract_log_id_and_data(self) -> Self::Output { self.filter_map(|r| match r { Receipt::LogData { rb, data: Some(data), id, .. } => Some((LogId(*id, (*rb).to_string()), data.to_vec())), Receipt::Log { ra, rb, id, .. } => { Some((LogId(*id, (*rb).to_string()), ra.to_be_bytes().to_vec())) } _ => None, }) } } impl<'a, I: Iterator> ExtractLogIdLazy for I { fn extract_matching_logs_lazy( self, target_ids: HashSet<&LogId>, decoder_config: DecoderConfig, ) -> impl Iterator Result> { self.filter_map(move |r| { let log_id = match r { Receipt::LogData { rb, id, .. } => LogId(*id, (*rb).to_string()), Receipt::Log { rb, id, .. } => LogId(*id, (*rb).to_string()), _ => return None, }; if !target_ids.contains(&log_id) { return None; } enum Data<'a> { LogData(&'a [u8]), LogRa(u64), } let data = match r { Receipt::LogData { data: Some(data), .. } => Some(Data::LogData(data.as_slice())), Receipt::Log { ra, .. } => Some(Data::LogRa(*ra)), _ => None, }; data.map(move |data| { move || { let normalized_data = match data { Data::LogData(data) => data, Data::LogRa(ra) => &ra.to_be_bytes(), }; let token = ABIDecoder::new(decoder_config) .decode(&T::param_type(), normalized_data)?; T::from_token(token) } }) }) } } pub fn log_formatters_lookup( log_id_log_formatter_pairs: Vec<(String, LogFormatter)>, contract_id: ContractId, ) -> HashMap { log_id_log_formatter_pairs .into_iter() .map(|(id, log_formatter)| (LogId(contract_id, id), log_formatter)) .collect() } ================================================ FILE: packages/fuels-core/src/codec/utils.rs ================================================ use crate::types::errors::{Result, error}; pub(crate) struct CounterWithLimit { count: usize, max: usize, name: String, direction: CodecDirection, } #[derive(Debug)] pub(crate) enum CodecDirection { Encoding, Decoding, } impl std::fmt::Display for CodecDirection { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { CodecDirection::Encoding => write!(f, "encoding"), CodecDirection::Decoding => write!(f, "decoding"), } } } impl CounterWithLimit { pub(crate) fn new(max: usize, name: impl Into, direction: CodecDirection) -> Self { Self { count: 0, max, direction, name: name.into(), } } pub(crate) fn increase(&mut self) -> Result<()> { self.count += 1; if self.count > self.max { return Err(error!( Codec, "{} limit `{}` reached while {}. Try increasing it", self.name, self.max, self.direction )); } Ok(()) } pub(crate) fn decrease(&mut self) { if self.count > 0 { self.count -= 1; } } } ================================================ FILE: packages/fuels-core/src/codec.rs ================================================ mod abi_decoder; mod abi_encoder; mod abi_formatter; mod function_selector; mod logs; mod utils; use std::io::Read; pub use abi_decoder::*; pub use abi_encoder::*; pub use abi_formatter::*; pub use function_selector::*; pub use logs::*; use crate::{ traits::{Parameterize, Tokenizable}, types::errors::Result, }; /// Decodes `bytes` into type `T` following the schema defined by T's `Parameterize` impl pub fn try_from_bytes(bytes: impl Read, decoder_config: DecoderConfig) -> Result where T: Parameterize + Tokenizable, { let token = ABIDecoder::new(decoder_config).decode(&T::param_type(), bytes)?; T::from_token(token) } #[cfg(test)] mod tests { use super::*; use crate::{ constants::WORD_SIZE, types::{Address, AsciiString, AssetId, ContractId}, }; #[test] fn convert_all_from_bool_to_u64() -> Result<()> { let bytes = [255; WORD_SIZE]; macro_rules! test_decode { ($($for_type: ident),*) => { $(assert_eq!( try_from_bytes::<$for_type>(bytes.as_slice(), DecoderConfig::default())?, $for_type::MAX );)* }; } assert!(try_from_bytes::( bytes.as_slice(), DecoderConfig::default() )?); test_decode!(u8, u16, u32, u64); Ok(()) } #[test] fn convert_bytes_into_tuple() -> Result<()> { let tuple_in_bytes = [0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 2]; let the_tuple: (u64, u32) = try_from_bytes(tuple_in_bytes.as_slice(), DecoderConfig::default())?; assert_eq!(the_tuple, (1, 2)); Ok(()) } #[test] fn convert_native_types() -> Result<()> { let bytes = [255; 32]; macro_rules! test_decode { ($($for_type: ident),*) => { $(assert_eq!( try_from_bytes::<$for_type>(bytes.as_slice(), DecoderConfig::default())?, $for_type::new(bytes.as_slice().try_into()?) );)* }; } test_decode!(Address, ContractId, AssetId); Ok(()) } #[test] fn string_slice_is_read_in_total() { // This was a bug where the decoder read more bytes than it reported, causing the next // element to be read incorrectly. // given #[derive( fuels_macros::Tokenizable, fuels_macros::Parameterize, Clone, PartialEq, Debug, )] #[FuelsCorePath = "crate"] #[FuelsTypesPath = "crate::types"] struct Test { name: AsciiString, age: u64, } let input = Test { name: AsciiString::new("Alice".to_owned()).unwrap(), age: 42, }; let encoded = ABIEncoder::default() .encode(&[input.clone().into_token()]) .unwrap(); // when let decoded = try_from_bytes::(encoded.as_slice(), DecoderConfig::default()).unwrap(); // then assert_eq!(decoded, input); } } ================================================ FILE: packages/fuels-core/src/lib.rs ================================================ pub mod codec; pub mod traits; pub mod types; mod utils; pub use utils::*; use crate::types::errors::Result; #[derive(Debug, Clone, Default, PartialEq)] pub struct Configurable { /// The offset (in bytes) within the binary where the data is located. pub offset: u64, /// The data related to the configurable. pub data: Vec, } #[derive(Debug, Clone, Default, PartialEq)] pub struct Configurables { pub offsets_with_data: Vec, } impl Configurables { pub fn new(offsets_with_data: Vec) -> Self { Self { offsets_with_data } } pub fn with_shifted_offsets(self, shift: i64) -> Result { let new_offsets_with_data = self .offsets_with_data .into_iter() .map(|c| { let new_offset = if shift.is_negative() { c.offset.checked_sub(shift.unsigned_abs()) } else { c.offset.checked_add(shift.unsigned_abs()) }; let new_offset = new_offset.ok_or_else(|| { crate::error!( Other, "Overflow occurred while shifting offset: {} + {shift}", c.offset ) })?; Ok(Configurable { offset: new_offset, data: c.data, }) }) .collect::>>()?; Ok(Self { offsets_with_data: new_offsets_with_data, }) } pub fn update_constants_in(&self, binary: &mut [u8]) { for c in &self.offsets_with_data { let offset = c.offset as usize; binary[offset..offset + c.data.len()].copy_from_slice(&c.data) } } } impl From for Vec { fn from(config: Configurables) -> Vec { config.offsets_with_data.clone() } } #[cfg(test)] mod tests { use super::*; #[test] fn test_with_shifted_offsets_positive_shift() { let offsets_with_data = vec![Configurable { offset: 10u64, data: vec![1, 2, 3], }]; let configurables = Configurables::new(offsets_with_data.clone()); let shifted_configurables = configurables.with_shifted_offsets(5).unwrap(); let expected_offsets_with_data = vec![Configurable { offset: 15u64, data: vec![1, 2, 3], }]; assert_eq!( shifted_configurables.offsets_with_data, expected_offsets_with_data ); } #[test] fn test_with_shifted_offsets_negative_shift() { let offsets_with_data = vec![Configurable { offset: 10u64, data: vec![4, 5, 6], }]; let configurables = Configurables::new(offsets_with_data.clone()); let shifted_configurables = configurables.with_shifted_offsets(-5).unwrap(); let expected_offsets_with_data = vec![Configurable { offset: 5u64, data: vec![4, 5, 6], }]; assert_eq!( shifted_configurables.offsets_with_data, expected_offsets_with_data ); } #[test] fn test_with_shifted_offsets_zero_shift() { let offsets_with_data = vec![Configurable { offset: 20u64, data: vec![7, 8, 9], }]; let configurables = Configurables::new(offsets_with_data.clone()); let shifted_configurables = configurables.with_shifted_offsets(0).unwrap(); let expected_offsets_with_data = offsets_with_data; assert_eq!( shifted_configurables.offsets_with_data, expected_offsets_with_data ); } #[test] fn test_with_shifted_offsets_overflow() { let offsets_with_data = vec![Configurable { offset: u64::MAX - 1, data: vec![1, 2, 3], }]; let configurables = Configurables::new(offsets_with_data); let result = configurables.with_shifted_offsets(10); assert!(result.is_err()); if let Err(e) = result { assert!( e.to_string() .contains("Overflow occurred while shifting offset") ); } } #[test] fn test_with_shifted_offsets_underflow() { let offsets_with_data = vec![Configurable { offset: 5u64, data: vec![4, 5, 6], }]; let configurables = Configurables::new(offsets_with_data); let result = configurables.with_shifted_offsets(-10); assert!(result.is_err()); if let Err(e) = result { assert!( e.to_string() .contains("Overflow occurred while shifting offset") ); } } } ================================================ FILE: packages/fuels-core/src/traits/parameterize.rs ================================================ use fuel_types::{Address, AssetId, ContractId}; use crate::types::{ AsciiString, Bits256, Bytes, RawSlice, SizedAsciiString, param_types::{EnumVariants, ParamType}, }; /// `abigen` requires `Parameterized` to construct nested types. It is also used by `try_from_bytes` /// to facilitate the instantiation of custom types from bytes. pub trait Parameterize { fn param_type() -> ParamType; } impl Parameterize for Bits256 { fn param_type() -> ParamType { ParamType::B256 } } impl Parameterize for RawSlice { fn param_type() -> ParamType { ParamType::RawSlice } } impl Parameterize for [T; SIZE] { fn param_type() -> ParamType { ParamType::Array(Box::new(T::param_type()), SIZE) } } impl Parameterize for Vec { fn param_type() -> ParamType { ParamType::Vector(Box::new(T::param_type())) } } impl Parameterize for Bytes { fn param_type() -> ParamType { ParamType::Bytes } } impl Parameterize for String { fn param_type() -> ParamType { ParamType::String } } impl Parameterize for Address { fn param_type() -> ParamType { ParamType::Struct { name: "Address".to_string(), fields: vec![("0".to_string(), ParamType::B256)], generics: vec![], } } } impl Parameterize for ContractId { fn param_type() -> ParamType { ParamType::Struct { name: "ContractId".to_string(), fields: vec![("0".to_string(), ParamType::B256)], generics: vec![], } } } impl Parameterize for AssetId { fn param_type() -> ParamType { ParamType::Struct { name: "AssetId".to_string(), fields: vec![("0".to_string(), ParamType::B256)], generics: vec![], } } } impl Parameterize for () { fn param_type() -> ParamType { ParamType::Unit } } impl Parameterize for bool { fn param_type() -> ParamType { ParamType::Bool } } impl Parameterize for u8 { fn param_type() -> ParamType { ParamType::U8 } } impl Parameterize for u16 { fn param_type() -> ParamType { ParamType::U16 } } impl Parameterize for u32 { fn param_type() -> ParamType { ParamType::U32 } } impl Parameterize for u64 { fn param_type() -> ParamType { ParamType::U64 } } impl Parameterize for u128 { fn param_type() -> ParamType { ParamType::U128 } } impl Parameterize for Option where T: Parameterize, { fn param_type() -> ParamType { let variant_param_types = vec![ ("None".to_string(), ParamType::Unit), ("Some".to_string(), T::param_type()), ]; let enum_variants = EnumVariants::new(variant_param_types) .expect("should never happen as we provided valid Option param types"); ParamType::Enum { name: "Option".to_string(), enum_variants, generics: vec![T::param_type()], } } } impl Parameterize for Result where T: Parameterize, E: Parameterize, { fn param_type() -> ParamType { let variant_param_types = vec![ ("Ok".to_string(), T::param_type()), ("Err".to_string(), E::param_type()), ]; let enum_variants = EnumVariants::new(variant_param_types) .expect("should never happen as we provided valid Result param types"); ParamType::Enum { name: "Result".to_string(), enum_variants, generics: vec![T::param_type(), E::param_type()], } } } impl Parameterize for SizedAsciiString { fn param_type() -> ParamType { ParamType::StringArray(LEN) } } impl Parameterize for AsciiString { fn param_type() -> ParamType { ParamType::StringSlice } } // Here we implement `Parameterize` for a given tuple of a given length. // This is done this way because we can't use `impl Parameterize for (T,)`. // So we implement `Parameterize` for each tuple length, covering // a reasonable range of tuple lengths. macro_rules! impl_parameterize_tuples { ($num: expr, $( $ty: ident : $no: tt, )+) => { impl<$($ty, )+> Parameterize for ($($ty,)+) where $( $ty: Parameterize, )+ { fn param_type() -> ParamType { ParamType::Tuple(vec![ $( $ty::param_type(), )+ ]) } } } } // And where we actually implement the `Parameterize` for tuples // from size 1 to size 16. impl_parameterize_tuples!(1, A:0, ); impl_parameterize_tuples!(2, A:0, B:1, ); impl_parameterize_tuples!(3, A:0, B:1, C:2, ); impl_parameterize_tuples!(4, A:0, B:1, C:2, D:3, ); impl_parameterize_tuples!(5, A:0, B:1, C:2, D:3, E:4, ); impl_parameterize_tuples!(6, A:0, B:1, C:2, D:3, E:4, F:5, ); impl_parameterize_tuples!(7, A:0, B:1, C:2, D:3, E:4, F:5, G:6, ); impl_parameterize_tuples!(8, A:0, B:1, C:2, D:3, E:4, F:5, G:6, H:7, ); impl_parameterize_tuples!(9, A:0, B:1, C:2, D:3, E:4, F:5, G:6, H:7, I:8, ); impl_parameterize_tuples!(10, A:0, B:1, C:2, D:3, E:4, F:5, G:6, H:7, I:8, J:9, ); impl_parameterize_tuples!(11, A:0, B:1, C:2, D:3, E:4, F:5, G:6, H:7, I:8, J:9, K:10, ); impl_parameterize_tuples!(12, A:0, B:1, C:2, D:3, E:4, F:5, G:6, H:7, I:8, J:9, K:10, L:11, ); impl_parameterize_tuples!(13, A:0, B:1, C:2, D:3, E:4, F:5, G:6, H:7, I:8, J:9, K:10, L:11, M:12, ); impl_parameterize_tuples!(14, A:0, B:1, C:2, D:3, E:4, F:5, G:6, H:7, I:8, J:9, K:10, L:11, M:12, N:13, ); impl_parameterize_tuples!(15, A:0, B:1, C:2, D:3, E:4, F:5, G:6, H:7, I:8, J:9, K:10, L:11, M:12, N:13, O:14, ); impl_parameterize_tuples!(16, A:0, B:1, C:2, D:3, E:4, F:5, G:6, H:7, I:8, J:9, K:10, L:11, M:12, N:13, O:14, P:15, ); #[cfg(test)] mod tests { use super::*; #[test] fn sized_ascii_string_is_parameterized_correctly() { let param_type = SizedAsciiString::<3>::param_type(); assert!(matches!(param_type, ParamType::StringArray(3))); } #[test] fn test_param_type_b256() { assert_eq!(Bits256::param_type(), ParamType::B256); } #[test] fn test_param_type_raw_slice() { assert_eq!(RawSlice::param_type(), ParamType::RawSlice); } } ================================================ FILE: packages/fuels-core/src/traits/signer.rs ================================================ use async_trait::async_trait; use auto_impl::auto_impl; use fuel_crypto::{Message, Signature}; use crate::types::{Address, errors::Result}; /// Trait for signing transactions and messages /// /// Implement this trait to support different signing modes, e.g. hardware wallet, hosted etc. #[cfg_attr(target_arch = "wasm32", async_trait(?Send))] #[cfg_attr(not(target_arch = "wasm32"), async_trait)] #[auto_impl(&, Box, Rc, Arc)] pub trait Signer { async fn sign(&self, message: Message) -> Result; fn address(&self) -> Address; } ================================================ FILE: packages/fuels-core/src/traits/tokenizable.rs ================================================ use fuel_types::{Address, AssetId, ContractId}; use crate::{ traits::Parameterize, types::{ AsciiString, Bits256, Bytes, RawSlice, SizedAsciiString, StaticStringToken, Token, errors::{Result, error}, param_types::ParamType, }, }; pub trait Tokenizable { /// Converts a `Token` into expected type. fn from_token(token: Token) -> Result where Self: Sized; /// Converts a specified type back into token. fn into_token(self) -> Token; } impl Tokenizable for Token { fn from_token(token: Token) -> Result { Ok(token) } fn into_token(self) -> Token { self } } impl Tokenizable for Bits256 { fn from_token(token: Token) -> Result where Self: Sized, { match token { Token::B256(data) => Ok(Bits256(data)), _ => Err(error!( Other, "`Bits256` cannot be constructed from token {token}" )), } } fn into_token(self) -> Token { Token::B256(self.0) } } impl Tokenizable for Vec { fn from_token(token: Token) -> Result where Self: Sized, { if let Token::Vector(tokens) = token { tokens.into_iter().map(Tokenizable::from_token).collect() } else { Err(error!( Other, "`Vec::from_token` must only be given a `Token::Vector`. Got: `{token}`" )) } } fn into_token(self) -> Token { let tokens = self.into_iter().map(Tokenizable::into_token).collect(); Token::Vector(tokens) } } impl Tokenizable for bool { fn from_token(token: Token) -> Result { match token { Token::Bool(data) => Ok(data), other => Err(error!(Other, "expected `bool`, got `{:?}`", other)), } } fn into_token(self) -> Token { Token::Bool(self) } } impl Tokenizable for () { fn from_token(token: Token) -> Result where Self: Sized, { match token { Token::Unit => Ok(()), other => Err(error!(Other, "expected `Unit`, got `{:?}`", other)), } } fn into_token(self) -> Token { Token::Unit } } impl Tokenizable for u8 { fn from_token(token: Token) -> Result { match token { Token::U8(data) => Ok(data), other => Err(error!(Other, "expected `u8`, got `{:?}`", other)), } } fn into_token(self) -> Token { Token::U8(self) } } impl Tokenizable for u16 { fn from_token(token: Token) -> Result { match token { Token::U16(data) => Ok(data), other => Err(error!(Other, "expected `u16`, got `{:?}`", other)), } } fn into_token(self) -> Token { Token::U16(self) } } impl Tokenizable for u32 { fn from_token(token: Token) -> Result { match token { Token::U32(data) => Ok(data), other => Err(error!(Other, "expected `u32`, got {:?}", other)), } } fn into_token(self) -> Token { Token::U32(self) } } impl Tokenizable for u64 { fn from_token(token: Token) -> Result { match token { Token::U64(data) => Ok(data), other => Err(error!(Other, "expected `u64`, got {:?}", other)), } } fn into_token(self) -> Token { Token::U64(self) } } impl Tokenizable for u128 { fn from_token(token: Token) -> Result { match token { Token::U128(data) => Ok(data), other => Err(error!(Other, "expected `u128`, got {:?}", other)), } } fn into_token(self) -> Token { Token::U128(self) } } impl Tokenizable for RawSlice { fn from_token(token: Token) -> Result where Self: Sized, { match token { Token::RawSlice(contents) => Ok(Self(contents)), _ => Err(error!( Other, "`RawSlice::from_token` expected a token of the variant `Token::RawSlice`, got: `{token}`" )), } } fn into_token(self) -> Token { Token::RawSlice(Vec::from(self)) } } impl Tokenizable for Bytes { fn from_token(token: Token) -> Result where Self: Sized, { match token { Token::Bytes(contents) => Ok(Self(contents)), _ => Err(error!( Other, "`Bytes::from_token` expected a token of the variant `Token::Bytes`, got: `{token}`" )), } } fn into_token(self) -> Token { Token::Bytes(Vec::from(self)) } } impl Tokenizable for String { fn from_token(token: Token) -> Result where Self: Sized, { match token { Token::String(string) => Ok(string), _ => Err(error!( Other, "`String::from_token` expected a token of the variant `Token::String`, got: `{token}`" )), } } fn into_token(self) -> Token { Token::String(self) } } // Here we implement `Tokenizable` for a given tuple of a given length. // This is done this way because we can't use `impl Tokenizable for (T,)`. // So we implement `Tokenizable` for each tuple length, covering // a reasonable range of tuple lengths. macro_rules! impl_tokenizable_tuples { ($num: expr, $( $ty: ident : $no: tt, )+) => { impl<$($ty, )+> Tokenizable for ($($ty,)+) where $( $ty: Tokenizable, )+ { fn from_token(token: Token) -> Result { match token { Token::Tuple(tokens) => { let mut it = tokens.into_iter(); let mut next_token = move || { it.next().ok_or_else(|| { error!(Other, "ran out of tokens before tuple could be constructed") }) }; Ok(($( $ty::from_token(next_token()?)?, )+)) }, other => Err(error!(Other, "expected `Tuple`, got `{:?}`", other )), } } fn into_token(self) -> Token { Token::Tuple(vec![ $( self.$no.into_token(), )+ ]) } } } } // And where we actually implement the `Tokenizable` for tuples // from size 1 to size 16. impl_tokenizable_tuples!(1, A:0, ); impl_tokenizable_tuples!(2, A:0, B:1, ); impl_tokenizable_tuples!(3, A:0, B:1, C:2, ); impl_tokenizable_tuples!(4, A:0, B:1, C:2, D:3, ); impl_tokenizable_tuples!(5, A:0, B:1, C:2, D:3, E:4, ); impl_tokenizable_tuples!(6, A:0, B:1, C:2, D:3, E:4, F:5, ); impl_tokenizable_tuples!(7, A:0, B:1, C:2, D:3, E:4, F:5, G:6, ); impl_tokenizable_tuples!(8, A:0, B:1, C:2, D:3, E:4, F:5, G:6, H:7, ); impl_tokenizable_tuples!(9, A:0, B:1, C:2, D:3, E:4, F:5, G:6, H:7, I:8, ); impl_tokenizable_tuples!(10, A:0, B:1, C:2, D:3, E:4, F:5, G:6, H:7, I:8, J:9, ); impl_tokenizable_tuples!(11, A:0, B:1, C:2, D:3, E:4, F:5, G:6, H:7, I:8, J:9, K:10, ); impl_tokenizable_tuples!(12, A:0, B:1, C:2, D:3, E:4, F:5, G:6, H:7, I:8, J:9, K:10, L:11, ); impl_tokenizable_tuples!(13, A:0, B:1, C:2, D:3, E:4, F:5, G:6, H:7, I:8, J:9, K:10, L:11, M:12, ); impl_tokenizable_tuples!(14, A:0, B:1, C:2, D:3, E:4, F:5, G:6, H:7, I:8, J:9, K:10, L:11, M:12, N:13, ); impl_tokenizable_tuples!(15, A:0, B:1, C:2, D:3, E:4, F:5, G:6, H:7, I:8, J:9, K:10, L:11, M:12, N:13, O:14, ); impl_tokenizable_tuples!(16, A:0, B:1, C:2, D:3, E:4, F:5, G:6, H:7, I:8, J:9, K:10, L:11, M:12, N:13, O:14, P:15, ); impl Tokenizable for ContractId { fn from_token(token: Token) -> Result where Self: Sized, { if let Token::Struct(tokens) = token { if let [Token::B256(data)] = tokens.as_slice() { Ok(ContractId::from(*data)) } else { Err(error!( Other, "`ContractId` expected one `Token::B256`, got `{tokens:?}`" )) } } else { Err(error!( Other, "`ContractId` expected `Token::Struct` got `{token:?}`" )) } } fn into_token(self) -> Token { let underlying_data: &[u8; 32] = &self; Token::Struct(vec![Bits256(*underlying_data).into_token()]) } } impl Tokenizable for Address { fn from_token(token: Token) -> Result where Self: Sized, { if let Token::Struct(tokens) = token { if let [Token::B256(data)] = tokens.as_slice() { Ok(Address::from(*data)) } else { Err(error!( Other, "`Address` expected one `Token::B256`, got `{tokens:?}`" )) } } else { Err(error!( Other, "`Address` expected `Token::Struct` got `{token:?}`" )) } } fn into_token(self) -> Token { let underlying_data: &[u8; 32] = &self; Token::Struct(vec![Bits256(*underlying_data).into_token()]) } } impl Tokenizable for AssetId { fn from_token(token: Token) -> Result where Self: Sized, { if let Token::Struct(tokens) = token { if let [Token::B256(data)] = tokens.as_slice() { Ok(AssetId::from(*data)) } else { Err(error!( Other, "`AssetId` expected one `Token::B256`, got `{tokens:?}`" )) } } else { Err(error!( Other, "`AssetId` expected `Token::Struct` got `{token:?}`" )) } } fn into_token(self) -> Token { let underlying_data: &[u8; 32] = &self; Token::Struct(vec![Bits256(*underlying_data).into_token()]) } } impl Tokenizable for Option where T: Tokenizable + Parameterize, { fn from_token(token: Token) -> Result { if let Token::Enum(enum_selector) = token { match *enum_selector { (0, _, _) => Ok(None), (1, token, _) => Ok(Option::::Some(T::from_token(token)?)), (_, _, _) => Err(error!( Other, "could not construct `Option` from `enum_selector`. Received: `{:?}`", enum_selector )), } } else { Err(error!( Other, "could not construct `Option` from token. Received: `{token:?}`" )) } } fn into_token(self) -> Token { let (dis, tok) = match self { None => (0, Token::Unit), Some(value) => (1, value.into_token()), }; if let ParamType::Enum { enum_variants, .. } = Self::param_type() { let selector = (dis, tok, enum_variants); Token::Enum(Box::new(selector)) } else { panic!("should never happen as `Option::param_type()` returns valid Enum variants"); } } } impl Tokenizable for std::result::Result where T: Tokenizable + Parameterize, E: Tokenizable + Parameterize, { fn from_token(token: Token) -> Result { if let Token::Enum(enum_selector) = token { match *enum_selector { (0, token, _) => Ok(std::result::Result::::Ok(T::from_token(token)?)), (1, token, _) => Ok(std::result::Result::::Err(E::from_token(token)?)), (_, _, _) => Err(error!( Other, "could not construct `Result` from `enum_selector`. Received: `{:?}`", enum_selector )), } } else { Err(error!( Other, "could not construct `Result` from token. Received: `{token:?}`" )) } } fn into_token(self) -> Token { let (dis, tok) = match self { Ok(value) => (0, value.into_token()), Err(value) => (1, value.into_token()), }; if let ParamType::Enum { enum_variants, .. } = Self::param_type() { let selector = (dis, tok, enum_variants); Token::Enum(Box::new(selector)) } else { panic!("should never happen as Result::param_type() returns valid Enum variants"); } } } impl Tokenizable for [T; SIZE] { fn from_token(token: Token) -> Result where Self: Sized, { let gen_error = |reason| error!(Other, "constructing an array of size {SIZE}: {reason}"); match token { Token::Array(elements) => { let len = elements.len(); if len != SIZE { return Err(gen_error(format!( "`Token::Array` has wrong number of elements: {len}" ))); } let detokenized = elements .into_iter() .map(Tokenizable::from_token) .collect::>>() .map_err(|err| { gen_error(format!(", not all elements could be detokenized: {err}")) })?; Ok(detokenized.try_into().unwrap_or_else(|_| { panic!("this should never fail since we're checking the length beforehand") })) } _ => Err(gen_error(format!("expected a `Token::Array`, got {token}"))), } } fn into_token(self) -> Token { Token::Array(self.map(Tokenizable::into_token).to_vec()) } } impl Tokenizable for SizedAsciiString { fn from_token(token: Token) -> Result where Self: Sized, { match token { Token::StringArray(contents) => { let expected_len = contents.get_encodable_str()?.len(); if expected_len != LEN { return Err(error!( Other, "`SizedAsciiString<{LEN}>::from_token` got a `Token::StringArray` whose expected length({}) is != {LEN}", expected_len )); } Self::new(contents.try_into()?) } _ => Err(error!( Other, "`SizedAsciiString<{LEN}>::from_token` expected a token of the variant `Token::StringArray`, got: `{token}`" )), } } fn into_token(self) -> Token { Token::StringArray(StaticStringToken::new(self.into(), Some(LEN))) } } impl Tokenizable for AsciiString { fn from_token(token: Token) -> Result where Self: Sized, { match token { Token::StringSlice(contents) => Self::new(contents.try_into()?), _ => Err(error!( Other, "`AsciiString::from_token` expected a token of the variant `Token::StringSlice`, got: `{token}`" )), } } fn into_token(self) -> Token { Token::StringSlice(StaticStringToken::new(self.into(), None)) } } #[cfg(test)] mod tests { use super::*; #[test] fn test_from_token_b256() -> Result<()> { let data = [1u8; 32]; let token = Token::B256(data); let bits256 = Bits256::from_token(token)?; assert_eq!(bits256.0, data); Ok(()) } #[test] fn test_into_token_b256() { let bytes = [1u8; 32]; let bits256 = Bits256(bytes); let token = bits256.into_token(); assert_eq!(token, Token::B256(bytes)); } #[test] fn test_from_token_raw_slice() -> Result<()> { let data = vec![42; 11]; let token = Token::RawSlice(data.clone()); let slice = RawSlice::from_token(token)?; assert_eq!(slice, data); Ok(()) } #[test] fn test_into_token_raw_slice() { let data = vec![13; 32]; let raw_slice_token = Token::RawSlice(data.clone()); let token = raw_slice_token.into_token(); assert_eq!(token, Token::RawSlice(data)); } #[test] fn sized_ascii_string_is_tokenized_correctly() -> Result<()> { let sut = SizedAsciiString::<3>::new("abc".to_string())?; let token = sut.into_token(); match token { Token::StringArray(string_token) => { let contents = string_token.get_encodable_str()?; assert_eq!(contents, "abc"); } _ => { panic!("not tokenized correctly! Should have gotten a `Token::String`") } } Ok(()) } #[test] fn sized_ascii_string_is_detokenized_correctly() -> Result<()> { let token = Token::StringArray(StaticStringToken::new("abc".to_string(), Some(3))); let sized_ascii_string = SizedAsciiString::<3>::from_token(token).expect("should have succeeded"); assert_eq!(sized_ascii_string, "abc"); Ok(()) } #[test] fn test_into_token_std_string() -> Result<()> { let expected = String::from("hello"); let token = Token::String(expected.clone()); let detokenized = String::from_token(token.into_token())?; assert_eq!(detokenized, expected); Ok(()) } } ================================================ FILE: packages/fuels-core/src/traits.rs ================================================ mod parameterize; mod signer; mod tokenizable; pub use parameterize::*; pub use signer::*; pub use tokenizable::*; ================================================ FILE: packages/fuels-core/src/types/checksum_address.rs ================================================ use sha2::{Digest, Sha256}; use crate::types::errors::{Error, Result}; pub fn checksum_encode(address: &str) -> Result { let trimmed = address.trim_start_matches("0x"); pre_validate(trimmed)?; let lowercase = trimmed.to_ascii_lowercase(); let hash = Sha256::digest(lowercase.as_bytes()); let mut checksum = String::with_capacity(trimmed.len()); for (i, addr_char) in lowercase.chars().enumerate() { let hash_byte = hash[i / 2]; let hash_nibble = if i % 2 == 0 { // even index: high nibble (hash_byte >> 4) & 0x0F } else { // odd index: low nibble hash_byte & 0x0F }; // checksum rule if hash_nibble > 7 { checksum.push(addr_char.to_ascii_uppercase()); } else { checksum.push(addr_char); } } Ok(format!("0x{checksum}")) } fn pre_validate(s: &str) -> Result<()> { if s.len() != 64 { return Err(Error::Codec("invalid address length".to_string())); } if !s.chars().all(|c| c.is_ascii_hexdigit()) { return Err(Error::Codec( "address contains invalid characters".to_string(), )); } Ok(()) } pub fn is_checksum_valid(address: &str) -> bool { let Ok(checksum) = checksum_encode(address) else { return false; }; let address_normalized = if address.starts_with("0x") { address.to_string() } else { format!("0x{}", address) }; checksum == address_normalized } #[cfg(test)] mod test { use std::str::FromStr; use fuel_core_client::client::schema::Address; use super::*; const VALID_CHECKSUM: [&str; 4] = [ "0x9cfB2CAd509D417ec40b70ebE1DD72a3624D46fdD1Ea5420dBD755CE7f4Dc897", "0x54944e5B8189827e470e5a8bAcFC6C3667397DC4E1EEF7EF3519d16D6D6c6610", "c36bE0E14d3EAf5d8D233e0F4a40b3b4e48427D25F84C460d2B03B242A38479e", "a1184D77D0D08A064E03b2bd9f50863e88faDdea4693A05cA1ee9B1732ea99B7", ]; const INVALID_CHECKSUM: [&str; 8] = [ "0x587aa0482482efEa0234752d1ad9a9c438D1f34D2859b8bef2d56A432cB68e33", "0xe10f526B192593793b7a1559aA91445faba82a1d669e3eb2DCd17f9c121b24b1", "6b63804cFbF9856e68e5B6e7aEf238dc8311ec55bec04df774003A2c96E0418e", "81f3A10b61828580D06cC4c7b0ed8f59b9Fb618bE856c55d33deCD95489A1e23", // all lower "0xf8f8b6283d7fa5b672b530cbb84fcccb4ff8dc40f8176ef4544ddb1f1952ad07", "7e2becd64cd598da59b4d1064b711661898656c6b1f4918a787156b8965dc83c", // all caps "0x26183FBE7375045250865947695DFC12500DCC43EFB9102B4E8C4D3C20009DCB", "577E424EE53A16E6A85291FEABC8443862495F74AC39A706D2DD0B9FC16955EB", ]; const INVALID_LEN: [&str; 6] = [ // too short "0x1234567890abcdef", // too long "0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234", // 65 characters "1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1", // 63 characters "0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcde", "", "0x", ]; const INVALID_CHARACTERS: &str = "0xGHIJKL7890abcdef1234567890abcdef1234567890abcdef1234567890abcdef"; #[test] fn will_detect_valid_checksums() { for valid in VALID_CHECKSUM.iter() { assert!(is_checksum_valid(valid)); } } #[test] fn will_detect_invalid_checksums() { for invalid in INVALID_CHECKSUM.iter() { assert!(!is_checksum_valid(invalid)); } } #[test] fn can_construct_address_from_checksum() { let checksum = checksum_encode(INVALID_CHECKSUM[0]).expect("should encode"); Address::from_str(&checksum).expect("should be valid address"); } #[test] fn will_detect_invalid_lengths() { for invalid in INVALID_LEN.iter() { let result = checksum_encode(invalid).expect_err("should not encode"); assert!(result.to_string().contains("invalid address length")); } } #[test] fn will_detect_invalid_characters() { let result = checksum_encode(INVALID_CHARACTERS).expect_err("should not encode"); assert!( result .to_string() .contains("address contains invalid characters") ); } } ================================================ FILE: packages/fuels-core/src/types/core/bits.rs ================================================ use fuel_tx::SubAssetId; use fuel_types::AssetId; use fuels_macros::{Parameterize, Tokenizable, TryFrom}; use crate::types::errors::Result; // A simple wrapper around [u8; 32] representing the `b256` type. Exists // mainly so that we may differentiate `Parameterize` and `Tokenizable` // implementations from what otherwise is just an array of 32 u8's. #[derive(Debug, PartialEq, Eq, Copy, Clone)] pub struct Bits256(pub [u8; 32]); impl Bits256 { /// Returns `Self` with zeroes inside. pub fn zeroed() -> Self { Self([0; 32]) } /// Create a new `Bits256` from a string representation of a hex. /// Accepts both `0x` prefixed and non-prefixed hex strings. pub fn from_hex_str(hex: &str) -> Result { let hex = if let Some(stripped_hex) = hex.strip_prefix("0x") { stripped_hex } else { hex }; let mut bytes = [0u8; 32]; hex::decode_to_slice(hex, &mut bytes as &mut [u8])?; Ok(Bits256(bytes)) } } impl From for Bits256 { fn from(value: AssetId) -> Self { Self(value.into()) } } impl From for Bits256 { fn from(value: SubAssetId) -> Self { Self(value.into()) } } // A simple wrapper around [Bits256; 2] representing the `B512` type. #[derive(Debug, PartialEq, Eq, Copy, Clone, Parameterize, Tokenizable, TryFrom)] #[FuelsCorePath = "crate"] #[FuelsTypesPath = "crate::types"] // ANCHOR: b512 pub struct B512 { pub bytes: [Bits256; 2], } // ANCHOR_END: b512 impl From<(Bits256, Bits256)> for B512 { fn from(bits_tuple: (Bits256, Bits256)) -> Self { B512 { bytes: [bits_tuple.0, bits_tuple.1], } } } #[derive(Debug, PartialEq, Eq, Copy, Clone, Parameterize, Tokenizable, TryFrom)] #[FuelsCorePath = "crate"] #[FuelsTypesPath = "crate::types"] // ANCHOR: evm_address pub struct EvmAddress { // An evm address is only 20 bytes, the first 12 bytes should be set to 0 value: Bits256, } // ANCHOR_END: evm_address impl EvmAddress { fn new(b256: Bits256) -> Self { Self { value: Bits256(Self::clear_12_bytes(b256.0)), } } pub fn value(&self) -> Bits256 { self.value } // sets the leftmost 12 bytes to zero fn clear_12_bytes(bytes: [u8; 32]) -> [u8; 32] { let mut bytes = bytes; bytes[..12].copy_from_slice(&[0u8; 12]); bytes } } impl From for EvmAddress { fn from(b256: Bits256) -> Self { EvmAddress::new(b256) } } #[cfg(test)] mod tests { use super::*; use crate::{ traits::{Parameterize, Tokenizable}, types::{Token, param_types::ParamType}, }; #[test] fn from_hex_str_b256() -> Result<()> { // ANCHOR: from_hex_str let hex_str = "0101010101010101010101010101010101010101010101010101010101010101"; let bits256 = Bits256::from_hex_str(hex_str)?; assert_eq!(bits256.0, [1u8; 32]); // With the `0x0` prefix // ANCHOR: hex_str_to_bits256 let hex_str = "0x0101010101010101010101010101010101010101010101010101010101010101"; let bits256 = Bits256::from_hex_str(hex_str)?; // ANCHOR_END: hex_str_to_bits256 assert_eq!(bits256.0, [1u8; 32]); // ANCHOR_END: from_hex_str Ok(()) } #[test] fn test_param_type_evm_addr() { assert_eq!( EvmAddress::param_type(), ParamType::Struct { name: "EvmAddress".to_string(), fields: vec![("value".to_string(), ParamType::B256)], generics: vec![] } ); } #[test] fn evm_address_clears_first_12_bytes() -> Result<()> { let data = [1u8; 32]; let address = EvmAddress::new(Bits256(data)); let expected_data = Bits256([ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, ]); assert_eq!(address.value(), expected_data); Ok(()) } #[test] fn test_into_token_evm_addr() { let bits = [1u8; 32]; let evm_address = EvmAddress::from(Bits256(bits)); let token = evm_address.into_token(); let expected_data = [ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, ]; assert_eq!(token, Token::Struct(vec![Token::B256(expected_data)])); } } ================================================ FILE: packages/fuels-core/src/types/core/bytes.rs ================================================ use crate::types::errors::Result; #[derive(Debug, PartialEq, Clone, Eq)] pub struct Bytes(pub Vec); impl Bytes { /// Create a new `Bytes` from a string representation of a hex. /// Accepts both `0x` prefixed and non-prefixed hex strings. pub fn from_hex_str(hex: &str) -> Result { let hex = if let Some(stripped_hex) = hex.strip_prefix("0x") { stripped_hex } else { hex }; let bytes = hex::decode(hex)?; Ok(Bytes(bytes)) } } impl From for Vec { fn from(bytes: Bytes) -> Vec { bytes.0 } } impl PartialEq> for Bytes { fn eq(&self, other: &Vec) -> bool { self.0 == *other } } impl PartialEq for Vec { fn eq(&self, other: &Bytes) -> bool { *self == other.0 } } #[cfg(test)] mod tests { use super::*; #[test] fn from_hex_str_b256() -> Result<()> { // ANCHOR: bytes_from_hex_str let hex_str = "0101010101010101010101010101010101010101010101010101010101010101"; let bytes = Bytes::from_hex_str(hex_str)?; assert_eq!(bytes.0, vec![1u8; 32]); // With the `0x0` prefix // ANCHOR: hex_string_to_bytes32 let hex_str = "0x0101010101010101010101010101010101010101010101010101010101010101"; let bytes = Bytes::from_hex_str(hex_str)?; // ANCHOR_END: hex_string_to_bytes32 assert_eq!(bytes.0, vec![1u8; 32]); // ANCHOR_END: bytes_from_hex_str Ok(()) } } ================================================ FILE: packages/fuels-core/src/types/core/identity.rs ================================================ use fuel_types::{Address, ContractId}; use fuels_macros::{Parameterize, Tokenizable, TryFrom}; use serde::{Deserialize, Serialize}; #[derive( Debug, Copy, Clone, PartialEq, Eq, Hash, Parameterize, Tokenizable, TryFrom, Serialize, Deserialize, )] #[FuelsCorePath = "crate"] #[FuelsTypesPath = "crate::types"] pub enum Identity { Address(Address), ContractId(ContractId), } impl Default for Identity { fn default() -> Self { Self::Address(Address::default()) } } impl AsRef<[u8]> for Identity { fn as_ref(&self) -> &[u8] { match self { Identity::Address(address) => address.as_ref(), Identity::ContractId(contract_id) => contract_id.as_ref(), } } } impl From<&Address> for Identity { fn from(address: &Address) -> Self { Self::Address(*address) } } impl From
for Identity { fn from(address: Address) -> Self { Self::Address(address) } } impl From<&ContractId> for Identity { fn from(contract_id: &ContractId) -> Self { Self::ContractId(*contract_id) } } impl From for Identity { fn from(contract_id: ContractId) -> Self { Self::ContractId(contract_id) } } ================================================ FILE: packages/fuels-core/src/types/core/raw_slice.rs ================================================ #[derive(Debug, PartialEq, Clone, Eq)] pub struct RawSlice(pub Vec); impl From for Vec { fn from(raw_slice: RawSlice) -> Vec { raw_slice.0 } } impl PartialEq> for RawSlice { fn eq(&self, other: &Vec) -> bool { self.0 == *other } } impl PartialEq for Vec { fn eq(&self, other: &RawSlice) -> bool { *self == other.0 } } ================================================ FILE: packages/fuels-core/src/types/core/sized_ascii_string.rs ================================================ use std::fmt::{Debug, Display, Formatter}; use serde::{Deserialize, Serialize}; use crate::types::errors::{Error, Result, error}; // To be used when interacting with contracts which have string slices in their ABI. // The FuelVM strings only support ascii characters. #[derive(Debug, PartialEq, Clone, Eq)] pub struct AsciiString { data: String, } impl AsciiString { pub fn new(data: String) -> Result { if !data.is_ascii() { return Err(error!( Other, "`AsciiString` must be constructed from a string containing only ascii encodable characters. Got: `{data}`" )); } Ok(Self { data }) } pub fn to_trimmed_str(&self) -> &str { self.data.trim() } pub fn to_left_trimmed_str(&self) -> &str { self.data.trim_start() } pub fn to_right_trimmed_str(&self) -> &str { self.data.trim_end() } } impl TryFrom<&str> for AsciiString { type Error = Error; fn try_from(value: &str) -> Result { Self::new(value.to_owned()) } } impl TryFrom for AsciiString { type Error = Error; fn try_from(value: String) -> Result { Self::new(value) } } impl From for String { fn from(ascii_str: AsciiString) -> Self { ascii_str.data } } impl AsRef for SizedAsciiString { fn as_ref(&self) -> &str { &self.data } } impl Display for AsciiString { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { write!(f, "{}", self.data) } } impl PartialEq<&str> for AsciiString { fn eq(&self, other: &&str) -> bool { self.data == *other } } impl PartialEq for &str { fn eq(&self, other: &AsciiString) -> bool { *self == other.data } } // To be used when interacting with contracts which have strings in their ABI. // The length of a string is part of its type -- i.e. str[2] is a // different type from str[3]. The FuelVM strings only support ascii characters. #[derive(Debug, PartialEq, Clone, Eq, Hash, Default)] pub struct SizedAsciiString { data: String, } impl SizedAsciiString { pub fn new(data: String) -> Result { if !data.is_ascii() { return Err(error!( Other, "`SizedAsciiString` must be constructed from a `String` containing only ascii encodable characters. Got: `{data}`" )); } if data.len() != LEN { return Err(error!( Other, "`SizedAsciiString<{LEN}>` must be constructed from a `String` of length {LEN}. Got: `{data}`" )); } Ok(Self { data }) } pub fn to_trimmed_str(&self) -> &str { self.data.trim() } pub fn to_left_trimmed_str(&self) -> &str { self.data.trim_start() } pub fn to_right_trimmed_str(&self) -> &str { self.data.trim_end() } /// Pad `data` string with whitespace characters on the right to fit into the `SizedAsciiString` pub fn new_with_right_whitespace_padding(data: String) -> Result { if data.len() > LEN { return Err(error!( Other, "`SizedAsciiString<{LEN}>` cannot be constructed from a string of size {}", data.len() )); } Ok(Self { data: format!("{:LEN$}", data), }) } } impl TryFrom<&str> for SizedAsciiString { type Error = Error; fn try_from(value: &str) -> Result { Self::new(value.to_owned()) } } impl TryFrom for SizedAsciiString { type Error = Error; fn try_from(value: String) -> Result { Self::new(value) } } impl From> for String { fn from(sized_ascii_str: SizedAsciiString) -> Self { sized_ascii_str.data } } impl Display for SizedAsciiString { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { write!(f, "{}", self.data) } } impl PartialEq<&str> for SizedAsciiString { fn eq(&self, other: &&str) -> bool { self.data == *other } } impl PartialEq> for &str { fn eq(&self, other: &SizedAsciiString) -> bool { *self == other.data } } impl Serialize for SizedAsciiString { fn serialize( &self, serializer: S, ) -> core::result::Result { self.data.serialize(serializer) } } impl<'de, const LEN: usize> Deserialize<'de> for SizedAsciiString { fn deserialize>( deserializer: D, ) -> core::result::Result { let data = String::deserialize(deserializer)?; Self::new(data).map_err(serde::de::Error::custom) } } #[cfg(test)] mod tests { use super::*; #[test] fn accepts_ascii_of_correct_length() { // ANCHOR: string_simple_example let ascii_data = "abc".to_string(); SizedAsciiString::<3>::new(ascii_data) .expect("should have succeeded since we gave ascii data of correct length!"); // ANCHOR_END: string_simple_example } #[test] fn refuses_non_ascii() { let ascii_data = "ab©".to_string(); let err = SizedAsciiString::<3>::new(ascii_data) .expect_err("should not have succeeded since we gave non ascii data"); let expected_reason = "`SizedAsciiString` must be constructed from a `String` containing only ascii encodable characters. Got: "; assert!(matches!(err, Error::Other(reason) if reason.starts_with(expected_reason))); } #[test] fn refuses_invalid_len() { let ascii_data = "abcd".to_string(); let err = SizedAsciiString::<3>::new(ascii_data) .expect_err("should not have succeeded since we gave data of wrong length"); let expected_reason = "`SizedAsciiString<3>` must be constructed from a `String` of length 3. Got: `abcd`"; assert!(matches!(err, Error::Other(reason) if reason.starts_with(expected_reason))); } // ANCHOR: conversion #[test] fn can_be_constructed_from_str_ref() { let _: SizedAsciiString<3> = "abc".try_into().expect("should have succeeded"); } #[test] fn can_be_constructed_from_string() { let _: SizedAsciiString<3> = "abc".to_string().try_into().expect("should have succeeded"); } #[test] fn can_be_converted_into_string() { let sized_str = SizedAsciiString::<3>::new("abc".to_string()).unwrap(); let str: String = sized_str.into(); assert_eq!(str, "abc"); } // ANCHOR_END: conversion #[test] fn can_be_printed() { let sized_str = SizedAsciiString::<3>::new("abc".to_string()).unwrap(); assert_eq!(sized_str.to_string(), "abc"); } #[test] fn can_be_compared_w_str_ref() { let sized_str = SizedAsciiString::<3>::new("abc".to_string()).unwrap(); assert_eq!(sized_str, "abc"); // and vice-versa assert_eq!("abc", sized_str); } #[test] fn trim() -> Result<()> { // Using single whitespaces let untrimmed = SizedAsciiString::<9>::new(" est abc ".to_string())?; assert_eq!("est abc ", untrimmed.to_left_trimmed_str()); assert_eq!(" est abc", untrimmed.to_right_trimmed_str()); assert_eq!("est abc", untrimmed.to_trimmed_str()); let padded = // adds 6 whitespaces SizedAsciiString::<12>::new_with_right_whitespace_padding("victor".to_string())?; assert_eq!("victor ", padded); Ok(()) } #[test] fn test_can_serialize_sized_ascii() { let sized_str = SizedAsciiString::<3>::new("abc".to_string()).unwrap(); let serialized = serde_json::to_string(&sized_str).unwrap(); assert_eq!(serialized, "\"abc\""); } #[test] fn test_can_deserialize_sized_ascii() { let serialized = "\"abc\""; let deserialized: SizedAsciiString<3> = serde_json::from_str(serialized).unwrap(); assert_eq!( deserialized, SizedAsciiString::<3>::new("abc".to_string()).unwrap() ); } #[test] fn test_can_convert_sized_ascii_to_bytes() { let sized_str = SizedAsciiString::<3>::new("abc".to_string()).unwrap(); let bytes: &[u8] = sized_str.as_ref().as_bytes(); assert_eq!(bytes, &[97, 98, 99]); } } ================================================ FILE: packages/fuels-core/src/types/core/u256.rs ================================================ #![allow(clippy::assign_op_pattern)] #![allow(clippy::manual_div_ceil)] use serde::{Deserialize, Deserializer, Serialize, Serializer, de}; use uint::construct_uint; use crate::{ traits::{Parameterize, Tokenizable}, types::{ Token, errors::{Result as FuelsResult, error}, param_types::ParamType, }, }; construct_uint! { pub struct U256(4); } impl Parameterize for U256 { fn param_type() -> ParamType { ParamType::U256 } } impl Tokenizable for U256 { fn from_token(token: Token) -> FuelsResult where Self: Sized, { match token { Token::U256(data) => Ok(data), _ => Err(error!( Other, "`U256` cannot be constructed from token `{token}`" )), } } fn into_token(self) -> Token { Token::U256(self) } } impl Serialize for U256 { fn serialize(&self, serializer: S) -> Result where S: Serializer, { serializer.serialize_str(&self.to_string()) } } impl<'de> Deserialize<'de> for U256 { fn deserialize(deserializer: D) -> Result where D: Deserializer<'de>, { U256::from_dec_str(Deserialize::deserialize(deserializer)?).map_err(de::Error::custom) } } #[cfg(test)] mod tests { use crate::types::U256; #[test] fn u256_serialize_deserialize() { let num = U256::from(123); let serialized: String = serde_json::to_string(&num).unwrap(); assert_eq!(serialized, "\"123\""); let deserialized_num: U256 = serde_json::from_str(&serialized).unwrap(); assert_eq!(deserialized_num, num); } } ================================================ FILE: packages/fuels-core/src/types/core.rs ================================================ pub use bits::*; pub use bytes::*; pub use identity::*; pub use raw_slice::*; pub use sized_ascii_string::*; pub use u256::*; mod bits; mod bytes; mod identity; mod raw_slice; mod sized_ascii_string; mod u256; ================================================ FILE: packages/fuels-core/src/types/dry_runner.rs ================================================ use std::fmt::Debug; use async_trait::async_trait; use fuel_tx::{ConsensusParameters, Transaction as FuelTransaction}; use crate::types::errors::Result; #[derive(Debug, Clone, Copy)] pub struct DryRun { pub succeeded: bool, pub script_gas: u64, pub variable_outputs: usize, } impl DryRun { pub fn gas_with_tolerance(&self, tolerance: f32) -> u64 { let gas_used = self.script_gas as f64; let adjusted_gas = gas_used * (1.0 + f64::from(tolerance)); adjusted_gas.ceil() as u64 } } #[cfg_attr(target_arch = "wasm32", async_trait(?Send))] #[cfg_attr(not(target_arch = "wasm32"), async_trait)] pub trait DryRunner: Send + Sync { async fn dry_run(&self, tx: FuelTransaction) -> Result; async fn estimate_gas_price(&self, block_horizon: u32) -> Result; async fn consensus_parameters(&self) -> Result; async fn estimate_predicates( &self, tx: &FuelTransaction, latest_chain_executor_version: Option, ) -> Result; } #[cfg_attr(target_arch = "wasm32", async_trait(?Send))] #[cfg_attr(not(target_arch = "wasm32"), async_trait)] impl DryRunner for &T { async fn dry_run(&self, tx: FuelTransaction) -> Result { (*self).dry_run(tx).await } async fn estimate_gas_price(&self, block_horizon: u32) -> Result { (*self).estimate_gas_price(block_horizon).await } async fn consensus_parameters(&self) -> Result { (*self).consensus_parameters().await } async fn estimate_predicates( &self, tx: &FuelTransaction, latest_chain_executor_version: Option, ) -> Result { (*self) .estimate_predicates(tx, latest_chain_executor_version) .await } } ================================================ FILE: packages/fuels-core/src/types/errors.rs ================================================ pub mod transaction { #[cfg(feature = "std")] use std::sync::Arc; #[cfg(not(feature = "std"))] use alloc::sync::Arc; #[derive(thiserror::Error, Debug, Clone)] pub enum Reason { #[error("builder: {0}")] Builder(String), #[error("validation: {0}")] Validation(String), #[error("squeezedOut: {0}")] SqueezedOut(String), #[error("reverted: {reason}, receipts: {receipts:?}")] Failure { reason: String, revert_id: Option, receipts: Arc>, }, #[error(": {0}")] Other(String), } impl Reason { pub(crate) fn context(self, context: impl std::fmt::Display) -> Self { match self { Reason::Builder(msg) => Reason::Builder(format!("{context}: {msg}")), Reason::Validation(msg) => Reason::Validation(format!("{context}: {msg}")), Reason::SqueezedOut(msg) => Reason::SqueezedOut(format!("{context}: {msg}")), Reason::Failure { reason, revert_id, receipts, } => Reason::Failure { reason: format!("{context}: {reason}"), revert_id, receipts, }, Reason::Other(msg) => Reason::Other(format!("{context}: {msg}")), } } } } use std::fmt::Display; use crate::sealed::Sealed; #[derive(thiserror::Error, Debug, Clone)] pub enum Error { #[error("io: {0}")] IO(String), #[error("codec: {0}")] Codec(String), #[error("transaction {0}")] Transaction(transaction::Reason), #[error("provider: {0}")] Provider(String), #[error("{0}")] Other(String), } impl From for Error { fn from(value: std::io::Error) -> Self { Self::IO(value.to_string()) } } impl Error { pub(crate) fn context(self, context: impl Display) -> Self { match self { Error::IO(msg) => Error::IO(format!("{context}: {msg}")), Error::Codec(msg) => Error::Codec(format!("{context}: {msg}")), Error::Transaction(reason) => Error::Transaction(reason.context(context)), Error::Provider(msg) => Error::Provider(format!("{context}: {msg}")), Error::Other(msg) => Error::Other(format!("{context}: {msg}")), } } } pub type Result = std::result::Result; /// Provides `context` and `with_context` to `Result`. /// /// # Examples /// ``` /// use fuels_core::types:: errors::{Context, Error, Result}; /// /// let res_with_context: Result<()> = /// Err(Error::Other("some error".to_owned())).context("some context"); /// /// let res_with_context: Result<()> = /// Err(Error::Other("some error".to_owned())).with_context(|| "some context"); /// ``` pub trait Context: Sealed { fn context(self, context: C) -> Result where C: Display + Send + Sync + 'static; fn with_context(self, f: F) -> Result where C: Display + Send + Sync + 'static, F: FnOnce() -> C; } impl Sealed for Result {} impl Context for Result { /// Wrap the error value with additional context fn context(self, context: C) -> Result where C: Display + Send + Sync + 'static, { self.map_err(|e| e.context(context)) } /// Wrap the error value with additional context that is evaluated lazily fn with_context(self, context: F) -> Result where C: Display + Send + Sync + 'static, F: FnOnce() -> C, { self.context(context()) } } /// This macro can only be used for `Error` variants that have a `String` field. /// Those are: `IO`, `Codec`, `Provider`, `Other`. #[macro_export] macro_rules! error { ($err_variant:ident, $fmt_str: literal $(,$arg: expr)*) => { $crate::types::errors::Error::$err_variant(format!($fmt_str,$($arg),*)) } } pub use error; /// This macro can only be used for `Error::Transaction` variants that have a `String` field. /// Those are: `Builder`, `Validation`, `SqueezedOut`, `Other`. #[macro_export] macro_rules! error_transaction { ($err_variant:ident, $fmt_str: literal $(,$arg: expr)*) => { $crate::types::errors::Error::Transaction( $crate::types::errors::transaction::Reason::$err_variant(format!($fmt_str,$($arg),*))) } } pub use error_transaction; impl From for Error { fn from(err: fuel_vm::checked_transaction::CheckError) -> Error { error_transaction!(Validation, "{err:?}") } } impl From for Error { fn from(err: fuel_tx::ValidityError) -> Error { error_transaction!(Validation, "{err:?}") } } macro_rules! impl_error_from { ($err_variant:ident, $err_type:ty ) => { impl From<$err_type> for $crate::types::errors::Error { fn from(err: $err_type) -> $crate::types::errors::Error { $crate::types::errors::Error::$err_variant(err.to_string()) } } }; } impl_error_from!(Other, &'static str); impl_error_from!(Other, fuel_crypto::Error); impl_error_from!(Other, serde_json::Error); impl_error_from!(Other, hex::FromHexError); impl_error_from!(Other, std::array::TryFromSliceError); impl_error_from!(Other, std::str::Utf8Error); impl_error_from!(Other, fuel_abi_types::error::Error); #[cfg(test)] mod tests { use super::*; #[test] fn result_context() { { let res_with_context: Result<()> = Err(error!(Provider, "some error")).context("some context"); assert_eq!( res_with_context.unwrap_err().to_string(), "provider: some context: some error", ); } { let res_with_context: Result<()> = Err(error_transaction!(Builder, "some error")).context("some context"); assert_eq!( res_with_context.unwrap_err().to_string(), "transaction builder: some context: some error" ); } } #[test] fn result_with_context() { { let res_with_context: Result<()> = Err(error!(Other, "some error")).with_context(|| "some context"); assert_eq!( res_with_context.unwrap_err().to_string(), "some context: some error", ); } { let res_with_context: Result<()> = Err(error_transaction!(Validation, "some error")).with_context(|| "some context"); assert_eq!( res_with_context.unwrap_err().to_string(), "transaction validation: some context: some error" ); } } } ================================================ FILE: packages/fuels-core/src/types/method_descriptor.rs ================================================ /// This type is used to specify the fn_selector and name /// of methods on contracts at compile time, exported by the abigen! macro #[derive(Debug, Clone, Copy)] pub struct MethodDescriptor { /// The name of the method. pub name: &'static str, /// The function selector of the method. pub fn_selector: &'static [u8], } impl MethodDescriptor { /// Returns the function selector of the method. pub const fn fn_selector(&self) -> &'static [u8] { self.fn_selector } /// Returns the name of the method. pub const fn name(&self) -> &'static str { self.name } } ================================================ FILE: packages/fuels-core/src/types/param_types/from_type_application.rs ================================================ use std::{collections::HashMap, iter::zip}; use fuel_abi_types::{ abi::unified_program::{UnifiedTypeApplication, UnifiedTypeDeclaration}, utils::{extract_array_len, extract_generic_name, extract_str_len, has_tuple_format}, }; use crate::types::{ errors::{Error, Result, error}, param_types::{EnumVariants, NamedParamType, ParamType}, }; impl ParamType { /// For when you need to convert a ABI JSON's UnifiedTypeApplication into a ParamType. /// /// # Arguments /// /// * `type_application`: The UnifiedTypeApplication you wish to convert into a ParamType /// * `type_lookup`: A HashMap of UnifiedTypeDeclarations mentioned in the /// UnifiedTypeApplication where the type id is the key. pub fn try_from_type_application( type_application: &UnifiedTypeApplication, type_lookup: &HashMap, ) -> Result { Type::try_from(type_application, type_lookup)?.try_into() } } #[derive(Debug, Clone)] struct Type { name: String, type_field: String, generic_params: Vec, components: Vec, } impl Type { /// Will recursively drill down the given generic parameters until all types are /// resolved. /// /// # Arguments /// /// * `type_application`: the type we wish to resolve /// * `types`: all types used in the function call pub fn try_from( type_application: &UnifiedTypeApplication, type_lookup: &HashMap, ) -> Result { Self::resolve(type_application, type_lookup, &[]) } fn resolve( type_application: &UnifiedTypeApplication, type_lookup: &HashMap, parent_generic_params: &[(usize, Type)], ) -> Result { let type_declaration = type_lookup.get(&type_application.type_id).ok_or_else(|| { error!( Codec, "type id {} not found in type lookup", type_application.type_id ) })?; if extract_generic_name(&type_declaration.type_field).is_some() { let (_, generic_type) = parent_generic_params .iter() .find(|(id, _)| *id == type_application.type_id) .ok_or_else(|| { error!( Codec, "type id {} not found in parent's generic parameters", type_application.type_id ) })?; // The generic will inherit the name from the parent `type_application` return Ok(Self { name: type_application.name.clone(), ..generic_type.clone() }); } // Figure out what does the current type do with the inherited generic // parameters and reestablish the mapping since the current type might have // renamed the inherited generic parameters. let generic_params_lookup = Self::determine_generics_for_type( type_application, type_lookup, type_declaration, parent_generic_params, )?; // Resolve the enclosed components (if any) with the newly resolved generic // parameters. let components = type_declaration .components .iter() .flatten() .map(|component| Self::resolve(component, type_lookup, &generic_params_lookup)) .collect::>>()?; Ok(Type { name: type_application.name.clone(), type_field: type_declaration.type_field.clone(), components, generic_params: generic_params_lookup .into_iter() .map(|(_, ty)| ty) .collect(), }) } /// For the given type generates generic_type_id -> Type mapping describing to /// which types generic parameters should be resolved. /// /// # Arguments /// /// * `type_application`: The type on which the generic parameters are defined. /// * `types`: All types used. /// * `parent_generic_params`: The generic parameters as inherited from the /// enclosing type (a struct/enum/array etc.). fn determine_generics_for_type( type_application: &UnifiedTypeApplication, type_lookup: &HashMap, type_declaration: &UnifiedTypeDeclaration, parent_generic_params: &[(usize, Type)], ) -> Result> { match &type_declaration.type_parameters { // The presence of type_parameters indicates that the current type // (a struct or an enum) defines some generic parameters (i.e. SomeStruct). Some(params) if !params.is_empty() => { // Determine what Types the generics will resolve to. let generic_params_from_current_type = type_application .type_arguments .iter() .flatten() .map(|ty| Self::resolve(ty, type_lookup, parent_generic_params)) .collect::>>()?; let generics_to_use = if !generic_params_from_current_type.is_empty() { generic_params_from_current_type } else { // Types such as arrays and enums inherit and forward their // generic parameters, without declaring their own. parent_generic_params .iter() .map(|(_, ty)| ty) .cloned() .collect() }; // All inherited but unused generic types are dropped. The rest are // re-mapped to new type_ids since child types are free to rename // the generic parameters as they see fit -- i.e. // struct ParentStruct{ // b: ChildStruct // } // struct ChildStruct { // c: K // } Ok(zip(params.clone(), generics_to_use).collect()) } _ => Ok(parent_generic_params.to_vec()), } } } impl TryFrom for ParamType { type Error = Error; fn try_from(value: Type) -> Result { (&value).try_into() } } impl TryFrom<&Type> for ParamType { type Error = Error; fn try_from(the_type: &Type) -> Result { let matched_param_type = [ try_primitive, try_array, try_str_array, try_str_slice, try_tuple, try_vector, try_bytes, try_std_string, try_raw_slice, try_enum, try_u128, try_struct, ] .into_iter() .map(|fun| fun(the_type)) .flat_map(|result| result.ok().flatten()) .next(); matched_param_type.map(Ok).unwrap_or_else(|| { Err(error!( Codec, "type {} couldn't be converted into a ParamType", the_type.type_field )) }) } } fn convert_into_param_types(coll: &[Type]) -> Result> { coll.iter().map(ParamType::try_from).collect() } fn named_param_types(coll: &[Type]) -> Result> { coll.iter() .map(|ttype| Ok((ttype.name.clone(), ttype.try_into()?))) .collect() } fn try_struct(the_type: &Type) -> Result> { let field = &the_type.type_field; if field.starts_with("struct ") { let fields = named_param_types(&the_type.components)?; let generics = param_types(&the_type.generic_params)?; return Ok(Some(ParamType::Struct { name: the_type .type_field .strip_prefix("struct ") .expect("has `struct`") .to_string(), fields, generics, })); } Ok(None) } fn try_vector(the_type: &Type) -> Result> { if !["struct std::vec::Vec", "struct Vec"].contains(&the_type.type_field.as_str()) { return Ok(None); } if the_type.generic_params.len() != 1 { return Err(error!( Codec, "`Vec` must have exactly one generic argument for its type. Found: `{:?}`", the_type.generic_params )); } let vec_elem_type = convert_into_param_types(&the_type.generic_params)?.remove(0); Ok(Some(ParamType::Vector(Box::new(vec_elem_type)))) } fn try_u128(the_type: &Type) -> Result> { Ok(["struct std::u128::U128", "struct U128"] .contains(&the_type.type_field.as_str()) .then_some(ParamType::U128)) } fn try_bytes(the_type: &Type) -> Result> { Ok(["struct std::bytes::Bytes", "struct Bytes"] .contains(&the_type.type_field.as_str()) .then_some(ParamType::Bytes)) } fn try_std_string(the_type: &Type) -> Result> { Ok(["struct std::string::String", "struct String"] .contains(&the_type.type_field.as_str()) .then_some(ParamType::String)) } fn try_raw_slice(the_type: &Type) -> Result> { Ok((the_type.type_field == "raw untyped slice").then_some(ParamType::RawSlice)) } fn try_enum(the_type: &Type) -> Result> { let field = &the_type.type_field; if field.starts_with("enum ") { let components = named_param_types(&the_type.components)?; let enum_variants = EnumVariants::new(components)?; let generics = param_types(&the_type.generic_params)?; return Ok(Some(ParamType::Enum { name: field.strip_prefix("enum ").expect("has `enum`").to_string(), enum_variants, generics, })); } Ok(None) } fn try_tuple(the_type: &Type) -> Result> { let result = if has_tuple_format(&the_type.type_field) { let tuple_elements = param_types(&the_type.components)?; Some(ParamType::Tuple(tuple_elements)) } else { None }; Ok(result) } fn param_types(coll: &[Type]) -> Result> { coll.iter().map(|t| t.try_into()).collect() } fn try_str_array(the_type: &Type) -> Result> { Ok(extract_str_len(&the_type.type_field).map(ParamType::StringArray)) } fn try_str_slice(the_type: &Type) -> Result> { Ok(if the_type.type_field == "str" { Some(ParamType::StringSlice) } else { None }) } fn try_array(the_type: &Type) -> Result> { if let Some(len) = extract_array_len(&the_type.type_field) { return match the_type.components.as_slice() { [single_type] => { let array_type = single_type.try_into()?; Ok(Some(ParamType::Array(Box::new(array_type), len))) } _ => Err(error!( Codec, "array must have elements of exactly one type. Array types: {:?}", the_type.components )), }; } Ok(None) } fn try_primitive(the_type: &Type) -> Result> { let result = match the_type.type_field.as_str() { "bool" => Some(ParamType::Bool), "u8" => Some(ParamType::U8), "u16" => Some(ParamType::U16), "u32" => Some(ParamType::U32), "u64" => Some(ParamType::U64), "u256" => Some(ParamType::U256), "b256" => Some(ParamType::B256), "()" => Some(ParamType::Unit), "str" => Some(ParamType::StringSlice), _ => None, }; Ok(result) } #[cfg(test)] mod tests { use super::*; #[test] fn handles_simple_types() -> Result<()> { let parse_param_type = |type_field: &str| { let type_application = UnifiedTypeApplication { name: "".to_string(), type_id: 0, type_arguments: None, error_message: None, }; let declarations = [UnifiedTypeDeclaration { type_id: 0, type_field: type_field.to_string(), components: None, type_parameters: None, alias_of: None, }]; let type_lookup = declarations .into_iter() .map(|decl| (decl.type_id, decl)) .collect::>(); ParamType::try_from_type_application(&type_application, &type_lookup) }; assert_eq!(parse_param_type("()")?, ParamType::Unit); assert_eq!(parse_param_type("bool")?, ParamType::Bool); assert_eq!(parse_param_type("u8")?, ParamType::U8); assert_eq!(parse_param_type("u16")?, ParamType::U16); assert_eq!(parse_param_type("u32")?, ParamType::U32); assert_eq!(parse_param_type("u64")?, ParamType::U64); assert_eq!(parse_param_type("u256")?, ParamType::U256); assert_eq!(parse_param_type("b256")?, ParamType::B256); assert_eq!(parse_param_type("str[21]")?, ParamType::StringArray(21)); assert_eq!(parse_param_type("str")?, ParamType::StringSlice); Ok(()) } #[test] fn handles_arrays() -> Result<()> { // given let type_application = UnifiedTypeApplication { name: "".to_string(), type_id: 0, type_arguments: None, error_message: None, }; let declarations = [ UnifiedTypeDeclaration { type_id: 0, type_field: "[_; 10]".to_string(), components: Some(vec![UnifiedTypeApplication { name: "__array_element".to_string(), type_id: 1, type_arguments: None, error_message: None, }]), type_parameters: None, alias_of: None, }, UnifiedTypeDeclaration { type_id: 1, type_field: "u8".to_string(), components: None, type_parameters: None, alias_of: None, }, ]; let type_lookup = declarations .into_iter() .map(|decl| (decl.type_id, decl)) .collect::>(); // when let result = ParamType::try_from_type_application(&type_application, &type_lookup)?; // then assert_eq!(result, ParamType::Array(Box::new(ParamType::U8), 10)); Ok(()) } #[test] fn handles_vectors() -> Result<()> { // given let declarations = [ UnifiedTypeDeclaration { type_id: 1, type_field: "generic T".to_string(), components: None, type_parameters: None, alias_of: None, }, UnifiedTypeDeclaration { type_id: 2, type_field: "raw untyped ptr".to_string(), components: None, type_parameters: None, alias_of: None, }, UnifiedTypeDeclaration { type_id: 3, type_field: "struct std::vec::RawVec".to_string(), components: Some(vec![ UnifiedTypeApplication { name: "ptr".to_string(), type_id: 2, type_arguments: None, error_message: None, }, UnifiedTypeApplication { name: "cap".to_string(), type_id: 5, type_arguments: None, error_message: None, }, ]), type_parameters: Some(vec![1]), alias_of: None, }, UnifiedTypeDeclaration { type_id: 4, type_field: "struct std::vec::Vec".to_string(), components: Some(vec![ UnifiedTypeApplication { name: "buf".to_string(), type_id: 3, type_arguments: Some(vec![UnifiedTypeApplication { name: "".to_string(), type_id: 1, type_arguments: None, error_message: None, }]), error_message: None, }, UnifiedTypeApplication { name: "len".to_string(), type_id: 5, type_arguments: None, error_message: None, }, ]), type_parameters: Some(vec![1]), alias_of: None, }, UnifiedTypeDeclaration { type_id: 5, type_field: "u64".to_string(), components: None, type_parameters: None, alias_of: None, }, UnifiedTypeDeclaration { type_id: 6, type_field: "u8".to_string(), components: None, type_parameters: None, alias_of: None, }, ]; let type_application = UnifiedTypeApplication { name: "arg".to_string(), type_id: 4, type_arguments: Some(vec![UnifiedTypeApplication { name: "".to_string(), type_id: 6, type_arguments: None, error_message: None, }]), error_message: None, }; let type_lookup = declarations .into_iter() .map(|decl| (decl.type_id, decl)) .collect::>(); // when let result = ParamType::try_from_type_application(&type_application, &type_lookup)?; // then assert_eq!(result, ParamType::Vector(Box::new(ParamType::U8))); Ok(()) } #[test] fn handles_structs() -> Result<()> { // given let declarations = [ UnifiedTypeDeclaration { type_id: 1, type_field: "generic T".to_string(), components: None, type_parameters: None, alias_of: None, }, UnifiedTypeDeclaration { type_id: 2, type_field: "struct SomeStruct".to_string(), components: Some(vec![UnifiedTypeApplication { name: "field".to_string(), type_id: 1, type_arguments: None, error_message: None, }]), type_parameters: Some(vec![1]), alias_of: None, }, UnifiedTypeDeclaration { type_id: 3, type_field: "u8".to_string(), components: None, type_parameters: None, alias_of: None, }, ]; let type_application = UnifiedTypeApplication { name: "arg".to_string(), type_id: 2, type_arguments: Some(vec![UnifiedTypeApplication { name: "".to_string(), type_id: 3, type_arguments: None, error_message: None, }]), error_message: None, }; let type_lookup = declarations .into_iter() .map(|decl| (decl.type_id, decl)) .collect::>(); // when let result = ParamType::try_from_type_application(&type_application, &type_lookup)?; // then assert_eq!( result, ParamType::Struct { name: "SomeStruct".to_string(), fields: vec![("field".to_string(), ParamType::U8)], generics: vec![ParamType::U8] } ); Ok(()) } #[test] fn handles_enums() -> Result<()> { // given let declarations = [ UnifiedTypeDeclaration { type_id: 1, type_field: "generic T".to_string(), components: None, type_parameters: None, alias_of: None, }, UnifiedTypeDeclaration { type_id: 2, type_field: "enum SomeEnum".to_string(), components: Some(vec![UnifiedTypeApplication { name: "Variant".to_string(), type_id: 1, type_arguments: None, error_message: None, }]), type_parameters: Some(vec![1]), alias_of: None, }, UnifiedTypeDeclaration { type_id: 3, type_field: "u8".to_string(), components: None, type_parameters: None, alias_of: None, }, ]; let type_application = UnifiedTypeApplication { name: "arg".to_string(), type_id: 2, type_arguments: Some(vec![UnifiedTypeApplication { name: "".to_string(), type_id: 3, type_arguments: None, error_message: None, }]), error_message: None, }; let type_lookup = declarations .into_iter() .map(|decl| (decl.type_id, decl)) .collect::>(); // when let result = ParamType::try_from_type_application(&type_application, &type_lookup)?; // then assert_eq!( result, ParamType::Enum { name: "SomeEnum".to_string(), enum_variants: EnumVariants::new(vec![("Variant".to_string(), ParamType::U8)])?, generics: vec![ParamType::U8] } ); Ok(()) } #[test] fn handles_tuples() -> Result<()> { // given let declarations = [ UnifiedTypeDeclaration { type_id: 1, type_field: "(_, _)".to_string(), components: Some(vec![ UnifiedTypeApplication { name: "__tuple_element".to_string(), type_id: 3, type_arguments: None, error_message: None, }, UnifiedTypeApplication { name: "__tuple_element".to_string(), type_id: 2, type_arguments: None, error_message: None, }, ]), type_parameters: None, alias_of: None, }, UnifiedTypeDeclaration { type_id: 2, type_field: "str[15]".to_string(), components: None, type_parameters: None, alias_of: None, }, UnifiedTypeDeclaration { type_id: 3, type_field: "u8".to_string(), components: None, type_parameters: None, alias_of: None, }, ]; let type_application = UnifiedTypeApplication { name: "arg".to_string(), type_id: 1, type_arguments: None, error_message: None, }; let type_lookup = declarations .into_iter() .map(|decl| (decl.type_id, decl)) .collect::>(); // when let result = ParamType::try_from_type_application(&type_application, &type_lookup)?; // then assert_eq!( result, ParamType::Tuple(vec![ParamType::U8, ParamType::StringArray(15)]) ); Ok(()) } #[test] fn ultimate_example() -> Result<()> { // given let declarations = [ UnifiedTypeDeclaration { type_id: 1, type_field: "(_, _)".to_string(), components: Some(vec![ UnifiedTypeApplication { name: "__tuple_element".to_string(), type_id: 11, type_arguments: None, error_message: None, }, UnifiedTypeApplication { name: "__tuple_element".to_string(), type_id: 11, type_arguments: None, error_message: None, }, ]), type_parameters: None, alias_of: None, }, UnifiedTypeDeclaration { type_id: 2, type_field: "(_, _)".to_string(), components: Some(vec![ UnifiedTypeApplication { name: "__tuple_element".to_string(), type_id: 4, type_arguments: None, error_message: None, }, UnifiedTypeApplication { name: "__tuple_element".to_string(), type_id: 24, type_arguments: None, error_message: None, }, ]), type_parameters: None, alias_of: None, }, UnifiedTypeDeclaration { type_id: 3, type_field: "(_, _)".to_string(), components: Some(vec![ UnifiedTypeApplication { name: "__tuple_element".to_string(), type_id: 5, type_arguments: None, error_message: None, }, UnifiedTypeApplication { name: "__tuple_element".to_string(), type_id: 13, type_arguments: None, error_message: None, }, ]), type_parameters: None, alias_of: None, }, UnifiedTypeDeclaration { type_id: 4, type_field: "[_; 1]".to_string(), components: Some(vec![UnifiedTypeApplication { name: "__array_element".to_string(), type_id: 8, type_arguments: Some(vec![UnifiedTypeApplication { name: "".to_string(), type_id: 22, type_arguments: Some(vec![UnifiedTypeApplication { name: "".to_string(), type_id: 21, type_arguments: Some(vec![UnifiedTypeApplication { name: "".to_string(), type_id: 18, type_arguments: Some(vec![UnifiedTypeApplication { name: "".to_string(), type_id: 13, type_arguments: None, error_message: None, }]), error_message: None, }]), error_message: None, }]), error_message: None, }]), error_message: None, }]), type_parameters: None, alias_of: None, }, UnifiedTypeDeclaration { type_id: 5, type_field: "[_; 2]".to_string(), components: Some(vec![UnifiedTypeApplication { name: "__array_element".to_string(), type_id: 14, type_arguments: None, error_message: None, }]), type_parameters: None, alias_of: None, }, UnifiedTypeDeclaration { type_id: 6, type_field: "[_; 2]".to_string(), components: Some(vec![UnifiedTypeApplication { name: "__array_element".to_string(), type_id: 10, type_arguments: None, error_message: None, }]), type_parameters: None, alias_of: None, }, UnifiedTypeDeclaration { type_id: 7, type_field: "b256".to_string(), components: None, type_parameters: None, alias_of: None, }, UnifiedTypeDeclaration { type_id: 8, type_field: "enum EnumWGeneric".to_string(), components: Some(vec![ UnifiedTypeApplication { name: "A".to_string(), type_id: 25, type_arguments: None, error_message: None, }, UnifiedTypeApplication { name: "B".to_string(), type_id: 12, type_arguments: None, error_message: None, }, ]), type_parameters: Some(vec![12]), alias_of: None, }, UnifiedTypeDeclaration { type_id: 9, type_field: "generic K".to_string(), components: None, type_parameters: None, alias_of: None, }, UnifiedTypeDeclaration { type_id: 10, type_field: "generic L".to_string(), components: None, type_parameters: None, alias_of: None, }, UnifiedTypeDeclaration { type_id: 11, type_field: "generic M".to_string(), components: None, type_parameters: None, alias_of: None, }, UnifiedTypeDeclaration { type_id: 12, type_field: "generic N".to_string(), components: None, type_parameters: None, alias_of: None, }, UnifiedTypeDeclaration { type_id: 13, type_field: "generic T".to_string(), components: None, type_parameters: None, alias_of: None, }, UnifiedTypeDeclaration { type_id: 14, type_field: "generic U".to_string(), components: None, type_parameters: None, alias_of: None, }, UnifiedTypeDeclaration { type_id: 15, type_field: "raw untyped ptr".to_string(), components: None, type_parameters: None, alias_of: None, }, UnifiedTypeDeclaration { type_id: 16, type_field: "str[2]".to_string(), components: None, type_parameters: None, alias_of: None, }, UnifiedTypeDeclaration { type_id: 17, type_field: "struct MegaExample".to_string(), components: Some(vec![ UnifiedTypeApplication { name: "a".to_string(), type_id: 3, type_arguments: None, error_message: None, }, UnifiedTypeApplication { name: "b".to_string(), type_id: 23, type_arguments: Some(vec![UnifiedTypeApplication { name: "".to_string(), type_id: 2, type_arguments: None, error_message: None, }]), error_message: None, }, ]), type_parameters: Some(vec![13, 14]), alias_of: None, }, UnifiedTypeDeclaration { type_id: 18, type_field: "struct PassTheGenericOn".to_string(), components: Some(vec![UnifiedTypeApplication { name: "one".to_string(), type_id: 20, type_arguments: Some(vec![UnifiedTypeApplication { name: "".to_string(), type_id: 9, type_arguments: None, error_message: None, }]), error_message: None, }]), type_parameters: Some(vec![9]), alias_of: None, }, UnifiedTypeDeclaration { type_id: 19, type_field: "struct std::vec::RawVec".to_string(), components: Some(vec![ UnifiedTypeApplication { name: "ptr".to_string(), type_id: 15, type_arguments: None, error_message: None, }, UnifiedTypeApplication { name: "cap".to_string(), type_id: 25, type_arguments: None, error_message: None, }, ]), type_parameters: Some(vec![13]), alias_of: None, }, UnifiedTypeDeclaration { type_id: 20, type_field: "struct SimpleGeneric".to_string(), components: Some(vec![UnifiedTypeApplication { name: "single_generic_param".to_string(), type_id: 13, type_arguments: None, error_message: None, }]), type_parameters: Some(vec![13]), alias_of: None, }, UnifiedTypeDeclaration { type_id: 21, type_field: "struct StructWArrayGeneric".to_string(), components: Some(vec![UnifiedTypeApplication { name: "a".to_string(), type_id: 6, type_arguments: None, error_message: None, }]), type_parameters: Some(vec![10]), alias_of: None, }, UnifiedTypeDeclaration { type_id: 22, type_field: "struct StructWTupleGeneric".to_string(), components: Some(vec![UnifiedTypeApplication { name: "a".to_string(), type_id: 1, type_arguments: None, error_message: None, }]), type_parameters: Some(vec![11]), alias_of: None, }, UnifiedTypeDeclaration { type_id: 23, type_field: "struct std::vec::Vec".to_string(), components: Some(vec![ UnifiedTypeApplication { name: "buf".to_string(), type_id: 19, type_arguments: Some(vec![UnifiedTypeApplication { name: "".to_string(), type_id: 13, type_arguments: None, error_message: None, }]), error_message: None, }, UnifiedTypeApplication { name: "len".to_string(), type_id: 25, type_arguments: None, error_message: None, }, ]), type_parameters: Some(vec![13]), alias_of: None, }, UnifiedTypeDeclaration { type_id: 24, type_field: "u32".to_string(), components: None, type_parameters: None, alias_of: None, }, UnifiedTypeDeclaration { type_id: 25, type_field: "u64".to_string(), components: None, type_parameters: None, alias_of: None, }, ]; let type_lookup = declarations .into_iter() .map(|decl| (decl.type_id, decl)) .collect::>(); let type_application = UnifiedTypeApplication { name: "arg1".to_string(), type_id: 17, type_arguments: Some(vec![ UnifiedTypeApplication { name: "".to_string(), type_id: 16, type_arguments: None, error_message: None, }, UnifiedTypeApplication { name: "".to_string(), type_id: 7, type_arguments: None, error_message: None, }, ]), error_message: None, }; // when let result = ParamType::try_from_type_application(&type_application, &type_lookup)?; // then let expected_param_type = { let fields = vec![( "one".to_string(), ParamType::Struct { name: "SimpleGeneric".to_string(), fields: vec![( "single_generic_param".to_string(), ParamType::StringArray(2), )], generics: vec![ParamType::StringArray(2)], }, )]; let pass_the_generic_on = ParamType::Struct { name: "PassTheGenericOn".to_string(), fields, generics: vec![ParamType::StringArray(2)], }; let fields = vec![( "a".to_string(), ParamType::Array(Box::from(pass_the_generic_on.clone()), 2), )]; let struct_w_array_generic = ParamType::Struct { name: "StructWArrayGeneric".to_string(), fields, generics: vec![pass_the_generic_on], }; let fields = vec![( "a".to_string(), ParamType::Tuple(vec![ struct_w_array_generic.clone(), struct_w_array_generic.clone(), ]), )]; let struct_w_tuple_generic = ParamType::Struct { name: "StructWTupleGeneric".to_string(), fields, generics: vec![struct_w_array_generic], }; let types = vec![ ("A".to_string(), ParamType::U64), ("B".to_string(), struct_w_tuple_generic.clone()), ]; let fields = vec![ ( "a".to_string(), ParamType::Tuple(vec![ ParamType::Array(Box::from(ParamType::B256), 2), ParamType::StringArray(2), ]), ), ( "b".to_string(), ParamType::Vector(Box::from(ParamType::Tuple(vec![ ParamType::Array( Box::from(ParamType::Enum { name: "EnumWGeneric".to_string(), enum_variants: EnumVariants::new(types).unwrap(), generics: vec![struct_w_tuple_generic], }), 1, ), ParamType::U32, ]))), ), ]; ParamType::Struct { name: "MegaExample".to_string(), fields, generics: vec![ParamType::StringArray(2), ParamType::B256], } }; assert_eq!(result, expected_param_type); Ok(()) } #[test] fn try_vector_correctly_resolves_param_type() { let the_type = given_generic_type_with_path("std::vec::Vec"); let param_type = try_vector(&the_type).unwrap().unwrap(); assert_eq!(param_type, ParamType::Vector(Box::new(ParamType::U8))); } #[test] fn try_bytes_correctly_resolves_param_type() { let the_type = given_type_with_path("std::bytes::Bytes"); let param_type = try_bytes(&the_type).unwrap().unwrap(); assert_eq!(param_type, ParamType::Bytes); } #[test] fn try_raw_slice_correctly_resolves_param_type() { let the_type = Type { name: "".to_string(), type_field: "raw untyped slice".to_string(), generic_params: vec![], components: vec![], }; let param_type = try_raw_slice(&the_type).unwrap().unwrap(); assert_eq!(param_type, ParamType::RawSlice); } #[test] fn try_std_string_correctly_resolves_param_type() { let the_type = given_type_with_path("std::string::String"); let param_type = try_std_string(&the_type).unwrap().unwrap(); assert_eq!(param_type, ParamType::String); } fn given_type_with_path(path: &str) -> Type { Type { name: "".to_string(), type_field: format!("struct {path}"), generic_params: vec![], components: vec![], } } fn given_generic_type_with_path(path: &str) -> Type { Type { name: "".to_string(), type_field: format!("struct {path}"), generic_params: vec![Type { name: "".to_string(), type_field: "u8".to_string(), generic_params: vec![], components: vec![], }], components: vec![], } } } ================================================ FILE: packages/fuels-core/src/types/param_types/param_type.rs ================================================ use crate::types::errors::{Result, error}; pub type NamedParamType = (String, ParamType); #[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)] pub enum ParamType { Unit, Bool, U8, U16, U32, U64, U128, U256, B256, Bytes, String, RawSlice, StringArray(usize), StringSlice, Tuple(Vec), Array(Box, usize), Vector(Box), Struct { name: String, fields: Vec, generics: Vec, }, Enum { name: String, enum_variants: EnumVariants, generics: Vec, }, } pub enum ReturnLocation { Return, ReturnData, } #[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)] pub struct EnumVariants { variants: Vec, } impl EnumVariants { pub fn new(variants: Vec) -> Result { if variants.is_empty() { return Err(error!(Other, "enum variants cannot be empty!")); } Ok(EnumVariants { variants }) } pub fn variants(&self) -> &Vec { &self.variants } pub fn param_types(&self) -> impl Iterator { self.variants.iter().map(|(_, param_type)| param_type) } pub fn select_variant(&self, discriminant: u64) -> Result<&NamedParamType> { self.variants.get(discriminant as usize).ok_or_else(|| { error!( Other, "discriminant `{discriminant}` doesn't point to any variant: {:?}", self.variants() ) }) } } ================================================ FILE: packages/fuels-core/src/types/param_types.rs ================================================ mod from_type_application; mod param_type; pub use param_type::*; ================================================ FILE: packages/fuels-core/src/types/token.rs ================================================ use std::fmt; use crate::types::{ core::U256, errors::{Error, Result, error}, param_types::EnumVariants, }; pub type EnumSelector = (u64, Token, EnumVariants); #[derive(Debug, Clone, PartialEq, Eq, Default, serde::Serialize, serde::Deserialize)] pub struct StaticStringToken { pub(crate) data: String, expected_len: Option, } impl StaticStringToken { pub fn new(data: String, expected_len: Option) -> Self { StaticStringToken { data, expected_len } } fn validate(&self) -> Result<()> { if !self.data.is_ascii() { return Err(error!(Codec, "string data can only have ascii values")); } if let Some(expected_len) = self.expected_len && self.data.len() != expected_len { return Err(error!( Codec, "string data has len {}, but the expected len is {}", self.data.len(), expected_len )); } Ok(()) } pub fn get_encodable_str(&self) -> Result<&str> { self.validate()?; Ok(self.data.as_str()) } } impl TryFrom for String { type Error = Error; fn try_from(string_token: StaticStringToken) -> Result { string_token.validate()?; Ok(string_token.data) } } #[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)] pub enum Token { // Used for unit type variants in Enum. An "empty" enum is not represented as Enum, // because this way we can have both unit and non-unit type variants. Unit, Bool(bool), U8(u8), U16(u16), U32(u32), U64(u64), U128(u128), U256(U256), B256([u8; 32]), Bytes(Vec), String(String), RawSlice(Vec), StringArray(StaticStringToken), StringSlice(StaticStringToken), Tuple(Vec), Array(Vec), Vector(Vec), Struct(Vec), Enum(Box), } impl fmt::Display for Token { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "{self:?}") } } impl Default for Token { fn default() -> Self { Token::U8(0) } } ================================================ FILE: packages/fuels-core/src/types/transaction_builders/blob.rs ================================================ use std::{fmt::Debug, iter::repeat, sync::Arc}; use async_trait::async_trait; use fuel_crypto::Signature; use fuel_tx::{ BlobIdExt, Chargeable, Output, Transaction as FuelTransaction, UniqueIdentifier, Witness, field::{Policies as PoliciesField, Witnesses}, policies::{Policies, PolicyType}, }; use fuel_types::bytes::padded_len_usize; use itertools::Itertools; use super::{ BuildableTransaction, GAS_ESTIMATION_BLOCK_HORIZON, Strategy, TransactionBuilder, UnresolvedWitnessIndexes, generate_missing_witnesses, impl_tx_builder_trait, resolve_fuel_inputs, }; use crate::{ constants::SIGNATURE_WITNESS_SIZE, traits::Signer, types::{ DryRunner, errors::{Result, error, error_transaction}, input::Input, transaction::{BlobTransaction, EstimablePredicates, Transaction, TxPolicies}, }, utils::{calculate_witnesses_size, sealed}, }; #[derive(Default, Clone, Debug, PartialEq)] pub struct Blob { data: Vec, } pub type BlobId = [u8; 32]; impl From> for Blob { fn from(data: Vec) -> Self { Self { data } } } impl AsRef<[u8]> for Blob { fn as_ref(&self) -> &[u8] { &self.data } } impl Blob { pub fn new(data: Vec) -> Self { Self { data } } pub fn len(&self) -> usize { self.data.len() } pub fn is_empty(&self) -> bool { self.data.is_empty() } pub fn id(&self) -> BlobId { fuel_tx::BlobId::compute(&self.data).into() } pub fn bytes(&self) -> &[u8] { self.data.as_slice() } fn as_blob_body(&self, witness_index: u16) -> fuel_tx::BlobBody { fuel_tx::BlobBody { id: self.id().into(), witness_index, } } } impl From for Vec { fn from(value: Blob) -> Self { value.data } } impl From for fuel_tx::Witness { fn from(blob: Blob) -> Self { blob.data.into() } } #[derive(Debug, Clone)] pub struct BlobTransactionBuilder { pub inputs: Vec, pub outputs: Vec, pub witnesses: Vec, pub tx_policies: TxPolicies, pub gas_price_estimation_block_horizon: u32, pub max_fee_estimation_tolerance: f32, pub build_strategy: Strategy, pub blob: Blob, unresolved_witness_indexes: UnresolvedWitnessIndexes, unresolved_signers: Vec>, enable_burn: bool, } impl Default for BlobTransactionBuilder { fn default() -> Self { Self { inputs: Default::default(), outputs: Default::default(), witnesses: Default::default(), tx_policies: Default::default(), gas_price_estimation_block_horizon: GAS_ESTIMATION_BLOCK_HORIZON, max_fee_estimation_tolerance: Default::default(), build_strategy: Default::default(), blob: Default::default(), unresolved_witness_indexes: Default::default(), unresolved_signers: Default::default(), enable_burn: false, } } } impl_tx_builder_trait!(BlobTransactionBuilder, BlobTransaction); impl BlobTransactionBuilder { /// Calculates the maximum possible blob size by determining the remaining space available in the current transaction before it reaches the maximum allowed size. /// Note: This calculation only considers the transaction size limit and does not account for the maximum gas per transaction. pub async fn estimate_max_blob_size(&self, provider: &impl DryRunner) -> Result { let mut tb = self.clone(); tb.blob = Blob::new(vec![]); let tx = tb .with_build_strategy(Strategy::NoSignatures) .build(provider) .await?; let current_tx_size = tx.size(); let max_tx_size = usize::try_from( provider .consensus_parameters() .await? .tx_params() .max_size(), ) .unwrap_or(usize::MAX); Ok(max_tx_size.saturating_sub(current_tx_size)) } pub async fn build(mut self, provider: impl DryRunner) -> Result { let consensus_parameters = provider.consensus_parameters().await?; self.intercept_burn(consensus_parameters.base_asset_id())?; let is_using_predicates = self.is_using_predicates(); let tx = match self.build_strategy { Strategy::Complete => self.resolve_fuel_tx(&provider).await?, Strategy::NoSignatures => { self.set_witness_indexes(); self.unresolved_signers = Default::default(); self.resolve_fuel_tx(&provider).await? } }; Ok(BlobTransaction { is_using_predicates, tx, }) } async fn resolve_fuel_tx(mut self, provider: &impl DryRunner) -> Result { let chain_id = provider.consensus_parameters().await?.chain_id(); let free_witness_index = self.num_witnesses()?; let body = self.blob.as_blob_body(free_witness_index); let blob_witness = std::mem::take(&mut self.blob).into(); self.witnesses_mut().push(blob_witness); let num_witnesses = self.num_witnesses()?; let policies = self.generate_fuel_policies()?; let is_using_predicates = self.is_using_predicates(); let mut tx = FuelTransaction::blob( body, policies, resolve_fuel_inputs(self.inputs, num_witnesses, &self.unresolved_witness_indexes)?, self.outputs, self.witnesses, ); if let Some(max_fee) = self.tx_policies.max_fee() { tx.policies_mut().set(PolicyType::MaxFee, Some(max_fee)); } else { Self::set_max_fee_policy( &mut tx, &provider, self.gas_price_estimation_block_horizon, is_using_predicates, self.max_fee_estimation_tolerance, ) .await?; } let signatures = generate_missing_witnesses(tx.id(&chain_id), &self.unresolved_signers).await?; tx.witnesses_mut().extend(signatures); Ok(tx) } pub fn with_blob(mut self, blob: Blob) -> Self { self.blob = blob; self } pub fn with_max_fee_estimation_tolerance(mut self, max_fee_estimation_tolerance: f32) -> Self { self.max_fee_estimation_tolerance = max_fee_estimation_tolerance; self } } impl sealed::Sealed for BlobTransactionBuilder {} #[cfg_attr(not(target_arch = "wasm32"), async_trait)] impl BuildableTransaction for BlobTransactionBuilder { type TxType = BlobTransaction; type Strategy = Strategy; fn with_build_strategy(mut self, strategy: Self::Strategy) -> Self { self.build_strategy = strategy; self } async fn build(self, provider: impl DryRunner) -> Result { BlobTransactionBuilder::build(self, provider).await } } ================================================ FILE: packages/fuels-core/src/types/transaction_builders/script_tx_estimator.rs ================================================ use std::iter::repeat_n; use fuel_crypto::Signature; use fuel_tx::{ AssetId, Chargeable, ConsensusParameters, Input as FuelInput, TxPointer, Witness, field::{Inputs, Outputs, ScriptGasLimit, WitnessLimit, Witnesses}, input::coin::{CoinPredicate, CoinSigned}, }; use itertools::Itertools; use crate::{ constants::WITNESS_STATIC_SIZE, types::{DryRun, DryRunner, errors::Result}, }; pub(crate) struct ScriptTxEstimator { dry_runner: R, predefined_witnesses: Vec, num_unresolved_witnesses: usize, last_dry_run: Option, } impl ScriptTxEstimator { pub fn new( dry_runner: R, predefined_witnesses: Vec, num_unresolved_witnesses: usize, ) -> Self { Self { dry_runner, predefined_witnesses, num_unresolved_witnesses, last_dry_run: None, } } } impl ScriptTxEstimator { pub async fn run( &mut self, mut tx: fuel_tx::Script, saturate_variable_outputs: bool, ) -> Result { self.prepare_for_estimation(&mut tx, saturate_variable_outputs) .await?; self._run(tx).await } pub async fn prepare_for_estimation( &mut self, tx: &mut fuel_tx::Script, saturate_variable_outputs: bool, ) -> Result<()> { let consensus_params = self.dry_runner.consensus_parameters().await?; self.add_fake_witnesses(tx); self.add_fake_coins(tx, &consensus_params); if saturate_variable_outputs { self.saturate_with_variable_outputs(tx, &consensus_params); } self.set_script_gas_limit_to_max(tx, &consensus_params); Ok(()) } pub fn last_dry_run(&self) -> Option { self.last_dry_run } async fn _run(&mut self, tx: fuel_tx::Script) -> Result { let dry_run = self.dry_runner.dry_run(tx.clone().into()).await?; self.last_dry_run = Some(dry_run); Ok(dry_run) } fn set_script_gas_limit_to_max( &self, tx: &mut fuel_tx::Script, consensus_params: &ConsensusParameters, ) { let max_gas = tx.max_gas(consensus_params.gas_costs(), consensus_params.fee_params()) + 1; *tx.script_gas_limit_mut() = consensus_params.tx_params().max_gas_per_tx() - max_gas; } fn saturate_with_variable_outputs( &self, tx: &mut fuel_tx::Script, consensus_params: &ConsensusParameters, ) { let max_outputs = usize::from(consensus_params.tx_params().max_outputs()); let used_outputs = tx.outputs().len(); let unused_outputs = max_outputs.saturating_sub(used_outputs); super::add_variable_outputs(tx, unused_outputs); } // When dry running a tx with `utxo_validation` off, the node will not validate signatures. // However, the node will check if the right number of witnesses is present. // This function will create witnesses from a default `Signature` such that the total length matches the expected one. // Using a `Signature` ensures that the calculated fee includes the fee generated by the witnesses. fn add_fake_witnesses(&self, tx: &mut fuel_tx::Script) { let witness: Witness = Signature::default().as_ref().into(); let dry_run_witnesses: Vec<_> = repeat_n(witness, self.num_unresolved_witnesses).collect(); *tx.witnesses_mut() = [self.predefined_witnesses.clone(), dry_run_witnesses].concat(); } fn add_fake_coins(&self, tx: &mut fuel_tx::Script, consensus_params: &ConsensusParameters) { if let Some(fake_input) = Self::needs_fake_base_input(tx.inputs(), consensus_params.base_asset_id()) { tx.inputs_mut().push(fake_input); // Add an empty `Witness` for the `coin_signed` we just added tx.witnesses_mut().push(Witness::default()); tx.set_witness_limit(tx.witness_limit() + WITNESS_STATIC_SIZE as u64); } } fn needs_fake_base_input( inputs: &[FuelInput], base_asset_id: &AssetId, ) -> Option { let has_base_asset = inputs.iter().any(|i| match i { FuelInput::CoinSigned(CoinSigned { asset_id, .. }) | FuelInput::CoinPredicate(CoinPredicate { asset_id, .. }) if asset_id == base_asset_id => { true } FuelInput::MessageCoinSigned(_) | FuelInput::MessageCoinPredicate(_) => true, _ => false, }); if has_base_asset { return None; } let unique_owners = inputs .iter() .filter_map(|input| match input { FuelInput::CoinSigned(CoinSigned { owner, .. }) | FuelInput::CoinPredicate(CoinPredicate { owner, .. }) => Some(owner), _ => None, }) .unique() .collect::>(); let fake_owner = if let [single_owner] = unique_owners.as_slice() { **single_owner } else { Default::default() }; Some(FuelInput::coin_signed( Default::default(), fake_owner, 1_000_000_000, *base_asset_id, TxPointer::default(), 0, )) } } ================================================ FILE: packages/fuels-core/src/types/transaction_builders.rs ================================================ #![cfg(feature = "std")] use async_trait::async_trait; use fuel_asm::{GTFArgs, RegId, op}; use fuel_crypto::{Hasher, Message as CryptoMessage, Signature}; use fuel_tx::{ Chargeable, ConsensusParameters, Create, Input as FuelInput, Output, Script, StorageSlot, Transaction as FuelTransaction, TransactionFee, TxPointer, UniqueIdentifier, Upgrade, Upload, UploadBody, Witness, field::{Outputs, Policies as PoliciesField, ScriptGasLimit, Witnesses}, policies::{Policies, PolicyType}, }; pub use fuel_tx::{UpgradePurpose, UploadSubsection}; use fuel_types::{Bytes32, Salt, bytes::padded_len_usize}; use itertools::Itertools; use script_tx_estimator::ScriptTxEstimator; use std::iter::repeat_n; use std::{ collections::HashMap, fmt::{Debug, Formatter}, iter::repeat, sync::Arc, }; use crate::{ constants::{DEFAULT_GAS_ESTIMATION_BLOCK_HORIZON, SIGNATURE_WITNESS_SIZE, WORD_SIZE}, traits::Signer, types::{ Address, AssetId, ContractId, DryRunner, coin::Coin, coin_type::CoinType, errors::{Result, error, error_transaction}, input::Input, message::Message, transaction::{ CreateTransaction, EstimablePredicates, ScriptTransaction, Transaction, TxPolicies, UpgradeTransaction, UploadTransaction, }, }, utils::{calculate_witnesses_size, sealed}, }; mod blob; mod script_tx_estimator; pub use blob::*; const GAS_ESTIMATION_BLOCK_HORIZON: u32 = DEFAULT_GAS_ESTIMATION_BLOCK_HORIZON; #[derive(Debug, Clone, Default)] struct UnresolvedWitnessIndexes { owner_to_idx_offset: HashMap, } #[cfg_attr(not(target_arch = "wasm32"), async_trait)] pub trait BuildableTransaction: sealed::Sealed { type TxType: Transaction; type Strategy; fn with_build_strategy(self, strategy: Self::Strategy) -> Self; async fn build(self, provider: impl DryRunner) -> Result; } impl sealed::Sealed for ScriptTransactionBuilder {} #[derive(Debug, Clone, Default)] pub enum ScriptBuildStrategy { /// Transaction is estimated and signatures are automatically added. #[default] Complete, /// Transaction is estimated but no signatures are added. /// Building without signatures will set the witness indexes of signed coins in the /// order as they appear in the inputs. Multiple coins with the same owner will have /// the same witness index. Make sure you sign the built transaction in the expected order. NoSignatures, /// No estimation is done and no signatures are added. Fake coins are added if no spendable inputs /// are present. Meant only for transactions that are to be dry-run with validations off. /// Useful for reading state with unfunded accounts. StateReadOnly, } #[derive(Debug, Clone, Default)] pub enum Strategy { /// Transaction is estimated and signatures are automatically added. #[default] Complete, /// Transaction is estimated but no signatures are added. /// Building without signatures will set the witness indexes of signed coins in the /// order as they appear in the inputs. Multiple coins with the same owner will have /// the same witness index. Make sure you sign the built transaction in the expected order. NoSignatures, } #[cfg_attr(not(target_arch = "wasm32"), async_trait)] impl BuildableTransaction for ScriptTransactionBuilder { type TxType = ScriptTransaction; type Strategy = ScriptBuildStrategy; fn with_build_strategy(mut self, strategy: Self::Strategy) -> Self { self.build_strategy = strategy; self } async fn build(self, provider: impl DryRunner) -> Result { self.build(provider).await } } impl sealed::Sealed for CreateTransactionBuilder {} #[cfg_attr(not(target_arch = "wasm32"), async_trait)] impl BuildableTransaction for CreateTransactionBuilder { type TxType = CreateTransaction; type Strategy = Strategy; fn with_build_strategy(mut self, strategy: Self::Strategy) -> Self { self.build_strategy = strategy; self } async fn build(self, provider: impl DryRunner) -> Result { self.build(provider).await } } impl sealed::Sealed for UploadTransactionBuilder {} #[cfg_attr(not(target_arch = "wasm32"), async_trait)] impl BuildableTransaction for UploadTransactionBuilder { type TxType = UploadTransaction; type Strategy = Strategy; fn with_build_strategy(mut self, strategy: Self::Strategy) -> Self { self.build_strategy = strategy; self } async fn build(self, provider: impl DryRunner) -> Result { self.build(provider).await } } impl sealed::Sealed for UpgradeTransactionBuilder {} #[cfg_attr(not(target_arch = "wasm32"), async_trait)] impl BuildableTransaction for UpgradeTransactionBuilder { type TxType = UpgradeTransaction; type Strategy = Strategy; fn with_build_strategy(mut self, strategy: Self::Strategy) -> Self { self.build_strategy = strategy; self } async fn build(self, provider: impl DryRunner) -> Result { self.build(provider).await } } #[cfg_attr(not(target_arch = "wasm32"), async_trait)] pub trait TransactionBuilder: BuildableTransaction + Send + sealed::Sealed { type TxType: Transaction; fn add_signer(&mut self, signer: impl Signer + Send + Sync + 'static) -> Result<&mut Self>; fn add_signers<'a>( &mut self, signers: impl IntoIterator>, ) -> Result<&mut Self>; async fn estimate_max_fee(&self, provider: impl DryRunner) -> Result; fn enable_burn(self, enable: bool) -> Self; fn with_tx_policies(self, tx_policies: TxPolicies) -> Self; fn with_inputs(self, inputs: Vec) -> Self; fn with_outputs(self, outputs: Vec) -> Self; fn with_witnesses(self, witnesses: Vec) -> Self; fn inputs(&self) -> &Vec; fn inputs_mut(&mut self) -> &mut Vec; fn outputs(&self) -> &Vec; fn outputs_mut(&mut self) -> &mut Vec; fn witnesses(&self) -> &Vec; fn witnesses_mut(&mut self) -> &mut Vec; fn with_estimation_horizon(self, block_horizon: u32) -> Self; } macro_rules! impl_tx_builder_trait { ($ty: ty, $tx_ty: ident) => { #[cfg_attr(not(target_arch = "wasm32"), async_trait)] impl $crate::types::transaction_builders::TransactionBuilder for $ty { type TxType = $tx_ty; fn add_signer(&mut self, signer: impl Signer + Send + Sync + 'static) -> Result<&mut Self> { self.validate_no_signer_available(&signer.address())?; let index_offset = self.unresolved_signers.len() as u64; self.unresolved_witness_indexes .owner_to_idx_offset .insert(signer.address().clone(), index_offset); self.unresolved_signers.push(std::sync::Arc::new(signer)); Ok(self) } fn add_signers<'a>(&mut self, signers: impl IntoIterator>) -> Result<&mut Self> { for signer in signers { self.validate_no_signer_available(&signer.address())?; let index_offset = self.unresolved_signers.len() as u64; self.unresolved_witness_indexes .owner_to_idx_offset .insert(signer.address().clone(), index_offset); self.unresolved_signers.push(signer.clone()); } Ok(self) } async fn estimate_max_fee(&self, provider: impl DryRunner) -> Result { let mut fee_estimation_tb = self .clone() .with_build_strategy(Self::Strategy::NoSignatures); // Add a temporary witness for every `Signer` to include them in the fee // estimation. let witness: Witness = Signature::default().as_ref().into(); fee_estimation_tb .witnesses_mut() .extend(repeat(witness).take(self.unresolved_signers.len())); // Temporarily enable burning to avoid errors when calculating the fee. let fee_estimation_tb = fee_estimation_tb.enable_burn(true); let mut tx = $crate::types::transaction_builders::BuildableTransaction::build( fee_estimation_tb, &provider, ) .await?; if tx.is_using_predicates() { tx.estimate_predicates(&provider, None).await?; } let consensus_parameters = provider.consensus_parameters().await?; let gas_price = provider .estimate_gas_price(self.gas_price_estimation_block_horizon) .await?; $crate::types::transaction_builders::estimate_max_fee_w_tolerance( tx.tx, self.max_fee_estimation_tolerance, gas_price, &consensus_parameters, ) } fn enable_burn(mut self, enable: bool) -> Self { self.enable_burn = enable; self } fn with_tx_policies(mut self, tx_policies: TxPolicies) -> Self { self.tx_policies = tx_policies; self } fn with_inputs(mut self, inputs: Vec) -> Self { self.inputs = inputs; self } fn with_outputs(mut self, outputs: Vec) -> Self { self.outputs = outputs; self } fn with_witnesses(mut self, witnesses: Vec) -> Self { self.witnesses = witnesses; self } fn inputs(&self) -> &Vec { self.inputs.as_ref() } fn inputs_mut(&mut self) -> &mut Vec { &mut self.inputs } fn outputs(&self) -> &Vec { self.outputs.as_ref() } fn outputs_mut(&mut self) -> &mut Vec { &mut self.outputs } fn witnesses(&self) -> &Vec { self.witnesses.as_ref() } fn witnesses_mut(&mut self) -> &mut Vec { &mut self.witnesses } fn with_estimation_horizon(mut self, block_horizon: u32) -> Self { self.gas_price_estimation_block_horizon = block_horizon; self } } impl $ty { fn validate_no_signer_available(&self, address: &$crate::types::Address) -> Result<()> { if self .unresolved_witness_indexes .owner_to_idx_offset .contains_key(address) { return Err(error_transaction!( Builder, "already added `Signer` with address: `{address}`" )); } Ok(()) } fn set_witness_indexes(&mut self) { use $crate::types::transaction_builders::TransactionBuilder; self.unresolved_witness_indexes.owner_to_idx_offset = self .inputs() .iter() .filter_map(|input| match input { Input::ResourceSigned { resource } => resource.owner(), _ => None, }) .unique() .cloned() .enumerate() .map(|(idx, owner)| (owner, idx as u64)) .collect(); } fn generate_fuel_policies(&self) -> Result { let witness_limit = match self.tx_policies.witness_limit() { Some(limit) => limit, None => self.calculate_witnesses_size()?, }; let mut policies = Policies::default().with_witness_limit(witness_limit); // `MaxFee` set to `tip` or `0` for `dry_run` policies.set(PolicyType::MaxFee, self.tx_policies.tip().or(Some(0))); policies.set(PolicyType::Maturity, self.tx_policies.maturity()); policies.set(PolicyType::Tip, self.tx_policies.tip()); policies.set(PolicyType::Expiration, self.tx_policies.expiration()); policies.set(PolicyType::Owner, self.tx_policies.owner()); Ok(policies) } fn is_using_predicates(&self) -> bool { use $crate::types::transaction_builders::TransactionBuilder; self.inputs() .iter() .any(|input| matches!(input, Input::ResourcePredicate { .. })) } fn intercept_burn(&self, base_asset_id: &$crate::types::AssetId) -> Result<()> { use std::collections::HashSet; if self.enable_burn { return Ok(()); } let assets_w_change = self .outputs .iter() .filter_map(|output| match output { Output::Change { asset_id, .. } => Some(*asset_id), _ => None, }) .collect::>(); let input_assets = self .inputs .iter() .filter_map(|input| match input { Input::ResourceSigned { resource } | Input::ResourcePredicate { resource, .. } => resource.asset_id(*base_asset_id), _ => None, }) .collect::>(); let diff = input_assets.difference(&assets_w_change).collect_vec(); if !diff.is_empty() { return Err(error_transaction!( Builder, "the following assets have no change outputs and may be burned unintentionally: {:?}. \ To resolve this, either add the necessary change outputs manually or explicitly allow asset burning \ by calling `.enable_burn(true)` on the transaction builder.", diff )); } Ok(()) } fn num_witnesses(&self) -> Result { use $crate::types::transaction_builders::TransactionBuilder; let num_witnesses = self.witnesses().len(); if num_witnesses + self.unresolved_signers.len() > u16::MAX as usize { return Err(error_transaction!( Builder, "tx exceeds maximum number of witnesses" )); } Ok(num_witnesses as u16) } fn calculate_witnesses_size(&self) -> Result { let witnesses_size = calculate_witnesses_size(&self.witnesses); let signature_size = SIGNATURE_WITNESS_SIZE * self.unresolved_witness_indexes.owner_to_idx_offset.len(); let padded_len = padded_len_usize(witnesses_size + signature_size) .ok_or_else(|| error!(Other, "witnesses size overflow"))?; Ok(padded_len as u64) } async fn set_max_fee_policy>( tx: &mut T, provider: impl DryRunner, block_horizon: u32, is_using_predicates: bool, max_fee_estimation_tolerance: f32, ) -> Result<()> { let mut wrapper_tx: $tx_ty = tx.clone().into(); if is_using_predicates { wrapper_tx.estimate_predicates(&provider, None).await?; } let gas_price = provider.estimate_gas_price(block_horizon).await?; let consensus_parameters = provider.consensus_parameters().await?; let max_fee = $crate::types::transaction_builders::estimate_max_fee_w_tolerance( wrapper_tx.tx, max_fee_estimation_tolerance, gas_price, &consensus_parameters, )?; tx.policies_mut().set(PolicyType::MaxFee, Some(max_fee)); Ok(()) } } }; } pub(crate) use impl_tx_builder_trait; pub(crate) fn estimate_max_fee_w_tolerance( tx: T, tolerance: f32, gas_price: u64, consensus_parameters: &ConsensusParameters, ) -> Result { let gas_costs = &consensus_parameters.gas_costs(); let fee_params = consensus_parameters.fee_params(); let tx_fee = TransactionFee::checked_from_tx(gas_costs, fee_params, &tx, gas_price).ok_or( error_transaction!( Builder, "error calculating `TransactionFee` in `TransactionBuilder`" ), )?; let max_fee_w_tolerance = tx_fee.max_fee() as f64 * (1.0 + f64::from(tolerance)); Ok(max_fee_w_tolerance.ceil() as u64) } impl Debug for dyn Signer + Send + Sync { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { f.debug_struct("Signer") .field("address", &self.address()) .finish() } } /// Controls the SDK behavior regarding variable transaction outputs. /// /// # Warning /// /// Estimation of variable outputs is performed by saturating the transaction with variable outputs /// and counting the number of outputs used. This process can be particularly unreliable in cases /// where the script introspects the number of variable outputs and adjusts its logic accordingly. /// The script could theoretically mint outputs until all variable outputs are utilized. /// /// In such scenarios, estimation of necessary variable outputs becomes nearly impossible. /// /// It is advised to avoid relying on automatic estimation of variable outputs if the script /// contains logic that dynamically adjusts based on the number of outputs. #[derive(Debug, Clone, Copy, PartialEq)] pub enum VariableOutputPolicy { /// Perform a dry run of the transaction estimating the minimum number of variable outputs to /// add. EstimateMinimum, /// Add exactly these many variable outputs to the transaction. Exactly(usize), } impl Default for VariableOutputPolicy { fn default() -> Self { Self::Exactly(0) } } #[derive(Debug, Clone)] pub struct ScriptTransactionBuilder { pub script: Vec, pub script_data: Vec, pub inputs: Vec, pub outputs: Vec, pub witnesses: Vec, pub tx_policies: TxPolicies, pub gas_estimation_tolerance: f32, pub max_fee_estimation_tolerance: f32, pub gas_price_estimation_block_horizon: u32, pub variable_output_policy: VariableOutputPolicy, pub build_strategy: ScriptBuildStrategy, unresolved_witness_indexes: UnresolvedWitnessIndexes, unresolved_signers: Vec>, enable_burn: bool, } impl Default for ScriptTransactionBuilder { fn default() -> Self { Self { script: Default::default(), script_data: Default::default(), inputs: Default::default(), outputs: Default::default(), witnesses: Default::default(), tx_policies: Default::default(), gas_estimation_tolerance: Default::default(), max_fee_estimation_tolerance: Default::default(), gas_price_estimation_block_horizon: GAS_ESTIMATION_BLOCK_HORIZON, variable_output_policy: Default::default(), build_strategy: Default::default(), unresolved_witness_indexes: Default::default(), unresolved_signers: Default::default(), enable_burn: false, } } } #[derive(Debug, Clone)] pub struct CreateTransactionBuilder { pub bytecode_length: u64, pub bytecode_witness_index: u16, pub storage_slots: Vec, pub inputs: Vec, pub outputs: Vec, pub witnesses: Vec, pub tx_policies: TxPolicies, pub salt: Salt, pub gas_price_estimation_block_horizon: u32, pub max_fee_estimation_tolerance: f32, pub build_strategy: Strategy, unresolved_witness_indexes: UnresolvedWitnessIndexes, unresolved_signers: Vec>, enable_burn: bool, } impl Default for CreateTransactionBuilder { fn default() -> Self { Self { bytecode_length: Default::default(), bytecode_witness_index: Default::default(), storage_slots: Default::default(), inputs: Default::default(), outputs: Default::default(), witnesses: Default::default(), tx_policies: Default::default(), salt: Default::default(), gas_price_estimation_block_horizon: GAS_ESTIMATION_BLOCK_HORIZON, max_fee_estimation_tolerance: Default::default(), build_strategy: Default::default(), unresolved_witness_indexes: Default::default(), unresolved_signers: Default::default(), enable_burn: false, } } } #[derive(Debug, Clone)] pub struct UploadTransactionBuilder { /// The root of the Merkle tree is created over the bytecode. pub root: Bytes32, /// The witness index of the subsection of the bytecode. pub witness_index: u16, /// The index of the subsection of the bytecode. pub subsection_index: u16, /// The total number of subsections on which bytecode was divided. pub subsections_number: u16, /// The proof set helps to verify the connection of the subsection to the `root`. pub proof_set: Vec, pub inputs: Vec, pub outputs: Vec, pub witnesses: Vec, pub tx_policies: TxPolicies, pub gas_price_estimation_block_horizon: u32, pub max_fee_estimation_tolerance: f32, pub build_strategy: Strategy, unresolved_witness_indexes: UnresolvedWitnessIndexes, unresolved_signers: Vec>, enable_burn: bool, } impl Default for UploadTransactionBuilder { fn default() -> Self { Self { root: Default::default(), witness_index: Default::default(), subsection_index: Default::default(), subsections_number: Default::default(), proof_set: Default::default(), inputs: Default::default(), outputs: Default::default(), witnesses: Default::default(), tx_policies: Default::default(), gas_price_estimation_block_horizon: GAS_ESTIMATION_BLOCK_HORIZON, max_fee_estimation_tolerance: Default::default(), build_strategy: Default::default(), unresolved_witness_indexes: Default::default(), unresolved_signers: Default::default(), enable_burn: false, } } } #[derive(Debug, Clone)] pub struct UpgradeTransactionBuilder { /// The purpose of the upgrade. pub purpose: UpgradePurpose, pub inputs: Vec, pub outputs: Vec, pub witnesses: Vec, pub tx_policies: TxPolicies, pub gas_price_estimation_block_horizon: u32, pub max_fee_estimation_tolerance: f32, pub build_strategy: Strategy, unresolved_witness_indexes: UnresolvedWitnessIndexes, unresolved_signers: Vec>, enable_burn: bool, } impl Default for UpgradeTransactionBuilder { fn default() -> Self { Self { purpose: UpgradePurpose::StateTransition { root: Default::default(), }, inputs: Default::default(), outputs: Default::default(), witnesses: Default::default(), tx_policies: Default::default(), gas_price_estimation_block_horizon: GAS_ESTIMATION_BLOCK_HORIZON, unresolved_witness_indexes: Default::default(), unresolved_signers: Default::default(), max_fee_estimation_tolerance: Default::default(), build_strategy: Default::default(), enable_burn: false, } } } impl_tx_builder_trait!(ScriptTransactionBuilder, ScriptTransaction); impl_tx_builder_trait!(CreateTransactionBuilder, CreateTransaction); impl_tx_builder_trait!(UploadTransactionBuilder, UploadTransaction); impl_tx_builder_trait!(UpgradeTransactionBuilder, UpgradeTransaction); impl ScriptTransactionBuilder { async fn build(mut self, provider: impl DryRunner) -> Result { let consensus_parameters = provider.consensus_parameters().await?; self.intercept_burn(consensus_parameters.base_asset_id())?; let is_using_predicates = self.is_using_predicates(); let tx = match self.build_strategy { ScriptBuildStrategy::Complete => self.resolve_fuel_tx(&provider).await?, ScriptBuildStrategy::NoSignatures => { self.set_witness_indexes(); self.unresolved_signers = Default::default(); self.resolve_fuel_tx(&provider).await? } ScriptBuildStrategy::StateReadOnly => { self.resolve_fuel_tx_for_state_reading(provider).await? } }; Ok(ScriptTransaction { is_using_predicates, tx, }) } async fn resolve_fuel_tx(self, dry_runner: impl DryRunner) -> Result