Skip to content

Commit

Permalink
refactor!: unify call handlers to CallHandler (#1402)
Browse files Browse the repository at this point in the history
The `type-state pattern` is used on the `CallHandler` where the
`states(calls)` are `ContractCall`, `ScriptCall` and `Vec<ContractCall`
for the multi-call case.

BREAKING CHANGE:
- `FuelCallResponse` renamed to `CallResponse`
- `ContractCallHandler` removed in favor of `CallHandler`
- `ScriptCallHandler` removed in favor of `CallHandler`
- `method_hash` removed in favor of `CallHandler::new_contract_call`
  • Loading branch information
hal3e authored Jun 18, 2024
1 parent a893bb7 commit 80fe445
Show file tree
Hide file tree
Showing 37 changed files with 1,433 additions and 1,580 deletions.
10 changes: 5 additions & 5 deletions docs/src/calling-contracts/call-response.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,19 +6,19 @@ You've probably noticed that you're often chaining `.call().await.unwrap()`. Tha

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<FuelCallResponse, Error>` returned by the contract call.
3. `.unwrap()` the `Result<CallResponse, Error>` returned by the contract call.
<!-- chaining:example:end -->

<!-- This section should preface what the `FuelCallResponse` is -->
<!-- This section should preface what the `CallResponse` is -->
<!-- call_resp:example:start -->
Once you unwrap the `FuelCallResponse`, you have access to this struct:
Once you unwrap the `CallResponse`, you have access to this struct:
<!-- call_resp:example:end -->

```rust,ignore
{{#include ../../../packages/fuels-programs/src/call_response.rs:fuel_call_response}}
{{#include ../../../packages/fuels-programs/src/responses/call.rs:call_response}}
```

<!-- This section should explain the fields of the `FuelCallResponse` struct -->
<!-- This section should explain the fields of the `CallResponse` struct -->
<!-- call_resp_fields:example:start -->
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).

Expand Down
2 changes: 1 addition & 1 deletion docs/src/calling-contracts/cost-estimation.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Estimating contract call cost

With the function `estimate_transaction_cost(tolerance: Option<f64>, block_horizon: Option<u32>)` provided by `ContractCallHandler` and `MultiContractCallHandler`, you can get a cost estimation for a specific call. The return type, `TransactionCost`, is a struct that contains relevant information for the estimation:
With the function `estimate_transaction_cost(tolerance: Option<f64>, block_horizon: Option<u32>)` 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}}
Expand Down
2 changes: 1 addition & 1 deletion docs/src/calling-contracts/logs.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ Consider the following contract method:
{{#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::<T>` from a `FuelCallResponse`, where `T` is the type of the logged variables you want to retrieve. The result will be a `Vec<T>`:
You can access the logged values in Rust by calling `decode_logs_with_type::<T>` from a `CallResponse`, where `T` is the type of the logged variables you want to retrieve. The result will be a `Vec<T>`:

```rust,ignore
{{#include ../../../e2e/tests/logs.rs:produce_logs}}
Expand Down
10 changes: 4 additions & 6 deletions docs/src/calling-contracts/multicalls.md
Original file line number Diff line number Diff line change
@@ -1,20 +1,20 @@
# Multiple contract calls

With `MultiContractCallHandler`, 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:
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()`.

Next, you provide the prepared calls to your `MultiContractCallHandler` and optionally configure transaction policies:
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 `MultiContractCallHandler`.
> **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:

Expand All @@ -30,10 +30,8 @@ To get the output values of the bundled calls, you need to provide explicit type
{{#include ../../../examples/contracts/src/lib.rs:multi_call_values}}
```

You can also interact with the `FuelCallResponse` by moving the type annotation to the invoked method:
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}}
```

> **Note:** The `MultiContractCallHandler` supports only one contract call that returns a heap type. Because of the way heap types are handled, this contract call needs to be at the last position, i.e., added last with `add_call`. This is a temporary limitation that we hope to lift soon. In the meantime, if you have multiple calls handling heap types, split them across multiple regular, single calls.
2 changes: 1 addition & 1 deletion docs/src/calling-contracts/other-contracts.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# 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 `ContractCallHandler` 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, ...])`.
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.

Expand Down
2 changes: 1 addition & 1 deletion docs/src/custom-transactions/custom-calls.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Custom contract and script calls

When preparing a contract call via `ContractCallHandler` or a script call via `ScriptCallHandler`, 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 `ContractCallHandler` or `ScriptCallHandler` 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.
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

Expand Down
5 changes: 3 additions & 2 deletions e2e/build.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
use flate2::read::GzDecoder;
use fuels_accounts::provider::SUPPORTED_FUEL_CORE_VERSION;
use std::{
io::Cursor,
path::{Path, PathBuf},
};

use flate2::read::GzDecoder;
use fuels_accounts::provider::SUPPORTED_FUEL_CORE_VERSION;
use tar::Archive;

struct Downloader {
Expand Down
4 changes: 2 additions & 2 deletions e2e/tests/bindings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ async fn compile_bindings_from_contract_file() {
.methods()
.takes_int_returns_bool(42);

let encoded_args = call_handler.contract_call.encoded_args.unwrap();
let encoded_args = call_handler.call.encoded_args.unwrap();

assert_eq!(encoded_args, [0, 0, 0, 42]);
}
Expand Down Expand Up @@ -95,7 +95,7 @@ async fn compile_bindings_from_inline_contract() -> Result<()> {
let contract_instance = SimpleContract::new(null_contract_id(), wallet);

let call_handler = contract_instance.methods().takes_ints_returns_bool(42_u32);
let encoded_args = call_handler.contract_call.encoded_args.unwrap();
let encoded_args = call_handler.call.encoded_args.unwrap();

assert_eq!(encoded_args, [0, 0, 0, 42]);

Expand Down
75 changes: 29 additions & 46 deletions e2e/tests/contracts.rs
Original file line number Diff line number Diff line change
Expand Up @@ -188,9 +188,7 @@ async fn test_multi_call_beginner() -> Result<()> {
let call_handler_1 = contract_methods.get_single(7);
let call_handler_2 = contract_methods.get_single(42);

let mut multi_call_handler = MultiContractCallHandler::new(wallet.clone());

multi_call_handler
let multi_call_handler = CallHandler::new_multi_call(wallet.clone())
.add_call(call_handler_1)
.add_call(call_handler_2);

Expand Down Expand Up @@ -228,9 +226,7 @@ async fn test_multi_call_pro() -> Result<()> {
let call_handler_5 = contract_methods.get_array([7; 2]);
let call_handler_6 = contract_methods.get_array([42; 2]);

let mut multi_call_handler = MultiContractCallHandler::new(wallet.clone());

multi_call_handler
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)
Expand Down Expand Up @@ -348,9 +344,7 @@ async fn mult_call_has_same_estimated_and_used_gas() -> Result<()> {
let call_handler_1 = contract_methods.initialize_counter(42);
let call_handler_2 = contract_methods.get_array([42; 2]);

let mut multi_call_handler = MultiContractCallHandler::new(wallet.clone());

multi_call_handler
let multi_call_handler = CallHandler::new_multi_call(wallet.clone())
.add_call(call_handler_1)
.add_call(call_handler_2);

Expand Down Expand Up @@ -473,7 +467,7 @@ async fn test_large_return_data() -> Result<()> {

let res = contract_methods.get_contract_id().call().await?;

// First `value` is from `FuelCallResponse`.
// First `value` is from `CallResponse`.
// Second `value` is from the `ContractId` type.
assert_eq!(
res.value,
Expand Down Expand Up @@ -733,11 +727,11 @@ async fn test_output_variable_estimation_multicall() -> Result<()> {
let amount = 1000;
let total_amount = amount * NUM_OF_CALLS;

let mut multi_call_handler = MultiContractCallHandler::new(wallets[0].clone());
(0..NUM_OF_CALLS).for_each(|_| {
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.add_call(call_handler);
});
multi_call_handler = multi_call_handler.add_call(call_handler);
}

wallets[0]
.force_transfer_to_contract(
Expand All @@ -751,7 +745,7 @@ async fn test_output_variable_estimation_multicall() -> Result<()> {

let base_layer_address = Bits256([1u8; 32]);
let call_handler = contract_methods.send_message(base_layer_address, amount);
multi_call_handler.add_call(call_handler);
multi_call_handler = multi_call_handler.add_call(call_handler);

let _ = multi_call_handler
.with_variable_output_policy(VariableOutputPolicy::EstimateMinimum)
Expand Down Expand Up @@ -938,18 +932,18 @@ async fn test_output_variable_contract_id_estimation_multicall() -> Result<()> {
let contract_methods = contract_caller_instance.methods();

let mut multi_call_handler =
MultiContractCallHandler::new(wallet.clone()).with_tx_policies(Default::default());
CallHandler::new_multi_call(wallet.clone()).with_tx_policies(Default::default());

(0..3).for_each(|_| {
for _ in 0..3 {
let call_handler = contract_methods.increment_from_contract(lib_contract_id, 42);
multi_call_handler.add_call(call_handler);
});
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.add_call(call_handler);
multi_call_handler = multi_call_handler.add_call(call_handler);

let call_response = multi_call_handler
.determine_missing_contracts(None)
Expand Down Expand Up @@ -1172,9 +1166,7 @@ async fn multi_call_from_calls_with_different_account_types() -> Result<()> {
let call_handler_1 = contract_methods_wallet.initialize_counter(42);
let call_handler_2 = contract_methods_predicate.get_array([42; 2]);

let mut multi_call_handler = MultiContractCallHandler::new(wallet);

multi_call_handler
let _multi_call_handler = CallHandler::new_multi_call(wallet)
.add_call(call_handler_1)
.add_call(call_handler_2);

Expand Down Expand Up @@ -1434,7 +1426,7 @@ async fn can_configure_decoding_of_contract_return() -> Result<()> {
}
{
// Multi call: Will not work if max_tokens not big enough
MultiContractCallHandler::new(wallet.clone())
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(
Expand All @@ -1443,7 +1435,7 @@ async fn can_configure_decoding_of_contract_return() -> Result<()> {
}
{
// Multi call: Works when configured
MultiContractCallHandler::new(wallet.clone())
CallHandler::new_multi_call(wallet.clone())
.add_call(methods.i_return_a_1k_el_array())
.with_decoder_config(DecoderConfig {
max_tokens: 1001,
Expand Down Expand Up @@ -1483,9 +1475,7 @@ async fn test_contract_submit_and_response() -> Result<()> {
let call_handler_1 = contract_methods.get_single(7);
let call_handler_2 = contract_methods.get_single(42);

let mut multi_call_handler = MultiContractCallHandler::new(wallet.clone());

multi_call_handler
let multi_call_handler = CallHandler::new_multi_call(wallet.clone())
.add_call(call_handler_1)
.add_call(call_handler_2);

Expand Down Expand Up @@ -1514,34 +1504,30 @@ async fn test_heap_type_multicall() -> Result<()> {
),
Deploy(
name = "contract_instance",
contract = "TestContract",
contract = "VectorOutputContract",
wallet = "wallet"
),
Deploy(
name = "contract_instance_2",
contract = "VectorOutputContract",
contract = "TestContract",
wallet = "wallet"
),
);

{
// One heap type at the last position is allowed
let call_handler_1 = contract_instance.methods().get_single(7);
let call_handler_2 = contract_instance.methods().get_single(42);
let call_handler_3 = contract_instance_2.methods().u8_in_vec(3);

let mut multi_call_handler = MultiContractCallHandler::new(wallet.clone());
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);

multi_call_handler
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 handle = multi_call_handler.submit().await?;
let (val_1, val_2, val_3): (u64, u64, Vec<u8>) = handle.response().await?.value;
let (val_1, val_2, val_3): (Vec<u8>, u64, Vec<u8>) = multi_call_handler.call().await?.value;

assert_eq!(val_1, 7);
assert_eq!(val_2, 42);
assert_eq!(val_1, vec![0, 1, 2, 3, 4]);
assert_eq!(val_2, 7);
assert_eq!(val_3, vec![0, 1, 2]);
}

Expand Down Expand Up @@ -1638,9 +1624,7 @@ async fn test_arguments_with_gas_forwarded() -> Result<()> {
let call_handler_1 = contract_instance.methods().get_single(x);
let call_handler_2 = contract_instance_2.methods().u32_vec(vec_input);

let mut multi_call_handler = MultiContractCallHandler::new(wallet.clone());

multi_call_handler
let multi_call_handler = CallHandler::new_multi_call(wallet.clone())
.add_call(call_handler_1)
.add_call(call_handler_2);

Expand Down Expand Up @@ -1871,8 +1855,7 @@ async fn variable_output_estimation_is_optimized() -> Result<()> {

#[tokio::test]
async fn contract_call_with_non_zero_base_asset_id_and_tip() -> Result<()> {
use fuels::prelude::*;
use fuels::tx::ConsensusParameters;
use fuels::{prelude::*, tx::ConsensusParameters};

abigen!(Contract(
name = "MyContract",
Expand Down
Loading

0 comments on commit 80fe445

Please sign in to comment.