Skip to content

Commit

Permalink
Merge branch 'master' into feature/remove-async-trait
Browse files Browse the repository at this point in the history
  • Loading branch information
MujkicA authored Jan 22, 2024
2 parents 6ee58c7 + 23a11a3 commit e84b852
Show file tree
Hide file tree
Showing 56 changed files with 817 additions and 423 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ env:
FUEL_CORE_VERSION: 0.22.0
FUEL_CORE_PATCH_BRANCH:
RUST_VERSION: 1.75.0
FORC_VERSION: 0.48.0
FORC_VERSION: 0.49.1
FORC_PATCH_BRANCH: ""
FORC_PATCH_REVISION: ""

Expand Down
18 changes: 15 additions & 3 deletions docs/src/custom-transactions/transaction-builders.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,20 +52,22 @@ We combine all of the inputs and outputs and set them on the builder:
{{#include ../../../examples/cookbook/src/lib.rs:custom_tx_io}}
```

As we have used coins that require a signature, we sign the transaction builder with:
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_sign}}
{{#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 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 transaction to spend regardless of fees. In our case, this is the **ask_amount** we are transferring to the predicate.
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 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 limit the gas price by doing the following:

```rust,ignore
Expand All @@ -83,3 +85,13 @@ Finally, we verify the transaction succeeded and that the cold storage indeed ho
```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 use the `build_without_signatures(&provider)` method and later sign the built transaction.

```rust,ignore
{{#include ../../../packages/fuels/tests/contracts.rs:tb_build_without_signatures}}
```

> **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.
2 changes: 1 addition & 1 deletion docs/src/types/address.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# `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/{{versions.fuel-types}}/fuel_types/struct.Address.html)).
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`:

Expand Down
2 changes: 1 addition & 1 deletion docs/src/types/bytes32.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,6 @@ These are the main ways of creating a `Bytes32`:
{{#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/{{versions.fuel-types}}/fuel_types/struct.Bytes32.html).
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.
20 changes: 14 additions & 6 deletions docs/src/wallets/signing.md
Original file line number Diff line number Diff line change
@@ -1,19 +1,27 @@
# Signing

Once you've instantiated your wallet in an unlocked state using one of the previously discussed methods, you can sign a message with `wallet.sign_message`. Below is a full example of how to sign and recover a message.
Once you've instantiated your wallet in an unlocked state using one of the previously discussed methods, you can sign a message with `wallet.sign`. Below is a full example of how to sign and recover a message.

```rust,ignore
{{#include ../../../packages/fuels-accounts/src/account.rs:sign_message}}
```

## Signing a transaction
## 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. Previously, the user had 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 signatures in the transaction builder and resolve the final transaction automatically. This is done by storing the secret keys of all signers until the final transaction is built.
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. Previously, the user had 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.

To sign a _transaction builder_ use the `wallet.sign_transaction`. Below is a full example of how to create a transaction and sign it.
Below is a full example of how to create a transaction builder and add signers to it.

> Note: When you sign a transaction builder the secret key is stored inside it and will not be resolved until you call `build()`!
> 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_tx}}
{{#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 ../../../packages/fuels/tests/contracts.rs:tx_sign_with}}
```
2 changes: 1 addition & 1 deletion examples/contracts/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -899,7 +899,7 @@ mod tests {
// customize the builder...

wallet.adjust_for_fee(&mut tb, 0).await?;
wallet.sign_transaction(&mut tb);
tb.add_signer(wallet.clone())?;

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

Expand Down
13 changes: 6 additions & 7 deletions examples/cookbook/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,7 @@ mod tests {
use std::str::FromStr;

use fuels::{
accounts::{
predicate::Predicate, wallet::WalletUnlocked, Account, Signer, ViewOnlyAccount,
},
accounts::{predicate::Predicate, wallet::WalletUnlocked, Account, ViewOnlyAccount},
core::constants::BASE_ASSET_ID,
prelude::Result,
test_helpers::{setup_single_asset_coins, setup_test_provider},
Expand Down Expand Up @@ -191,7 +189,8 @@ mod tests {
// ANCHOR: transfer_multiple_transaction
let mut tb =
ScriptTransactionBuilder::prepare_transfer(inputs, outputs, TxPolicies::default());
wallet_1.sign_transaction(&mut tb);
tb.add_signer(wallet_1.clone())?;

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

provider.send_transaction_and_await_commit(tx).await?;
Expand Down Expand Up @@ -296,9 +295,9 @@ mod tests {
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_add_signer
tb.add_signer(hot_wallet.clone())?;
// ANCHOR_END: custom_tx_add_signer

// ANCHOR: custom_tx_adjust
hot_wallet.adjust_for_fee(&mut tb, 100).await?;
Expand Down
23 changes: 5 additions & 18 deletions examples/predicates/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,13 @@
mod tests {
use fuels::{
accounts::{predicate::Predicate, Account},
crypto::{Message, SecretKey},
prelude::*,
types::B512,
};

#[tokio::test]
async fn predicate_example() -> Result<()> {
use fuels::accounts::fuel_crypto::SecretKey;

// ANCHOR: predicate_wallets
let secret_key1: SecretKey =
"0x862512a2363db2b3a375c0d4bbbd27172180d89f23f2e259bac850ab02619301"
Expand Down Expand Up @@ -53,22 +52,10 @@ mod tests {
});
// ANCHOR_END: predicate_coins

let data_to_sign = [0; 32];
let signature1: B512 = wallet
.sign_message(data_to_sign)
.await?
.as_ref()
.try_into()?;
let signature2: B512 = wallet2
.sign_message(data_to_sign)
.await?
.as_ref()
.try_into()?;
let signature3: B512 = wallet3
.sign_message(data_to_sign)
.await?
.as_ref()
.try_into()?;
let data_to_sign = Message::new([0; 32]);
let signature1: B512 = wallet.sign(data_to_sign).await?.as_ref().try_into()?;
let signature2: B512 = wallet2.sign(data_to_sign).await?.as_ref().try_into()?;
let signature3: B512 = wallet3.sign(data_to_sign).await?.as_ref().try_into()?;

let signatures = [signature1, signature2, signature3];

Expand Down
2 changes: 1 addition & 1 deletion examples/providers/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ mod tests {
// ANCHOR: connect_to_testnet
use std::str::FromStr;

use fuels::{accounts::fuel_crypto::SecretKey, prelude::*};
use fuels::{crypto::SecretKey, prelude::*};

// Create a provider pointing to the testnet.
// This example will not work as the testnet does not support the new version of fuel-core
Expand Down
2 changes: 1 addition & 1 deletion examples/wallets/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ mod tests {
// ANCHOR: create_wallet_from_secret_key
use std::str::FromStr;

use fuels::{accounts::fuel_crypto::SecretKey, prelude::*};
use fuels::{crypto::SecretKey, prelude::*};

// Use the test helper to setup a test provider.
let provider = setup_test_provider(vec![], vec![], None, None).await?;
Expand Down
60 changes: 22 additions & 38 deletions packages/fuels-accounts/src/account.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,6 @@
use std::{collections::HashMap, fmt::Display};

use fuel_core_client::client::pagination::{PaginatedResult, PaginationRequest};
#[doc(no_inline)]
pub use fuel_crypto;
use fuel_crypto::Signature;
use fuel_tx::{Output, Receipt, TxId, TxPointer, UtxoId};
use fuel_types::{AssetId, Bytes32, ContractId, Nonce};
use fuels_core::{
Expand All @@ -30,21 +27,6 @@ use crate::{
provider::{Provider, ResourceFilter},
};

/// Trait for signing transactions and messages
///
/// Implement this trait to support different signing modes, e.g. Ledger, hosted etc.
pub trait Signer: std::fmt::Debug + Send + Sync {
type Error: std::error::Error + Send + Sync;

async fn sign_message<S: Send + Sync + AsRef<[u8]>>(
&self,
message: S,
) -> std::result::Result<Signature, Self::Error>;

/// Signs the transaction
fn sign_transaction(&self, message: &mut impl TransactionBuilder);
}

#[derive(Debug)]
pub struct AccountError(String);

Expand Down Expand Up @@ -189,7 +171,9 @@ pub trait Account: ViewOnlyAccount {
}

// Add signatures to the builder if the underlying account is a wallet
fn add_witnessses<Tb: TransactionBuilder>(&self, _tb: &mut Tb) {}
fn add_witnesses<Tb: TransactionBuilder>(&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.
Expand All @@ -209,7 +193,7 @@ pub trait Account: ViewOnlyAccount {
let mut tx_builder =
ScriptTransactionBuilder::prepare_transfer(inputs, outputs, tx_policies);

self.add_witnessses(&mut tx_builder);
self.add_witnesses(&mut tx_builder)?;

let used_base_amount = if asset_id == AssetId::BASE { amount } else { 0 };
self.adjust_for_fee(&mut tx_builder, used_base_amount)
Expand Down Expand Up @@ -271,8 +255,9 @@ pub trait Account: ViewOnlyAccount {
tx_policies,
);

self.add_witnessses(&mut tb);
self.add_witnesses(&mut tb)?;
self.adjust_for_fee(&mut tb, balance).await?;

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

let tx_id = tx.id(provider.chain_id());
Expand Down Expand Up @@ -305,8 +290,9 @@ pub trait Account: ViewOnlyAccount {
tx_policies,
);

self.add_witnessses(&mut tb);
self.add_witnesses(&mut tb)?;
self.adjust_for_fee(&mut tb, amount).await?;

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

let tx_id = tx.id(provider.chain_id());
Expand All @@ -325,9 +311,12 @@ pub trait Account: ViewOnlyAccount {
mod tests {
use std::str::FromStr;

use fuel_crypto::{Message, SecretKey};
use fuel_crypto::{Message, SecretKey, Signature};
use fuel_tx::{Address, ConsensusParameters, Output, Transaction as FuelTransaction};
use fuels_core::types::{transaction::Transaction, transaction_builders::DryRunner};
use fuels_core::{
traits::Signer,
types::{transaction::Transaction, transaction_builders::DryRunner},
};
use rand::{rngs::StdRng, RngCore, SeedableRng};

use super::*;
Expand All @@ -348,15 +337,13 @@ mod tests {
// Create a wallet using the private key created above.
let wallet = WalletUnlocked::new_from_private_key(secret, None);

let message = "my message";

let signature = wallet.sign_message(message).await?;
let message = Message::new("my message".as_bytes());
let signature = wallet.sign(message).await?;

// Check if signature is what we expect it to be
assert_eq!(signature, Signature::from_str("0x8eeb238db1adea4152644f1cd827b552dfa9ab3f4939718bb45ca476d167c6512a656f4d4c7356bfb9561b14448c230c6e7e4bd781df5ee9e5999faa6495163d")?);

// Recover address that signed the message
let message = Message::new(message);
let recovered_address = signature.recover(&message)?;

assert_eq!(wallet.address().hash(), recovered_address.hash());
Expand All @@ -368,6 +355,7 @@ mod tests {
Ok(())
}

#[derive(Default)]
struct MockDryRunner {
c_param: ConsensusParameters,
}
Expand All @@ -386,7 +374,7 @@ mod tests {

#[tokio::test]
async fn sign_tx_and_verify() -> std::result::Result<(), Box<dyn std::error::Error>> {
// ANCHOR: sign_tx
// ANCHOR: sign_tb
let secret = SecretKey::from_str(
"5f70feeff1f229e4a95e1056e8b4d80d0b24b565674860cc213bdb07127ce1b1",
)?;
Expand Down Expand Up @@ -417,23 +405,19 @@ mod tests {
)
};

// Sign the transaction
wallet.sign_transaction(&mut tb); // Add the private key to the transaction builder
// ANCHOR_END: sign_tx
// Add `Signer` to the transaction builder
tb.add_signer(wallet.clone())?;
// ANCHOR_END: sign_tb

let tx = tb
.build(&MockDryRunner {
c_param: ConsensusParameters::default(),
})
.await?; // Resolve signatures and add corresponding witness indexes
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 = Signature::sign(&wallet.private_key, &message);
let signature = wallet.sign(message).await?;

// Check if the signatures are the same
assert_eq!(signature, tx_signature);
Expand Down
2 changes: 1 addition & 1 deletion packages/fuels-accounts/src/provider.rs
Original file line number Diff line number Diff line change
Expand Up @@ -221,7 +221,7 @@ impl Provider {
tx.precompute(&self.chain_id())?;

let chain_info = self.chain_info().await?;
tx.check_without_signatures(
tx.check(
chain_info.latest_block.header.height,
self.consensus_parameters(),
)?;
Expand Down
Loading

0 comments on commit e84b852

Please sign in to comment.