Skip to content

Commit

Permalink
Merge branch 'master' into iqdecay/test-using-fuzzer
Browse files Browse the repository at this point in the history
  • Loading branch information
iqdecay authored Nov 29, 2023
2 parents b631682 + e6b1d76 commit 0480040
Show file tree
Hide file tree
Showing 27 changed files with 483 additions and 95 deletions.
16 changes: 8 additions & 8 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ readme = "README.md"
license = "Apache-2.0"
repository = "https://github.com/FuelLabs/fuels-rs"
rust-version = "1.73.0"
version = "0.51.0"
version = "0.52.0"

[workspace.dependencies]
Inflector = "0.11.4"
Expand Down Expand Up @@ -90,10 +90,10 @@ fuel-types = { version = "0.43.0"}
fuel-vm = { version = "0.43.0"}

# Workspace projects
fuels = { version = "0.51.0", path = "./packages/fuels" }
fuels-accounts = { version = "0.51.0", path = "./packages/fuels-accounts", default-features = false }
fuels-code-gen = { version = "0.51.0", path = "./packages/fuels-code-gen", default-features = false }
fuels-core = { version = "0.51.0", path = "./packages/fuels-core", default-features = false }
fuels-macros = { version = "0.51.0", path = "./packages/fuels-macros", default-features = false }
fuels-programs = { version = "0.51.0", path = "./packages/fuels-programs", default-features = false }
fuels-test-helpers = { version = "0.51.0", path = "./packages/fuels-test-helpers", default-features = false }
fuels = { version = "0.52.0", path = "./packages/fuels" }
fuels-accounts = { version = "0.52.0", path = "./packages/fuels-accounts", default-features = false }
fuels-code-gen = { version = "0.52.0", path = "./packages/fuels-code-gen", default-features = false }
fuels-core = { version = "0.52.0", path = "./packages/fuels-core", default-features = false }
fuels-macros = { version = "0.52.0", path = "./packages/fuels-macros", default-features = false }
fuels-programs = { version = "0.52.0", path = "./packages/fuels-programs", default-features = false }
fuels-test-helpers = { version = "0.52.0", path = "./packages/fuels-test-helpers", default-features = false }
3 changes: 3 additions & 0 deletions docs/src/SUMMARY.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,9 @@
- [Running scripts](./running-scripts.md)
- [Predicates](./predicates/index.md)
- [Signatures example](./predicates/send-spend-predicate.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)
Expand Down
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 outpus 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 `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, ...])`.

`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/calling-contracts/read-only.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ You can do this with the SDK. Instead of `.call()`ing the method, use `.simulate
{{#include ../../../examples/contracts/src/lib.rs:simulate}}
```

<!-- This section should explain what happens if you try a read-only call on a method that changes stae -->
<!-- This section should explain what happens if you try a read-only call on a method that changes state -->
<!-- simulate:example:start -->
Note that if you use `.simulate()` on a method that _does_ change the state of the blockchain, it won't work properly; it will just `dry-run` it.

Expand Down
2 changes: 1 addition & 1 deletion docs/src/connecting/external-node.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ In the code example, we connected a new provider to the Testnet node and created
>
> 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 Tesnet at
> In addition to the faucet, there is a block explorer for the Testnet at
>
> [block-explorer](https://fuellabs.github.io/block-explorer-v2)
Expand Down
4 changes: 2 additions & 2 deletions docs/src/connecting/short-lived.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,13 +27,13 @@ let wallet = launch_provider_and_get_wallet().await?;
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.51.0", features = ["fuel-core-lib"] }
fuels = { version = "0.52.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.51.0", features = ["rocksdb"] }
fuels = { version = "0.52.0", features = ["rocksdb"] }
```
15 changes: 15 additions & 0 deletions docs/src/custom-transactions/custom-calls.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# 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.

## Custom contract call

```rust,ignore
{{#include ../../../examples/contracts/src/lib.rs:contract_call_tb}}
```

## Custom script call

```rust,ignore
{{#include ../../../packages/fuels/tests/scripts.rs:script_call_tb}}
```
3 changes: 3 additions & 0 deletions docs/src/custom-transactions/index.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# 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.
85 changes: 85 additions & 0 deletions docs/src/custom-transactions/transaction-builders.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
# 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()` 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 tx is finalized. The transactions resultign 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 sign the transaction builder with:

```rust,ignore
{{#include ../../../examples/cookbook/src/lib.rs:custom_tx_sign}}
```

> **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 Account 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 tx 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}}
```

We can also define transaction policies. For example, we can limit the gas price by doing the following:

```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}}
```
43 changes: 43 additions & 0 deletions examples/contracts/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -870,4 +870,47 @@ mod tests {

Ok(())
}

#[tokio::test]
async fn contract_custom_call() -> Result<()> {
use fuels::prelude::*;

setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TestContract",
project = "packages/fuels/tests/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "TestContract",
wallet = "wallet"
),
);
let provider = wallet.try_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.sign_transaction(&mut tb);

let tx = tb.build(provider).await?;

let tx_id = provider.send_transaction(tx).await?;
let tx_status = provider.tx_status(&tx_id).await?;

let response = call_handler.get_response_from(tx_status)?;

assert_eq!(counter, response.value);
// ANCHOR_END: contract_call_tb

Ok(())
}
}
117 changes: 115 additions & 2 deletions examples/cookbook/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,22 @@
#[cfg(test)]
mod tests {
use std::str::FromStr;

use fuels::{
accounts::{
predicate::Predicate, wallet::WalletUnlocked, Account, Signer, ViewOnlyAccount,
},
core::constants::BASE_ASSET_ID,
prelude::Result,
test_helpers::{setup_single_asset_coins, setup_test_provider},
types::{
transaction_builders::{BuildableTransaction, ScriptTransactionBuilder},
Bits256,
bech32::Bech32Address,
transaction::TxPolicies,
transaction_builders::{
BuildableTransaction, ScriptTransactionBuilder, TransactionBuilder,
},
tx_status::TxStatus,
AssetId,
},
};

Expand All @@ -13,6 +25,7 @@ mod tests {
use fuels::{
prelude::*,
test_helpers::{AssetConfig, WalletsConfig},
types::Bits256,
};

// ANCHOR: liquidity_abigen
Expand Down Expand Up @@ -217,4 +230,104 @@ mod tests {

Ok(())
}

#[tokio::test]
async fn custom_transaction() -> Result<()> {
let mut hot_wallet = WalletUnlocked::new_random(None);
let mut cold_wallet = WalletUnlocked::new_random(None);

let code_path = "../../packages/fuels/tests/predicates/swap/out/debug/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.address(), BASE_ASSET_ID, 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?;

hot_wallet.set_provider(provider.clone());
cold_wallet.set_provider(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 = Bech32Address::from_str(
"fuel1p8qt95dysmzrn2rmewntg6n6rg3l8ztueqafg5s6jmd9cgautrdslwdqdw",
)?;
// ANCHOR_END: custom_tx_receiver

// ANCHOR: custom_tx
let network_info = provider.network_info().await?;
let tb = ScriptTransactionBuilder::new(network_info);
// ANCHOR_END: custom_tx

// ANCHOR: custom_tx_io_base
let base_inputs = hot_wallet
.get_asset_inputs_for_amount(BASE_ASSET_ID, ask_amount)
.await?;
let base_outputs =
hot_wallet.get_asset_outputs_for_amount(&receiver, BASE_ASSET_ID, ask_amount);
// 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)
.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_sign
hot_wallet.sign_transaction(&mut tb);
// ANCHOR_END: custom_tx_sign

// 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_gas_price(1);
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

// ANCHOR: custom_tx_verify
let status = provider.tx_status(&tx_id).await?;
assert!(matches!(status, TxStatus::Success { .. }));

let balance = cold_wallet.get_asset_balance(&bridged_asset_id).await?;
assert_eq!(balance, locked_amount);
// ANCHOR_END: custom_tx_verify

Ok(())
}
}
2 changes: 1 addition & 1 deletion examples/providers/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ mod tests {
use fuels::prelude::Result;

#[tokio::test]
#[ignore] //TODO: Enable this test once beta supports the new `fuel-core` >= `0.51.0`
#[ignore] //TODO: Enable this test once beta supports the new `fuel-core` >= `0.21.0`
async fn connect_to_fuel_node() -> Result<()> {
// ANCHOR: connect_to_testnet
use std::str::FromStr;
Expand Down
Loading

0 comments on commit 0480040

Please sign in to comment.